diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..bcb58e41cdd0bc2e44d80a132865e7f076dfc81c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,187 @@ +# ============================================================================= +# .dockerignore - Exclude files from Docker build context +# ============================================================================= +# This file ensures clean, efficient Docker builds by excluding unnecessary +# files from the build context. Smaller context = faster builds. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Python Artifacts +# ----------------------------------------------------------------------------- +# Bytecode, compiled files, and Python build artifacts +__pycache__/ +**/__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# ----------------------------------------------------------------------------- +# Virtual Environments +# ----------------------------------------------------------------------------- +# Local Python virtual environments - Docker creates its own +therm_venv/ +venv/ +.venv/ +env/ +ENV/ +.env.local + +# ----------------------------------------------------------------------------- +# IDE and Editor Files +# ----------------------------------------------------------------------------- +# Editor-specific settings and temporary files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.project +.pydevproject +.settings/ +*.sublime-project +*.sublime-workspace + +# ----------------------------------------------------------------------------- +# Version Control +# ----------------------------------------------------------------------------- +# Git history and configuration (not needed in container) +.git/ +.gitignore +.gitattributes + +# ----------------------------------------------------------------------------- +# Testing and Coverage +# ----------------------------------------------------------------------------- +# Test files, caches and coverage reports +tests/ +.pytest_cache/ +htmlcov/ +.coverage +.coverage.* +coverage.xml +*.cover +.hypothesis/ +.tox/ +.nox/ + +# ----------------------------------------------------------------------------- +# Development Tools +# ----------------------------------------------------------------------------- +# Linting, type checking, and pre-commit caches +.pre-commit-config.yaml +.mypy_cache/ +.ruff_cache/ +.dmypy.json +dmypy.json + +# ----------------------------------------------------------------------------- +# Data and Build Pipeline +# ----------------------------------------------------------------------------- +# Raw data and build scripts (artifacts downloaded at runtime) +data/ +scripts/ + +# ----------------------------------------------------------------------------- +# Frontend +# ----------------------------------------------------------------------------- +# Next.js frontend has its own Dockerfile +frontend/ + +# ----------------------------------------------------------------------------- +# Documentation and Project Files +# ----------------------------------------------------------------------------- +# Markdown docs, documentation directories, and project-specific files +*.md +docs/ +CLAUDE.md +PROJECT_README.md +RAG_Chatbot_Plan.md +IMPLEMENTATION_PLAN.md +README.rst +LICENSE + +# ----------------------------------------------------------------------------- +# Development Files +# ----------------------------------------------------------------------------- +# Development-only files not needed in production +commands +raw_data/ + +# ----------------------------------------------------------------------------- +# Environment and Secrets +# ----------------------------------------------------------------------------- +# Environment files containing secrets (injected at runtime) +.env +.env.* +*.local +.secrets + +# ----------------------------------------------------------------------------- +# Build Artifacts +# ----------------------------------------------------------------------------- +# Python package build outputs +dist/ +build/ +*.egg-info/ +*.egg +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +MANIFEST + +# ----------------------------------------------------------------------------- +# Jupyter Notebooks +# ----------------------------------------------------------------------------- +# Notebooks and checkpoints (development only) +*.ipynb +.ipynb_checkpoints/ + +# ----------------------------------------------------------------------------- +# Logs +# ----------------------------------------------------------------------------- +# Log files generated during development +*.log +logs/ +log/ + +# ----------------------------------------------------------------------------- +# Docker Files +# ----------------------------------------------------------------------------- +# Prevent recursive Docker context issues +Dockerfile* +docker-compose* +.docker/ +.dockerignore + +# ----------------------------------------------------------------------------- +# Miscellaneous +# ----------------------------------------------------------------------------- +# OS-generated files and other artifacts +.DS_Store +Thumbs.db +*.bak +*.tmp +*.temp +.cache/ +tmp/ +temp/ + +# ----------------------------------------------------------------------------- +# CI/CD Configuration +# ----------------------------------------------------------------------------- +# CI configuration files not needed in container +.github/ +.gitlab-ci.yml +.travis.yml +.circleci/ +Makefile +Taskfile.yml + +# ============================================================================= +# IMPORTANT: Files that ARE included (not ignored): +# - src/ (application source code) +# - pyproject.toml (Poetry dependency specification) +# - poetry.lock (reproducible dependency versions) +# - py.typed (type hint marker file) +# ============================================================================= diff --git a/.gitignore b/.gitignore index 694a96a20b9082b1bfaf911a3490662b5d47ad51..d799270fb0a7140eea762c090567423b1a3b7d29 100644 --- a/.gitignore +++ b/.gitignore @@ -152,8 +152,16 @@ cython_debug/ # User requested exclusions IMPLEMENTATION_PLAN.md RAG_Chatbot_Plan.md +PROJECT_README.md +CLAUDE.md +commands data/ +raw_data/ +tests/ + +# Hidden directories (except specific ones) .*/ !.gitignore !.pre-commit-config.yaml !.env.example +!.dockerignore diff --git a/CLAUDE.md b/CLAUDE.md index 13574b03410bee21783ad7faa43a8d07066223e0..b0e35a715e461e5b6c2982e3de580177f0c4a411 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ Downloads prebuilt artifacts from HF dataset, serves FastAPI backend with hybrid - `src/rag_chatbot/chunking/` - Structure-aware chunking with heading inheritance - `src/rag_chatbot/embeddings/` - BGE encoder with float16 storage - `src/rag_chatbot/retrieval/` - Hybrid retriever (dense + BM25 with RRF fusion) -- `src/rag_chatbot/llm/` - Provider registry with fallback: Gemini -> Groq -> DeepSeek +- `src/rag_chatbot/llm/` - Provider registry with fallback: Gemini -> DeepSeek -> Anthropic - `src/rag_chatbot/api/` - FastAPI endpoints with SSE streaming - `src/rag_chatbot/qlog/` - Async query logging to HF dataset @@ -77,7 +77,7 @@ Downloads prebuilt artifacts from HF dataset, serves FastAPI backend with hybrid ## Environment Variables Required secrets for deployment: -- `GEMINI_API_KEY`, `DEEPSEEK_API_KEY`, `GROQ_API_KEY` - LLM providers +- `GEMINI_API_KEY`, `DEEPSEEK_API_KEY`, `ANTHROPIC_API_KEY` - LLM providers - `HF_TOKEN` - HuggingFace authentication Key configuration: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0d96cd00c2135df2bd974d75aed1f77d94022842 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,654 @@ +# ============================================================================= +# RAG Chatbot Combined Dockerfile - HuggingFace Spaces Production Deployment +# ============================================================================= +# This is a multi-stage Dockerfile that combines the Next.js frontend and +# FastAPI backend into a single container, using nginx as a reverse proxy. +# +# Architecture Overview: +# - External port: 7860 (HuggingFace Spaces requirement) +# - nginx: Reverse proxy on port 7860 +# - Frontend: Next.js standalone on internal port 3000 +# - Backend: FastAPI/uvicorn on internal port 8000 +# +# Routing: +# - /api/* -> Backend (FastAPI) on port 8000 +# - /* -> Frontend (Next.js) on port 3000 +# +# Stages (4 total): +# 1. frontend-deps: Install npm dependencies +# 2. frontend-builder: Build Next.js standalone output +# 3. backend-builder: Export Python dependencies via Poetry +# 4. runtime: Final image with nginx + Node.js + Python +# +# Target image size: < 1.5GB (no torch, no build dependencies) +# Base image: python:3.11-slim (Debian-based for nginx availability) +# +# Build command: +# docker build -t rag-chatbot . +# +# Run command: +# docker run -p 7860:7860 \ +# -e GEMINI_API_KEY=xxx \ +# -e HF_TOKEN=xxx \ +# rag-chatbot +# +# ============================================================================= + + +# ============================================================================= +# STAGE 1: FRONTEND DEPENDENCIES +# ============================================================================= +# Purpose: Install all npm dependencies in an isolated stage +# This stage uses Node.js Alpine for minimal footprint during dependency install. +# The node_modules are passed to the builder stage; this stage is discarded. +# ============================================================================= +FROM node:22-alpine AS frontend-deps + +# ----------------------------------------------------------------------------- +# Install System Dependencies +# ----------------------------------------------------------------------------- +# libc6-compat: Required for some native Node.js modules that expect glibc +# Alpine uses musl libc by default, and this compatibility layer helps with +# packages that have native bindings compiled against glibc. +# ----------------------------------------------------------------------------- +RUN apk add --no-cache libc6-compat + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Copy Package Files +# ----------------------------------------------------------------------------- +# Copy only package.json and package-lock.json for optimal layer caching. +# This layer is cached until these files change, avoiding unnecessary +# reinstalls when only source code changes. +# ----------------------------------------------------------------------------- +COPY frontend/package.json frontend/package-lock.json ./ + +# ----------------------------------------------------------------------------- +# Install Dependencies +# ----------------------------------------------------------------------------- +# npm ci (Clean Install) is preferred because: +# - Faster: Uses exact versions from lock file +# - Reproducible: Guarantees identical dependency tree +# - Strict: Fails if lock file is out of sync +# - Clean: Removes existing node_modules before install +# +# --prefer-offline: Use cached packages when available (faster in CI/CD) +# Clean npm cache after install to reduce layer size. +# ----------------------------------------------------------------------------- +RUN npm ci --prefer-offline && npm cache clean --force + + +# ============================================================================= +# STAGE 2: FRONTEND BUILDER +# ============================================================================= +# Purpose: Build the Next.js application with standalone output +# This stage compiles TypeScript, bundles assets, and creates the minimal +# standalone server that will be copied to the runtime image. +# ============================================================================= +FROM node:22-alpine AS frontend-builder + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Copy Dependencies from frontend-deps Stage +# ----------------------------------------------------------------------------- +# Reuse the installed node_modules to avoid reinstalling. +# This ensures consistent dependencies across stages. +# ----------------------------------------------------------------------------- +COPY --from=frontend-deps /app/node_modules ./node_modules + +# ----------------------------------------------------------------------------- +# Copy Frontend Source Code +# ----------------------------------------------------------------------------- +# Copy all frontend files needed for the build. +# The .dockerignore in frontend/ should exclude node_modules, .next, etc. +# ----------------------------------------------------------------------------- +COPY frontend/ . + +# ----------------------------------------------------------------------------- +# Build-Time Environment Variables +# ----------------------------------------------------------------------------- +# NEXT_PUBLIC_API_URL: Empty string = same-origin requests via nginx +# The frontend will use relative URLs like /api/query, and nginx will +# proxy these to the backend. This avoids CORS and simplifies deployment. +# +# NEXT_TELEMETRY_DISABLED: Prevent telemetry during build +# +# NODE_ENV: Production mode for optimized output +# ----------------------------------------------------------------------------- +ENV NEXT_PUBLIC_API_URL="" +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +# ----------------------------------------------------------------------------- +# Build the Application +# ----------------------------------------------------------------------------- +# Run Next.js production build. The --webpack flag ensures compatibility +# with the custom webpack configuration in next.config.ts. +# +# Output structure: +# .next/standalone/ - Minimal Node.js server with bundled deps +# .next/static/ - Static assets (JS, CSS chunks) +# public/ - Static files (images, fonts) +# ----------------------------------------------------------------------------- +RUN npx next build --webpack + + +# ============================================================================= +# STAGE 3: BACKEND BUILDER +# ============================================================================= +# Purpose: Use Poetry to export serve-only dependencies to requirements.txt +# This stage is discarded after generating the requirements file. +# We don't need torch, sentence-transformers, or build tools in production. +# ============================================================================= +FROM python:3.11-slim AS backend-builder + +# ----------------------------------------------------------------------------- +# Install Poetry +# ----------------------------------------------------------------------------- +# Poetry manages Python dependencies. We use the official installer for +# proper isolation. The version is pinned for reproducible builds. +# ----------------------------------------------------------------------------- +ENV POETRY_VERSION=1.8.2 +ENV POETRY_HOME=/opt/poetry +ENV PATH="${POETRY_HOME}/bin:${PATH}" + +# Install Poetry using the official installer script +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && curl -sSL https://install.python-poetry.org | python3 - \ + && apt-get purge -y curl \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /build + +# ----------------------------------------------------------------------------- +# Copy Dependency Files +# ----------------------------------------------------------------------------- +# Copy only pyproject.toml and poetry.lock for dependency resolution. +# This layer is cached until these files change. +# ----------------------------------------------------------------------------- +COPY pyproject.toml poetry.lock ./ + +# ----------------------------------------------------------------------------- +# Export Requirements +# ----------------------------------------------------------------------------- +# Export only main + serve dependencies (no dense, build, or dev groups). +# +# INCLUDED: +# - Core: pydantic, numpy, httpx, tiktoken, rank-bm25, faiss-cpu, etc. +# - Serve: fastapi, uvicorn, sse-starlette +# +# EXCLUDED: +# - Dense: torch, sentence-transformers (too large, not needed for CPU serving) +# - Build: pymupdf4llm, pyarrow, datasets (offline pipeline only) +# - Dev: mypy, ruff, pytest (development tools only) +# +# --without-hashes: Required for pip compatibility +# ----------------------------------------------------------------------------- +RUN poetry export --only main,serve --without-hashes -f requirements.txt -o requirements.txt + + +# ============================================================================= +# STAGE 4: RUNTIME +# ============================================================================= +# Purpose: Final production image with nginx, Node.js, and Python +# This image serves both frontend and backend through nginx reverse proxy. +# +# Components: +# - nginx: Reverse proxy on port 7860 +# - Node.js: Runs Next.js standalone server on port 3000 +# - Python: Runs FastAPI/uvicorn on port 8000 +# +# Security: +# - Non-root nginx worker processes +# - Minimal installed packages +# - No build tools or development dependencies +# ============================================================================= +FROM python:3.11-slim AS runtime + +# ----------------------------------------------------------------------------- +# Environment Variables +# ----------------------------------------------------------------------------- +# PYTHONDONTWRITEBYTECODE: Don't write .pyc files (reduces image size) +# PYTHONUNBUFFERED: Ensure logs appear immediately in container output +# PYTHONPATH: Add src/ to Python path for module imports +# NODE_ENV: Production mode for Node.js +# ----------------------------------------------------------------------------- +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app/backend/src +ENV NODE_ENV=production + +# ----------------------------------------------------------------------------- +# Install System Dependencies +# ----------------------------------------------------------------------------- +# nginx: Reverse proxy server +# curl: Health checks +# supervisor: Process manager to run multiple services +# gnupg: Required for adding NodeSource GPG key +# ca-certificates: SSL certificates for HTTPS +# +# Node.js 22.x is installed from NodeSource repository for Debian. +# This provides an up-to-date Node.js version compatible with Next.js. +# ----------------------------------------------------------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + nginx \ + curl \ + supervisor \ + gnupg \ + ca-certificates \ + # Add NodeSource repository for Node.js 22.x + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + # Clean up apt cache to reduce image size + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# ----------------------------------------------------------------------------- +# Create Application User +# ----------------------------------------------------------------------------- +# Create a non-root user for running the application services. +# Using UID/GID 1000 for compatibility with common host systems. +# +# Note: nginx master process runs as root (required for port binding), +# but worker processes run as www-data (configured in nginx.conf). +# ----------------------------------------------------------------------------- +RUN groupadd --gid 1000 appgroup \ + && useradd --uid 1000 --gid appgroup --shell /bin/false --no-create-home appuser + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Copy Python Requirements from Backend Builder +# ----------------------------------------------------------------------------- +# The requirements.txt was generated by Poetry and contains only serve deps. +# ----------------------------------------------------------------------------- +COPY --from=backend-builder /build/requirements.txt ./requirements.txt + +# ----------------------------------------------------------------------------- +# Install Python Dependencies +# ----------------------------------------------------------------------------- +# Install all serve dependencies using pip. +# Flags: +# --no-cache-dir: Don't cache packages (reduces image size) +# --no-compile: Don't compile .py to .pyc (PYTHONDONTWRITEBYTECODE handles this) +# ----------------------------------------------------------------------------- +RUN pip install --no-cache-dir --no-compile -r requirements.txt + +# ----------------------------------------------------------------------------- +# Copy Backend Source Code +# ----------------------------------------------------------------------------- +# Copy the Python source code to /app/backend/src/ +# PYTHONPATH=/app/backend/src allows importing rag_chatbot module +# ----------------------------------------------------------------------------- +COPY src/ /app/backend/src/ + +# ----------------------------------------------------------------------------- +# Copy Frontend Standalone Build +# ----------------------------------------------------------------------------- +# Copy the Next.js standalone output from the frontend-builder stage. +# Structure: +# /app/frontend/server.js - Next.js production server +# /app/frontend/.next/ - Compiled application +# /app/frontend/node_modules/ - Minimal runtime dependencies +# /app/frontend/public/ - Static assets +# ----------------------------------------------------------------------------- +COPY --from=frontend-builder /app/public /app/frontend/public +COPY --from=frontend-builder /app/.next/standalone /app/frontend +COPY --from=frontend-builder /app/.next/static /app/frontend/.next/static + +# ----------------------------------------------------------------------------- +# Create nginx Configuration +# ----------------------------------------------------------------------------- +# Configure nginx as reverse proxy: +# - Listen on port 7860 (HuggingFace Spaces requirement) +# - Proxy /api/* requests to backend on port 8000 +# - Proxy all other requests to frontend on port 3000 +# - Enable gzip compression for text content +# - Configure appropriate timeouts for SSE streaming +# ----------------------------------------------------------------------------- +RUN cat > /etc/nginx/nginx.conf << 'EOF' +# ============================================================================= +# nginx Configuration for RAG Chatbot +# ============================================================================= +# This configuration sets up nginx as a reverse proxy for the combined +# frontend (Next.js) and backend (FastAPI) application. +# ============================================================================= + +# Run nginx master process as root (required for port binding) +# Worker processes run as www-data for security +user www-data; + +# Auto-detect number of CPU cores for worker processes +worker_processes auto; + +# Error log location and level +error_log /var/log/nginx/error.log warn; + +# PID file location +pid /var/run/nginx.pid; + +# ----------------------------------------------------------------------------- +# Events Configuration +# ----------------------------------------------------------------------------- +events { + # Maximum simultaneous connections per worker + worker_connections 1024; + + # Use epoll for better performance on Linux + use epoll; + + # Accept multiple connections at once + multi_accept on; +} + +# ----------------------------------------------------------------------------- +# HTTP Configuration +# ----------------------------------------------------------------------------- +http { + # Include MIME types for proper content-type headers + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # Performance optimizations + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + # Keep-alive settings + keepalive_timeout 65; + + # Gzip compression for text content + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript + application/xml application/xml+rss text/javascript application/x-javascript; + + # Upstream definitions for backend and frontend + upstream backend { + server 127.0.0.1:8000; + keepalive 32; + } + + upstream frontend { + server 127.0.0.1:3000; + keepalive 32; + } + + # ------------------------------------------------------------------------- + # Main Server Block + # ------------------------------------------------------------------------- + server { + # Listen on port 7860 (HuggingFace Spaces requirement) + listen 7860; + server_name _; + + # Client body size limit (for potential file uploads) + client_max_body_size 10M; + + # --------------------------------------------------------------------- + # Backend API Routes + # --------------------------------------------------------------------- + # Proxy all /api/* requests to the FastAPI backend + # This includes /api/query, /api/health, etc. + # --------------------------------------------------------------------- + location /api/ { + proxy_pass http://backend/; + proxy_http_version 1.1; + + # Headers for proper proxying + 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; + + # Connection upgrade for WebSocket (if needed in future) + proxy_set_header Connection ""; + + # Timeouts for long-running requests (SSE streaming) + # These are generous to support streaming LLM responses + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + + # Disable buffering for SSE (Server-Sent Events) + # This ensures streaming responses are sent immediately + proxy_buffering off; + proxy_cache off; + + # SSE-specific headers + proxy_set_header Accept-Encoding ""; + } + + # --------------------------------------------------------------------- + # Health Check Endpoint (direct to backend) + # --------------------------------------------------------------------- + # Allow direct access to health endpoints for container orchestration + # --------------------------------------------------------------------- + location /health { + proxy_pass http://backend/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Connection ""; + } + + # --------------------------------------------------------------------- + # Frontend Routes (catch-all) + # --------------------------------------------------------------------- + # Proxy all other requests to the Next.js frontend + # This includes pages, static assets, and _next/* resources + # --------------------------------------------------------------------- + location / { + proxy_pass http://frontend; + proxy_http_version 1.1; + + # Headers for proper proxying + 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; + proxy_set_header Connection ""; + + # Standard timeouts for frontend + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # --------------------------------------------------------------------- + # Static Files Caching + # --------------------------------------------------------------------- + # Cache static assets with long expiry for performance + # Next.js includes content hashes in filenames, so this is safe + # --------------------------------------------------------------------- + location /_next/static { + proxy_pass http://frontend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Connection ""; + + # Cache static assets for 1 year (immutable content-hashed files) + expires 1y; + add_header Cache-Control "public, immutable"; + } + } +} +EOF + +# ----------------------------------------------------------------------------- +# Create Supervisor Configuration +# ----------------------------------------------------------------------------- +# Supervisor manages multiple processes in a single container: +# - nginx: Reverse proxy (master process) +# - frontend: Next.js server (node process) +# - backend: FastAPI/uvicorn (python process) +# +# All processes are started together and supervisor monitors their health. +# If any process crashes, supervisor will restart it automatically. +# ----------------------------------------------------------------------------- +RUN cat > /etc/supervisor/conf.d/supervisord.conf << 'EOF' +; ============================================================================= +; Supervisor Configuration for RAG Chatbot +; ============================================================================= +; Manages nginx, frontend (Next.js), and backend (FastAPI) processes. +; All processes run in the foreground (nodaemon=true). +; ============================================================================= + +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid +childlogdir=/var/log/supervisor + +; ----------------------------------------------------------------------------- +; nginx - Reverse Proxy +; ----------------------------------------------------------------------------- +; nginx master process must run as root for port binding. +; Worker processes run as www-data (configured in nginx.conf). +; ----------------------------------------------------------------------------- +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=true +priority=10 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ----------------------------------------------------------------------------- +; Frontend - Next.js Server +; ----------------------------------------------------------------------------- +; Runs the Next.js standalone server on port 3000. +; The standalone server is minimal and includes only required dependencies. +; ----------------------------------------------------------------------------- +[program:frontend] +command=node /app/frontend/server.js +directory=/app/frontend +autostart=true +autorestart=true +priority=20 +user=appuser +environment=NODE_ENV="production",PORT="3000",HOSTNAME="0.0.0.0" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ----------------------------------------------------------------------------- +; Backend - FastAPI/Uvicorn Server +; ----------------------------------------------------------------------------- +; Runs the FastAPI application on port 8000. +; Uses the app factory pattern for proper initialization. +; +; LLM Provider API Keys (inherited from container environment): +; - GEMINI_API_KEY: Google Gemini API (primary provider) +; - DEEPSEEK_API_KEY: DeepSeek API (fallback provider) +; - ANTHROPIC_API_KEY: Anthropic API (fallback provider) +; - GROQ_API_KEY: Groq API (optional provider) +; - HF_TOKEN: HuggingFace token for dataset access and logging +; +; Note: The environment= directive ADDS to the inherited environment. +; API keys set via "docker run -e" or HuggingFace Spaces secrets are +; automatically passed through to this process without explicit listing. +; ----------------------------------------------------------------------------- +[program:backend] +command=python -m uvicorn rag_chatbot.api.main:create_app --factory --host 0.0.0.0 --port 8000 +directory=/app/backend +autostart=true +autorestart=true +priority=30 +user=appuser +environment=PYTHONPATH="/app/backend/src",PYTHONDONTWRITEBYTECODE="1",PYTHONUNBUFFERED="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +EOF + +# ----------------------------------------------------------------------------- +# Create Log Directories +# ----------------------------------------------------------------------------- +# Create directories for nginx and supervisor logs with proper permissions. +# ----------------------------------------------------------------------------- +RUN mkdir -p /var/log/nginx /var/log/supervisor \ + && chown -R www-data:www-data /var/log/nginx \ + && chmod -R 755 /var/log/supervisor + +# ----------------------------------------------------------------------------- +# Set Permissions +# ----------------------------------------------------------------------------- +# Ensure appuser can read application files and write to necessary locations. +# nginx runs as www-data, frontend/backend run as appuser. +# ----------------------------------------------------------------------------- +RUN chown -R appuser:appgroup /app/frontend /app/backend + +# ----------------------------------------------------------------------------- +# Expose Port +# ----------------------------------------------------------------------------- +# HuggingFace Spaces expects the application to listen on port 7860. +# All traffic flows through nginx on this port. +# ----------------------------------------------------------------------------- +EXPOSE 7860 + +# ----------------------------------------------------------------------------- +# Health Check +# ----------------------------------------------------------------------------- +# Docker health check for container orchestration. +# Checks the /health endpoint via nginx, which proxies to the backend. +# +# Options: +# --interval=30s: Check every 30 seconds +# --timeout=10s: Wait up to 10 seconds for response +# --start-period=60s: Grace period for all services to start +# --retries=3: Mark unhealthy after 3 consecutive failures +# +# Note: Using /health/ready because it returns 200 when ready, 503 when loading. +# The start-period is longer (60s) because we need nginx + frontend + backend +# to all be ready before the health check can pass. +# ----------------------------------------------------------------------------- +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl --fail http://localhost:7860/health/ready || exit 1 + +# ----------------------------------------------------------------------------- +# Entrypoint +# ----------------------------------------------------------------------------- +# Start supervisor, which manages all three services: +# 1. nginx (reverse proxy on port 7860) +# 2. frontend (Next.js on port 3000) +# 3. backend (FastAPI on port 8000) +# +# Supervisor runs in the foreground (nodaemon=true) as the main process. +# It monitors all child processes and restarts them if they crash. +# +# Environment variables (GEMINI_API_KEY, HF_TOKEN, etc.) should be passed +# at runtime via docker run -e or configured in HuggingFace Spaces secrets. +# Supervisor passes them through to the backend process. +# ----------------------------------------------------------------------------- +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 0000000000000000000000000000000000000000..2f45004ca2c290c399ad162934332c4658bcf425 --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,225 @@ +# ============================================================================= +# RAG Chatbot Backend - Production Dockerfile +# ============================================================================= +# This is a multi-stage Dockerfile optimized for HuggingFace Spaces deployment. +# It creates a minimal runtime image that excludes heavy build-time dependencies +# like torch, sentence-transformers, and pyarrow. +# +# Architecture: +# Stage 1 (builder): Installs Poetry and exports serve-only dependencies +# Stage 2 (runtime): Minimal image with only runtime dependencies +# +# Target image size: < 2GB +# Base image: python:3.11-slim (Debian-based, ~150MB) +# ============================================================================= + +# ============================================================================= +# STAGE 1: BUILDER +# ============================================================================= +# Purpose: Use Poetry to export a clean requirements.txt for serve dependencies +# This stage is discarded after build, so Poetry overhead doesn't affect runtime +# ============================================================================= +FROM python:3.11-slim AS builder + +# ----------------------------------------------------------------------------- +# Install Poetry +# ----------------------------------------------------------------------------- +# Poetry is used to manage dependencies and export requirements.txt +# We pin the version for reproducible builds +# Using pipx isolation avoids polluting the system Python +# ----------------------------------------------------------------------------- +ENV POETRY_VERSION=1.8.2 +ENV POETRY_HOME=/opt/poetry +ENV PATH="${POETRY_HOME}/bin:${PATH}" + +# Install Poetry using the official installer script +# This method is recommended over pip install for isolation +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && curl -sSL https://install.python-poetry.org | python3 - \ + && apt-get purge -y curl \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /build + +# ----------------------------------------------------------------------------- +# Copy Dependency Files +# ----------------------------------------------------------------------------- +# Copy only the files needed for dependency resolution +# This layer is cached until pyproject.toml or poetry.lock changes +# ----------------------------------------------------------------------------- +COPY pyproject.toml poetry.lock ./ + +# ----------------------------------------------------------------------------- +# Export Requirements +# ----------------------------------------------------------------------------- +# Export only the serve group dependencies to requirements.txt +# Flags explained: +# --only main,serve : Include core deps + serve group (FastAPI, uvicorn, SSE) +# --without-hashes : Omit hashes for compatibility with pip install +# -f requirements.txt: Output to requirements.txt file +# +# IMPORTANT: This excludes: +# - dense group (torch, sentence-transformers) - not needed at runtime +# - build group (pymupdf4llm, pyarrow, etc.) - offline pipeline only +# - dev group (mypy, ruff, pytest) - development tools only +# ----------------------------------------------------------------------------- +RUN poetry export --only main,serve --without-hashes -f requirements.txt -o requirements.txt + +# ============================================================================= +# STAGE 2: RUNTIME +# ============================================================================= +# Purpose: Minimal production image with only serve dependencies +# This is the final image that runs on HuggingFace Spaces +# ============================================================================= +FROM python:3.11-slim AS runtime + +# ----------------------------------------------------------------------------- +# Environment Variables +# ----------------------------------------------------------------------------- +# PYTHONDONTWRITEBYTECODE: Prevents Python from writing .pyc bytecode files +# - Reduces image size slightly +# - Avoids permission issues with read-only filesystems +# +# PYTHONUNBUFFERED: Forces stdout/stderr to be unbuffered +# - Ensures log messages appear immediately in container logs +# - Critical for debugging and monitoring in production +# +# PYTHONPATH: Adds src/ to Python's module search path +# - Allows importing rag_chatbot package without installation +# - Matches the src layout convention used in the project +# ----------------------------------------------------------------------------- +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app/src + +# ----------------------------------------------------------------------------- +# Create Non-Root User +# ----------------------------------------------------------------------------- +# Security best practice: Run the application as a non-root user +# This limits the impact of potential security vulnerabilities +# +# - Create group 'appgroup' with GID 1000 +# - Create user 'appuser' with UID 1000 in that group +# - No home directory needed (-M), no login shell (-s /bin/false) +# ----------------------------------------------------------------------------- +RUN groupadd --gid 1000 appgroup \ + && useradd --uid 1000 --gid appgroup --shell /bin/false --no-create-home appuser + +# ----------------------------------------------------------------------------- +# Install System Dependencies +# ----------------------------------------------------------------------------- +# Install curl for health checks and clean up apt cache to reduce image size +# The health check endpoint will be probed using curl +# ----------------------------------------------------------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Copy Requirements from Builder Stage +# ----------------------------------------------------------------------------- +# The requirements.txt was generated by Poetry in the builder stage +# It contains only the serve dependencies (no torch, no build deps) +# ----------------------------------------------------------------------------- +COPY --from=builder /build/requirements.txt . + +# ----------------------------------------------------------------------------- +# Install Python Dependencies +# ----------------------------------------------------------------------------- +# Install all serve dependencies using pip +# Flags explained: +# --no-cache-dir : Don't cache pip packages (reduces image size) +# --no-compile : Don't compile .py to .pyc (PYTHONDONTWRITEBYTECODE=1 handles this) +# -r requirements.txt : Install from the exported requirements +# +# This installs: +# - Core deps: pydantic, numpy, httpx, tiktoken, rank-bm25, faiss-cpu, etc. +# - Serve deps: fastapi, uvicorn, sse-starlette +# +# This does NOT install: +# - torch, sentence-transformers (dense group) +# - pymupdf4llm, pyarrow, datasets (build group) +# - mypy, ruff, pytest (dev group) +# ----------------------------------------------------------------------------- +RUN pip install --no-cache-dir --no-compile -r requirements.txt + +# ----------------------------------------------------------------------------- +# Copy Application Source Code +# ----------------------------------------------------------------------------- +# Copy the source code from the host to the container +# The src/rag_chatbot/ directory contains the application package +# PYTHONPATH=/app/src allows Python to find the rag_chatbot module +# ----------------------------------------------------------------------------- +COPY src/ /app/src/ + +# ----------------------------------------------------------------------------- +# Set Ownership +# ----------------------------------------------------------------------------- +# Change ownership of the application directory to the non-root user +# This ensures the application can read its own files when running as appuser +# ----------------------------------------------------------------------------- +RUN chown -R appuser:appgroup /app + +# ----------------------------------------------------------------------------- +# Switch to Non-Root User +# ----------------------------------------------------------------------------- +# From this point on, all commands run as appuser (UID 1000) +# This is the user that will run the application in production +# ----------------------------------------------------------------------------- +USER appuser + +# ----------------------------------------------------------------------------- +# Expose Port +# ----------------------------------------------------------------------------- +# HuggingFace Spaces expects the application to listen on port 7860 +# This documents the port but doesn't actually publish it (done at runtime) +# ----------------------------------------------------------------------------- +EXPOSE 7860 + +# ----------------------------------------------------------------------------- +# Health Check +# ----------------------------------------------------------------------------- +# Docker health check configuration for container orchestration +# This allows Docker/HF Spaces to know if the application is healthy +# +# --interval=30s : Check every 30 seconds +# --timeout=10s : Wait up to 10 seconds for response +# --start-period=40s : Grace period for startup (resources load lazily on first request) +# --retries=3 : Mark unhealthy after 3 consecutive failures +# +# Health endpoint paths: +# /health/ready - Simple readiness probe (200 OK when ready, 503 when loading) +# /health/health - Full health status with version and component details +# +# Note: Using /health/ready because it returns 200/503 which curl --fail can detect. +# The endpoint returns 503 while resources are loading, which is expected during +# the start period. Once resources load (on first request), it returns 200. +# ----------------------------------------------------------------------------- +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD curl --fail http://localhost:7860/health/ready || exit 1 + +# ----------------------------------------------------------------------------- +# Entrypoint +# ----------------------------------------------------------------------------- +# Start the FastAPI application using uvicorn ASGI server +# Command breakdown: +# uvicorn : ASGI server for async Python web apps +# rag_chatbot.api.main:create_app : Module path to the app factory function +# --factory : create_app is a factory function, not an app instance +# --host 0.0.0.0 : Listen on all network interfaces (required for containers) +# --port 7860 : HuggingFace Spaces default port +# +# Using CMD allows the command to be overridden for debugging if needed +# Using exec form (JSON array) ensures proper signal handling +# ----------------------------------------------------------------------------- +CMD ["uvicorn", "rag_chatbot.api.main:create_app", "--factory", "--host", "0.0.0.0", "--port", "7860"] diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 0000000000000000000000000000000000000000..d367aed09f20aa886759e1b15a3caa969a85e00c --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,353 @@ +# ============================================================================= +# RAG Chatbot Frontend - Production Dockerfile +# ============================================================================= +# This is a multi-stage Dockerfile optimized for production deployment of the +# Next.js frontend application on HuggingFace Spaces or any Docker-based hosting. +# +# Architecture (3 stages): +# Stage 1 (deps): Install all dependencies using npm ci +# Stage 2 (builder): Build the Next.js application with standalone output +# Stage 3 (runner): Minimal production image with only runtime files +# +# Key optimizations: +# - Alpine-based images for minimal size +# - Multi-stage build eliminates build-time dependencies from final image +# - Standalone output mode bundles only required node_modules +# - Gzip compression enabled via Node.js flags +# - Non-root user for security +# +# Target image size: < 500MB +# Base image: node:22-alpine (~50MB base) +# +# Build command: +# docker build -f Dockerfile.frontend -t frontend ./frontend +# +# Run command: +# docker run -p 3000:3000 frontend +# +# ============================================================================= + + +# ============================================================================= +# STAGE 1: DEPENDENCIES +# ============================================================================= +# Purpose: Install all npm dependencies in an isolated stage +# This stage installs both production and dev dependencies because we need +# devDependencies (TypeScript, Tailwind, etc.) to build the application. +# The final image will not include these - only the standalone output. +# ============================================================================= +FROM node:22-alpine AS deps + +# ----------------------------------------------------------------------------- +# Install System Dependencies +# ----------------------------------------------------------------------------- +# libc6-compat: Required for some native Node.js modules that expect glibc +# Alpine uses musl libc by default, and this compatibility layer helps with +# packages that have native bindings compiled against glibc. +# ----------------------------------------------------------------------------- +RUN apk add --no-cache libc6-compat + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +# Use /app as the standard working directory for the application +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Copy Package Files +# ----------------------------------------------------------------------------- +# Copy only package.json and package-lock.json first for better layer caching. +# Docker caches this layer until these files change, avoiding unnecessary +# reinstalls when only source code changes. +# ----------------------------------------------------------------------------- +COPY package.json package-lock.json ./ + +# ----------------------------------------------------------------------------- +# Install Dependencies +# ----------------------------------------------------------------------------- +# npm ci (Clean Install) is used instead of npm install because: +# - It's faster: skips package resolution, uses exact versions from lock file +# - It's reproducible: guarantees exact same dependency tree +# - It's stricter: fails if lock file is out of sync with package.json +# - It clears node_modules before install: ensures clean state +# +# --prefer-offline: Use cached packages when available (faster in CI/CD) +# +# After install, clean npm cache to reduce layer size. +# ----------------------------------------------------------------------------- +RUN npm ci --prefer-offline && npm cache clean --force + + +# ============================================================================= +# STAGE 2: BUILDER +# ============================================================================= +# Purpose: Build the Next.js application for production +# This stage: +# 1. Copies dependencies from the deps stage +# 2. Copies source code +# 3. Runs the production build +# 4. Outputs standalone server + static assets +# +# The standalone output (enabled in next.config.ts) creates: +# - .next/standalone/ - Minimal Node.js server with bundled dependencies +# - .next/static/ - Static assets (JS, CSS chunks) +# - public/ - Public static files (images, fonts, etc.) +# ============================================================================= +FROM node:22-alpine AS builder + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Copy Dependencies from deps Stage +# ----------------------------------------------------------------------------- +# Copy the fully installed node_modules from the deps stage. +# This is faster than reinstalling and ensures consistent dependencies. +# ----------------------------------------------------------------------------- +COPY --from=deps /app/node_modules ./node_modules + +# ----------------------------------------------------------------------------- +# Copy Source Code and Configuration +# ----------------------------------------------------------------------------- +# Copy all source files needed for the build. +# Note: Files matching patterns in .dockerignore are excluded automatically. +# +# The key files needed: +# - src/ : Application source code (pages, components, etc.) +# - public/ : Static assets to be copied to output +# - package.json : Project metadata and scripts +# - next.config.ts : Next.js configuration (standalone output enabled) +# - tsconfig.json : TypeScript configuration +# - postcss.config.mjs, tailwind config : CSS processing +# ----------------------------------------------------------------------------- +COPY . . + +# ----------------------------------------------------------------------------- +# Build-Time Environment Variables +# ----------------------------------------------------------------------------- +# NEXT_PUBLIC_API_URL: The base URL for API requests +# - Set at build time because NEXT_PUBLIC_* vars are inlined into the bundle +# - Empty string = same-origin requests (frontend and backend on same domain) +# - Set to absolute URL if backend is on different domain +# +# NEXT_TELEMETRY_DISABLED: Disable Next.js anonymous telemetry +# - Prevents outbound network requests during build +# - Recommended for CI/CD and production environments +# +# NODE_ENV: Set to production for optimized build output +# - Enables production optimizations (minification, dead code elimination) +# - Disables development-only features and warnings +# ----------------------------------------------------------------------------- +ENV NEXT_PUBLIC_API_URL="" +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +# ----------------------------------------------------------------------------- +# Build the Application +# ----------------------------------------------------------------------------- +# Run the Next.js production build using webpack bundler. +# +# Note: Next.js 16+ uses Turbopack by default, but we use --webpack flag here +# because the next.config.ts contains a webpack configuration block. +# Using webpack ensures compatibility with existing configuration. +# +# This command will: +# 1. Compile TypeScript to JavaScript +# 2. Bundle and optimize client-side code using webpack +# 3. Pre-render static pages (if any) +# 4. Generate standalone output in .next/standalone/ +# 5. Generate static assets in .next/static/ +# +# The standalone output includes: +# - server.js: Minimal Node.js server +# - node_modules: Only production dependencies needed by the server +# - Required Next.js internal files +# ----------------------------------------------------------------------------- +RUN npx next build --webpack + + +# ============================================================================= +# STAGE 3: RUNNER (Production) +# ============================================================================= +# Purpose: Minimal production image containing only what's needed to run +# This is the final image that will be deployed. +# +# Size optimization: +# - Alpine base image (~50MB vs ~350MB for Debian) +# - Only standalone output copied (no full node_modules) +# - No build tools, devDependencies, or source TypeScript +# +# Security: +# - Runs as non-root user (nextjs) +# - Minimal attack surface +# - No unnecessary packages or tools +# ============================================================================= +FROM node:22-alpine AS runner + +# ----------------------------------------------------------------------------- +# Set Working Directory +# ----------------------------------------------------------------------------- +WORKDIR /app + +# ----------------------------------------------------------------------------- +# Environment Variables for Production +# ----------------------------------------------------------------------------- +# NODE_ENV: production +# - Next.js uses this to enable production optimizations +# - Disables development-only features +# +# NEXT_TELEMETRY_DISABLED: Disable telemetry at runtime +# - No anonymous usage data sent to Vercel +# +# PORT: The port the server will listen on +# - Default 3000, can be overridden at runtime +# - HuggingFace Spaces may require specific ports (e.g., 7860) +# +# HOSTNAME: The hostname to bind to +# - 0.0.0.0 binds to all network interfaces +# - Required for Docker networking (localhost wouldn't be accessible) +# +# NODE_OPTIONS: Node.js runtime flags +# - --enable-source-maps: Better error stack traces in production +# - Note: Gzip compression is handled by Next.js automatically +# (via the compress option, which defaults to true) +# ----------------------------------------------------------------------------- +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 +ENV NODE_OPTIONS="--enable-source-maps" + +# ----------------------------------------------------------------------------- +# Create Non-Root User +# ----------------------------------------------------------------------------- +# Security best practice: Run applications as non-root user. +# This limits the potential damage from security vulnerabilities. +# +# addgroup: Create a system group 'nodejs' with GID 1001 +# adduser: Create a system user 'nextjs' with UID 1001 +# - -S: Create a system user (no password, no home dir contents) +# - -G: Add to the nodejs group +# - -u: Set specific UID for consistency across environments +# +# Using UID/GID 1001 to avoid conflicts with existing system users. +# ----------------------------------------------------------------------------- +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 --ingroup nodejs nextjs + +# ----------------------------------------------------------------------------- +# Copy Public Directory +# ----------------------------------------------------------------------------- +# Copy static files from the public directory. +# These files are served directly by Next.js without processing. +# Common contents: favicon.ico, robots.txt, static images, fonts +# +# --chown: Set ownership to nextjs user for proper permissions +# ----------------------------------------------------------------------------- +COPY --from=builder --chown=nextjs:nodejs /app/public ./public + +# ----------------------------------------------------------------------------- +# Create .next Directory +# ----------------------------------------------------------------------------- +# Create the .next directory with proper ownership. +# Next.js writes cache and runtime files here. +# Pre-creating with correct ownership prevents permission errors. +# ----------------------------------------------------------------------------- +RUN mkdir -p .next && chown nextjs:nodejs .next + +# ----------------------------------------------------------------------------- +# Copy Standalone Server +# ----------------------------------------------------------------------------- +# Copy the standalone output from the builder stage. +# This is the minimal Node.js server with bundled dependencies. +# +# Structure of .next/standalone/: +# - server.js : The entry point for the production server +# - node_modules/ : Only the dependencies required by server.js +# - .next/ : Compiled server components and routes +# - package.json : Minimal package info +# +# The standalone output is significantly smaller than full node_modules +# because it only includes the specific files needed for production. +# ----------------------------------------------------------------------------- +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ + +# ----------------------------------------------------------------------------- +# Copy Static Assets +# ----------------------------------------------------------------------------- +# Copy the static assets directory to the standalone output. +# These are the compiled JavaScript/CSS chunks and other static files. +# +# IMPORTANT: The standalone output does NOT automatically include .next/static +# because these files should typically be served by a CDN. However, for +# HuggingFace Spaces and simple deployments, we serve them from the same server. +# +# The static directory must be placed inside .next/ for Next.js to find it. +# ----------------------------------------------------------------------------- +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# ----------------------------------------------------------------------------- +# Switch to Non-Root User +# ----------------------------------------------------------------------------- +# All subsequent commands and the container runtime will use this user. +# This is the final security measure before running the application. +# ----------------------------------------------------------------------------- +USER nextjs + +# ----------------------------------------------------------------------------- +# Expose Port +# ----------------------------------------------------------------------------- +# Document the port that the application listens on. +# This is informational; actual port mapping is done at runtime with -p flag. +# The PORT environment variable (default 3000) controls the actual binding. +# ----------------------------------------------------------------------------- +EXPOSE 3000 + +# ----------------------------------------------------------------------------- +# Health Check +# ----------------------------------------------------------------------------- +# Docker health check to verify the container is running properly. +# Used by orchestrators (Docker Compose, Kubernetes, HF Spaces) for: +# - Container lifecycle management +# - Load balancer health checks +# - Automatic container restarts on failure +# +# Options: +# --interval=30s : Check every 30 seconds +# --timeout=10s : Wait up to 10 seconds for response +# --start-period=30s: Grace period for application startup +# --retries=3 : Mark unhealthy after 3 consecutive failures +# +# Command: +# wget: Use wget instead of curl (curl not installed in alpine by default) +# --no-verbose: Reduce output noise +# --tries=1: Single attempt per check +# --spider: Don't download, just check availability +# http://localhost:3000/: The root page (or use a dedicated health endpoint) +# ----------------------------------------------------------------------------- +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1 + +# ----------------------------------------------------------------------------- +# Start Command +# ----------------------------------------------------------------------------- +# Start the Next.js production server using the standalone server.js file. +# +# The standalone server.js is a minimal Node.js server that: +# - Handles all Next.js routing (pages, API routes, etc.) +# - Serves static files from .next/static and public +# - Includes production optimizations (compression, caching headers) +# - Uses the HOSTNAME and PORT environment variables +# +# Using exec form (JSON array) ensures: +# - Proper signal handling (SIGTERM reaches Node.js directly) +# - No shell process overhead +# - Correct argument parsing +# +# Note: The server automatically enables gzip compression via Next.js +# (compress: true is the default in production mode). +# ----------------------------------------------------------------------------- +CMD ["node", "server.js"] diff --git a/PROJECT_README.md b/PROJECT_README.md new file mode 100644 index 0000000000000000000000000000000000000000..2e8dfbdc504cdf8a03aea5a77120c2f040ba50f9 --- /dev/null +++ b/PROJECT_README.md @@ -0,0 +1,250 @@ +# RAG Chatbot for pythermalcomfort + +> **Note**: For HuggingFace Space configuration and user documentation, see [README.md](README.md). + +![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue) +![License: MIT](https://img.shields.io/badge/License-MIT-green.svg) + +A Retrieval-Augmented Generation (RAG) chatbot for the pythermalcomfort library documentation. The chatbot uses hybrid retrieval (dense embeddings + BM25) to find relevant documentation chunks and streams responses via LLM providers. + +## Features + +- Hybrid retrieval combining dense embeddings (BGE) with BM25 keyword search +- Multi-provider LLM support with automatic fallback (Gemini -> Groq -> DeepSeek) +- Server-Sent Events (SSE) streaming for real-time responses +- Structure-aware PDF chunking with heading inheritance +- Async query logging to HuggingFace datasets + +## Installation + +### Prerequisites + +- Python 3.11 or higher +- [Poetry](https://python-poetry.org/docs/#installation) for dependency management + +### Environment Setup + +1. Clone the repository: + ```bash + git clone https://github.com/sadickam/pythermalcomfort-chat.git + cd pythermalcomfort-chat + ``` + +2. Create and activate a virtual environment: + ```bash + python -m venv therm_venv + source therm_venv/bin/activate # Linux/macOS + # or + therm_venv\Scripts\activate # Windows + ``` + +3. Install dependencies using one of the install modes below. + +### Install Modes + +The project uses Poetry dependency groups to support different deployment scenarios: + +```bash +# Server only (BM25 retrieval) - lightweight, no GPU required +poetry install --with serve + +# Server with dense retrieval - requires torch, optional GPU +poetry install --with serve,dense + +# Full build pipeline (local) - for rebuilding embeddings and indexes +poetry install --with build,dev +``` + +### Dependency Comparison + +| Dependency | Core | Serve | Dense | Build | +|------------------------|:----:|:-----:|:-----:|:-----:| +| pydantic | x | | | | +| pydantic-settings | x | | | | +| numpy | x | | | | +| httpx | x | | | | +| tiktoken | x | | | | +| rank-bm25 | x | | | | +| faiss-cpu | x | | | | +| fastapi | | x | | | +| uvicorn | | x | | | +| sse-starlette | | x | | | +| sentence-transformers | | | x | x | +| torch | | | x | x | +| pymupdf4llm | | | | x | +| pymupdf | | | | x | +| pyarrow | | | | x | +| datasets | | | | x | + +**Install mode explanations:** + +- **Core**: Always installed. Provides base functionality for retrieval and LLM communication. +- **Serve** (`--with serve`): Adds FastAPI server for hosting the chatbot API. Suitable for CPU-only deployments using precomputed embeddings. +- **Dense** (`--with dense`): Adds sentence-transformers and PyTorch for runtime embedding generation. Use when you need to embed queries with dense vectors. +- **Build** (`--with build`): Full offline pipeline for processing PDFs, generating embeddings, and publishing indexes to HuggingFace. + +## Quick Start + +1. Set up environment variables: + ```bash + cp .env.example .env + # Edit .env with your API keys + ``` + +2. Start the server: + ```bash + poetry run uvicorn src.rag_chatbot.api.main:app --reload + ``` + +3. The API will be available at `http://localhost:8000` + +## Project Structure + +``` +src/rag_chatbot/ +├── api/ # FastAPI endpoints and middleware +├── chunking/ # Structure-aware document chunking +├── config/ # Settings and configuration +├── embeddings/ # BGE encoder and storage +├── extraction/ # PDF to Markdown conversion +├── llm/ # LLM provider registry (Gemini, Groq, DeepSeek) +├── qlog/ # Async query logging to HuggingFace +└── retrieval/ # Hybrid retriever (FAISS + BM25 with RRF fusion) +``` + +## Build Pipeline + +For rebuilding embeddings and indexes from source PDFs: + +```bash +# Full rebuild: extract -> chunk -> embed -> index -> publish +poetry run python scripts/rebuild.py data/raw/ + +# Individual steps +poetry run python scripts/extract.py data/raw/ data/processed/ +poetry run python scripts/chunk.py data/processed/ data/chunks/chunks.jsonl +poetry run python scripts/embed.py data/chunks/chunks.jsonl data/embeddings/ --publish +``` + +## Development + +### Running Tests + +```bash +# Run all tests +poetry run pytest + +# Run unit tests only +poetry run pytest tests/unit/ + +# Run a single test file +poetry run pytest tests/unit/test_foo.py + +# Run a single test by name +poetry run pytest -k "test_name" + +# Generate coverage report +poetry run pytest --cov=src --cov-report=html +``` + +### Code Quality + +```bash +# Type checking (strict mode) +poetry run mypy src/ + +# Linting +poetry run ruff check src/ + +# Formatting +poetry run ruff format src/ +``` + +### Pre-commit Hooks + +Install pre-commit hooks to run checks automatically before each commit: + +```bash +poetry run pre-commit install +``` + +## Environment Variables + +Required secrets for deployment: + +| Variable | Description | +|--------------------|--------------------------------| +| `GEMINI_API_KEY` | Google Gemini API key | +| `DEEPSEEK_API_KEY` | DeepSeek API key | +| `GROQ_API_KEY` | Groq API key | +| `HF_TOKEN` | HuggingFace authentication | + +Configuration options: + +| Variable | Default | Description | +|-----------------------|---------|--------------------------------------| +| `USE_HYBRID` | `true` | Enable BM25 + dense retrieval | +| `USE_RERANKER` | `false` | Enable cross-encoder reranking | +| `TOP_K` | `6` | Number of chunks to retrieve | +| `PROVIDER_TIMEOUT_MS` | `30000` | Timeout before provider fallback | + +## Retrieval Configuration + +The retrieval system can be tuned via environment variables to balance latency and answer quality. + +### Available Settings + +| Setting | Default | Range | Description | +|---------|---------|-------|-------------| +| `TOP_K` | `6` | 1-20 | Number of chunks to retrieve. Higher values provide more context but increase latency. | +| `USE_HYBRID` | `true` | boolean | Enable hybrid retrieval combining dense (FAISS) and sparse (BM25) search with RRF fusion. | +| `USE_RERANKER` | `false` | boolean | Enable cross-encoder reranking for improved relevance. Adds ~200-500ms latency. | + +### Retriever Modes + +**Hybrid Mode (default):** Combines dense semantic search (FAISS with BGE embeddings) and sparse keyword search (BM25) using Reciprocal Rank Fusion. Best for mixed queries containing both technical terms and natural language. + +**Dense-Only Mode:** Uses only FAISS semantic search. Faster than hybrid mode and works well for purely semantic queries where exact keyword matching is less important. + +### Performance Tradeoffs + +| Setting | Default | Latency Impact | Quality Impact | +|---------|---------|----------------|----------------| +| `TOP_K` | 6 | +~5ms per chunk | More context = better answers | +| `USE_HYBRID` | true | +~20-50ms | Better for mixed queries | +| `USE_RERANKER` | false | +~200-500ms | Significantly improved relevance | + +### Recommended Configurations + +**Low-latency production (default):** +```bash +USE_HYBRID=true +USE_RERANKER=false +TOP_K=6 +``` + +**High-precision answers:** +```bash +USE_HYBRID=true +USE_RERANKER=true +TOP_K=10 +``` + +**Fastest responses:** +```bash +USE_HYBRID=false +USE_RERANKER=false +TOP_K=3 +``` + +## HuggingFace Repositories + +| Repository | Purpose | +|----------------------------------|--------------------------------------| +| `sadickam/pytherm_index` | Chunks, embeddings, FAISS/BM25 indexes | +| `sadickam/Pytherm_Qlog` | Query/answer logs (no PII) | +| `sadickam/Pythermalcomfort-Chat` | Deployment Space (Docker SDK) | + +## License + +MIT License - see [LICENSE](LICENSE) for details. diff --git a/README.md b/README.md index d393f566810c3804fe86856b38626e58adcc9153..083c4c813dade91fcbb9ee45ee1363c7f3c2ccdd 100644 --- a/README.md +++ b/README.md @@ -1,198 +1,249 @@ -# RAG Chatbot for pythermalcomfort +--- +title: Pythermalcomfort Chat +emoji: 🌖 +colorFrom: yellow +colorTo: red +sdk: docker +pinned: false +license: mit +--- -![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue) -![License: MIT](https://img.shields.io/badge/License-MIT-green.svg) + +# 🌡️ Pythermalcomfort RAG Chatbot -A Retrieval-Augmented Generation (RAG) chatbot for the pythermalcomfort library documentation. The chatbot uses hybrid retrieval (dense embeddings + BM25) to find relevant documentation chunks and streams responses via LLM providers. +Welcome to the **Pythermalcomfort Chat** — an AI-powered assistant designed to help you understand and use the [pythermalcomfort](https://github.com/CenterForTheBuiltEnvironment/pythermalcomfort) Python library for thermal comfort calculations. -## Features +## What is This? -- Hybrid retrieval combining dense embeddings (BGE) with BM25 keyword search -- Multi-provider LLM support with automatic fallback (Gemini -> Groq -> DeepSeek) -- Server-Sent Events (SSE) streaming for real-time responses -- Structure-aware PDF chunking with heading inheritance -- Async query logging to HuggingFace datasets +This chatbot uses **Retrieval-Augmented Generation (RAG)** to provide accurate, documentation-grounded answers about thermal comfort science and the pythermalcomfort library. Instead of relying solely on general AI knowledge, every response is backed by relevant excerpts from the official documentation, ensuring you get precise and reliable information. -## Installation +**Why use this chatbot?** +- 📚 Get instant answers about thermal comfort standards (ASHRAE 55, ISO 7730, EN 16798) +- 🔧 Learn how to use pythermalcomfort functions with practical examples +- 🎯 Understand complex concepts like PMV/PPD, adaptive comfort, and more +- 📖 Receive responses with source citations so you can dive deeper -### Prerequisites +--- -- Python 3.11 or higher -- [Poetry](https://python-poetry.org/docs/#installation) for dependency management + +## 💬 What You Can Ask -### Environment Setup +Here are some example questions to get you started: -1. Clone the repository: - ```bash - git clone https://github.com/sadickam/pythermalcomfort-chat.git - cd pythermalcomfort-chat - ``` +| Category | Example Questions | +|----------|-------------------| +| **Concepts** | "What is PMV and how is it calculated?" | +| | "What is the difference between PMV and PPD?" | +| | "How does adaptive thermal comfort work?" | +| **Standards** | "What are the ASHRAE Standard 55 requirements?" | +| | "What thermal comfort categories does ISO 7730 define?" | +| | "What is the EN 16798 standard?" | +| **Library Usage** | "What inputs does the pmv_ppd function need?" | +| | "How do I calculate thermal comfort for an office?" | +| | "How do I use the adaptive comfort model?" | +| **Parameters** | "What is metabolic rate and how do I estimate it?" | +| | "How do I measure clothing insulation?" | +| | "What is operative temperature?" | -2. Create and activate a virtual environment: - ```bash - python -m venv therm_venv - source therm_venv/bin/activate # Linux/macOS - # or - therm_venv\Scripts\activate # Windows - ``` +--- -3. Install dependencies using one of the install modes below. + +## ⚙️ Technical Features -### Install Modes +| Feature | Description | +|---------|-------------| +| **RAG-Powered Responses** | Every answer includes source citations from pythermalcomfort documentation | +| **Hybrid Retrieval** | Combines dense embeddings (FAISS) + sparse retrieval (BM25) with Reciprocal Rank Fusion for accurate document search | +| **Multi-Provider LLM** | Automatic fallback chain: Gemini → DeepSeek → Anthropic → Groq ensures high availability | +| **Real-Time Streaming** | Responses stream via Server-Sent Events (SSE) for a responsive chat experience | +| **Query Logging** | Anonymous query logging enables continuous improvement of retrieval quality | -The project uses Poetry dependency groups to support different deployment scenarios: +--- -```bash -# Server only (BM25 retrieval) - lightweight, no GPU required -poetry install --with serve +## 🤖 Available LLM Models -# Server with dense retrieval - requires torch, optional GPU -poetry install --with serve,dense + -# Full build pipeline (local) - for rebuilding embeddings and indexes -poetry install --with build,dev -``` +The chatbot leverages multiple LLM providers with intelligent fallback to ensure high availability: + +### Google Gemini (Primary Provider) + +| Model | Rate Limits (Free Tier) | Description | +|-------|-------------------------|-------------| +| `gemini-2.5-flash-lite` | 10 RPM, 250K TPM | Primary model - fastest response times | +| `gemini-2.5-flash` | 5 RPM, 250K TPM | Secondary - balanced speed and quality | +| `gemini-3-flash` | 5 RPM, 250K TPM | Tertiary - latest Gemini capabilities | +| `gemma-3-27b-it` | 30 RPM, 15K TPM | Final fallback - open-weights model | + +### Groq (High-Speed Provider) + +| Model | Rate Limits (Free Tier) | Description | +|-------|-------------------------|-------------| +| `openai/gpt-oss-120b` | 30 RPM, 8K TPM | Primary - large model via Groq | +| `llama-3.3-70b-versatile` | 30 RPM, 12K TPM | Secondary - Meta's Llama 3.3 | + +### DeepSeek (Fallback Provider) -### Dependency Comparison - -| Dependency | Core | Serve | Dense | Build | -|------------------------|:----:|:-----:|:-----:|:-----:| -| pydantic | x | | | | -| pydantic-settings | x | | | | -| numpy | x | | | | -| httpx | x | | | | -| tiktoken | x | | | | -| rank-bm25 | x | | | | -| faiss-cpu | x | | | | -| fastapi | | x | | | -| uvicorn | | x | | | -| sse-starlette | | x | | | -| sentence-transformers | | | x | x | -| torch | | | x | x | -| pymupdf4llm | | | | x | -| pymupdf | | | | x | -| pyarrow | | | | x | -| datasets | | | | x | - -**Install mode explanations:** - -- **Core**: Always installed. Provides base functionality for retrieval and LLM communication. -- **Serve** (`--with serve`): Adds FastAPI server for hosting the chatbot API. Suitable for CPU-only deployments using precomputed embeddings. -- **Dense** (`--with dense`): Adds sentence-transformers and PyTorch for runtime embedding generation. Use when you need to embed queries with dense vectors. -- **Build** (`--with build`): Full offline pipeline for processing PDFs, generating embeddings, and publishing indexes to HuggingFace. - -## Quick Start - -1. Set up environment variables: - ```bash - cp .env.example .env - # Edit .env with your API keys - ``` - -2. Start the server: - ```bash - poetry run uvicorn src.rag_chatbot.api.main:app --reload - ``` - -3. The API will be available at `http://localhost:8000` - -## Project Structure +| Model | Description | +|-------|-------------| +| `deepseek-chat` | Cost-effective alternative with strong reasoning | + +### Provider Fallback Chain ``` -src/rag_chatbot/ -├── api/ # FastAPI endpoints and middleware -├── chunking/ # Structure-aware document chunking -├── config/ # Settings and configuration -├── embeddings/ # BGE encoder and storage -├── extraction/ # PDF to Markdown conversion -├── llm/ # LLM provider registry (Gemini, Groq, DeepSeek) -├── qlog/ # Async query logging to HuggingFace -└── retrieval/ # Hybrid retriever (FAISS + BM25 with RRF fusion) +Gemini → Groq → DeepSeek → Anthropic + ↓ ↓ ↓ ↓ +Primary Fast Budget Premium ``` -## Build Pipeline +When a provider hits rate limits or encounters errors: +1. The system automatically tries the next provider in the chain +2. Rate limit cooldowns are tracked per-model +3. Responses indicate which provider was used -For rebuilding embeddings and indexes from source PDFs: +> **Note**: The fallback chain ensures maximum availability while staying within free tier limits. -```bash -# Full rebuild: extract -> chunk -> embed -> index -> publish -poetry run python scripts/rebuild.py data/raw/ +--- -# Individual steps -poetry run python scripts/extract.py data/raw/ data/processed/ -poetry run python scripts/chunk.py data/processed/ data/chunks/chunks.jsonl -poetry run python scripts/embed.py data/chunks/chunks.jsonl data/embeddings/ --publish -``` + +## 📖 About pythermalcomfort -## Development +**Thermal comfort** refers to the condition of mind that expresses satisfaction with the thermal environment. It's influenced by factors like air temperature, humidity, air velocity, radiant temperature, clothing, and metabolic rate. -### Running Tests +The **pythermalcomfort** library is an open-source Python package that implements thermal comfort models and indices according to international standards, including: -```bash -# Run all tests -poetry run pytest +- 🏛️ **ASHRAE Standard 55** — Thermal Environmental Conditions for Human Occupancy +- 🌍 **ISO 7730** — Ergonomics of the thermal environment +- 🇪🇺 **EN 16798** — Energy performance of buildings + +### Key Capabilities + +- Calculate **PMV (Predicted Mean Vote)** and **PPD (Predicted Percentage Dissatisfied)** +- Evaluate **adaptive thermal comfort** for naturally ventilated buildings +- Compute various thermal comfort indices (SET, UTCI, Cooling Effect, etc.) +- Support for both SI and IP unit systems + +### Resources + +- 📦 **GitHub Repository**: [CenterForTheBuiltEnvironment/pythermalcomfort](https://github.com/CenterForTheBuiltEnvironment/pythermalcomfort) +- 📚 **Documentation**: [pythermalcomfort.readthedocs.io](https://pythermalcomfort.readthedocs.io/) +- 🐍 **PyPI**: [pythermalcomfort on PyPI](https://pypi.org/project/pythermalcomfort/) -# Run unit tests only -poetry run pytest tests/unit/ +--- -# Run a single test file -poetry run pytest tests/unit/test_foo.py + +## 🔌 API Endpoints -# Run a single test by name -poetry run pytest -k "test_name" +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/query` | POST | Submit a question and receive a streamed response | +| `/api/providers` | GET | Check availability status of LLM providers | +| `/health` | GET | Basic health check | +| `/health/ready` | GET | Readiness probe (checks if indexes are loaded) | -# Generate coverage report -poetry run pytest --cov=src --cov-report=html +### Query Request Format + +```json +{ + "question": "What is PMV?", + "provider": "gemini" +} ``` -### Code Quality +The `provider` field is optional. If omitted, the system automatically selects the best available provider. -```bash -# Type checking (strict mode) -poetry run mypy src/ +--- -# Linting -poetry run ruff check src/ + +## 🏗️ Architecture -# Formatting -poetry run ruff format src/ +This Space uses a multi-container architecture optimized for HuggingFace Spaces: + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Nginx │────▶│ Next.js │ │ FastAPI │ +│ (Proxy) │────▶│ Frontend │────▶│ Backend │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ + ▼ + ┌─────────────────┐ + │ FAISS + BM25 │ + │ Indexes │ + └─────────────────┘ ``` -### Pre-commit Hooks +- **Nginx** — Reverse proxy handling routing between frontend and backend +- **Next.js** — React frontend with responsive chat interface +- **FastAPI** — Python backend with RAG pipeline and LLM orchestration + +The backend downloads prebuilt FAISS indexes and BM25 vocabularies from the [`sadickam/pytherm_index`](https://huggingface.co/datasets/sadickam/pytherm_index) HuggingFace dataset at startup, enabling efficient hybrid retrieval without requiring GPU compute. + +--- + + +## 👩‍💻 For Developers + +Interested in running this locally or contributing? See the **[PROJECT_README.md](PROJECT_README.md)** for detailed development setup instructions, including: -Install pre-commit hooks to run checks automatically before each commit: +- Local development without Docker +- Building the retrieval indexes from scratch +- Running tests and type checking +- Contributing guidelines + +### Quick Start (Development) ```bash -poetry run pre-commit install +# Backend +poetry install --with serve,dense +poetry run uvicorn src.rag_chatbot.api.main:app --reload --port 7860 + +# Frontend (in separate terminal) +cd frontend +npm install +npm run dev ``` -## Environment Variables +--- + + +## 🔐 Environment Variables (Deployment) + +The following secrets must be configured in HuggingFace Space settings for deployment: + +| Variable | Required | Description | +|----------|----------|-------------| +| `GEMINI_API_KEY` | Yes | Google Gemini API key (primary provider) | +| `DEEPSEEK_API_KEY` | No | DeepSeek API key (fallback provider) | +| `ANTHROPIC_API_KEY` | No | Anthropic API key (fallback provider) | +| `GROQ_API_KEY` | No | Groq API key (fallback provider) | +| `HF_TOKEN` | Yes | HuggingFace token for accessing datasets and query logging | + +**Note:** At least one LLM provider key is required for the chatbot to function. -Required secrets for deployment: +--- -| Variable | Description | -|--------------------|--------------------------------| -| `GEMINI_API_KEY` | Google Gemini API key | -| `DEEPSEEK_API_KEY` | DeepSeek API key | -| `GROQ_API_KEY` | Groq API key | -| `HF_TOKEN` | HuggingFace authentication | + +## 🔗 Related Repositories -Configuration options: +| Repository | Description | +|------------|-------------| +| [pythermalcomfort](https://github.com/CenterForTheBuiltEnvironment/pythermalcomfort) | The Python library this chatbot documents | +| [sadickam/pytherm_index](https://huggingface.co/datasets/sadickam/pytherm_index) | Prebuilt retrieval indexes (FAISS + BM25) | +| [sadickam/Pytherm_Qlog](https://huggingface.co/datasets/sadickam/Pytherm_Qlog) | Anonymous query logs for improvement | -| Variable | Default | Description | -|-----------------------|---------|--------------------------------------| -| `USE_HYBRID` | `true` | Enable BM25 + dense retrieval | -| `TOP_K` | `6` | Number of chunks to retrieve | -| `PROVIDER_TIMEOUT_MS` | `30000` | Timeout before provider fallback | +--- -## HuggingFace Repositories + +## 📄 License -| Repository | Purpose | -|----------------------------------|--------------------------------------| -| `sadickam/pytherm_index` | Chunks, embeddings, FAISS/BM25 indexes | -| `sadickam/Pytherm_Qlog` | Query/answer logs (no PII) | -| `sadickam/Pythermalcomfort-Chat` | Deployment Space (Docker SDK) | +This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details. -## License +--- -MIT License - see [LICENSE](LICENSE) for details. +

+ Made with ❤️ for the thermal comfort research community +

diff --git a/commands b/commands index c31bc5717ee68e3a8eac2cb08c553f172467df9f..a917c0f1b1036afb6f9e8da012da081347bfb386 100644 --- a/commands +++ b/commands @@ -41,6 +41,10 @@ Verbose mode - Basic rebuild with detailed output showing each file being proces clear all artifacts, generate new artifacts and publish to HF +## Commit to HF Space +- git push space master --force ( first time) +- git push space master (subsequent commits) + ## CREATE AASBS2 and SDG Targets Database with Embeddings @@ -85,9 +89,8 @@ After your solution, rate your confidence (0-1) on: If any score < 0.9, refine your answer. [TASK] - Read the **## Overview** sections of **IMPLEMENTATION_PLAN.md** to understand the system, and context. -- Review **Step 4.7: Implement Full Rebuild Script** step by step to understand all of its requirements and objectives. -- Spawn specilaised agents and orchestrate them to succesfully complete all tasks for **Step 4.7: Implement Full Rebuild Script** based on defined details in the **@IMPLEMENTATION_PLAN.md** -- Ensure normalisation includes correcting jumbled words and sentences, removing extra spaces, and ensuring proper capitalization. +- Review **Step 9.5: Add Dataset Freshness Check on Startup** step by step to understand all of its requirements and objectives. +- Spawn specilaised agents and orchestrate them to succesfully complete all tasks for **Step 9.5: Add Dataset Freshness Check on Startup** based on defined details in the **@IMPLEMENTATION_PLAN.md** - Spawn at most 2 specialised agents at a time - Ensure clean hands off between agents to avoid file write conflicts occurring between agents and potential data loss - It is critical to identify and fix potentially missing items that can affect production-readiness. diff --git a/docs/HF_SPACE_CONFIG.md b/docs/HF_SPACE_CONFIG.md new file mode 100644 index 0000000000000000000000000000000000000000..d0ea0055e1491a71dd2be62988c4725e0ccf79bf --- /dev/null +++ b/docs/HF_SPACE_CONFIG.md @@ -0,0 +1,427 @@ +# HuggingFace Space Configuration Guide + + + +## Overview + +This guide explains how to configure the HuggingFace Space at **[sadickam/Pythermalcomfort-Chat](https://huggingface.co/spaces/sadickam/Pythermalcomfort-Chat)** for deployment. + +The Space uses the **Docker SDK**, which means: +- HuggingFace builds a Docker image from the repository's `Dockerfile` +- The container runs on HuggingFace's infrastructure +- Environment variables and secrets are injected at runtime +- The application serves both the Next.js frontend and FastAPI backend + +--- + +## README File Structure + + + +The HuggingFace Space requires a `README.md` file with specific YAML frontmatter for configuration. This repository is structured so that `README.md` serves as the Space configuration file directly, eliminating the need for file renaming during deployment. + +### File Structure + +| File | Purpose | +|------|---------| +| `README.md` | HuggingFace Space configuration with YAML frontmatter, user-facing documentation | +| `PROJECT_README.md` | Developer documentation, installation instructions, contribution guidelines | + +### YAML Frontmatter Requirements + +The `README.md` starts with this required frontmatter: + +```yaml +--- +title: Pythermalcomfort Chat +emoji: 🌖 +colorFrom: yellow +colorTo: red +sdk: docker +pinned: false +license: mit +--- +``` + +This tells HuggingFace how to build and display the Space. + +--- + +## Required Secrets Configuration + + + +### Secret Reference Table + +| Secret Name | Required | Description | +|-------------|----------|-------------| +| `GEMINI_API_KEY` | **Required** | Google Gemini API key - Primary LLM provider for generating responses | +| `HF_TOKEN` | **Required** | HuggingFace token for accessing the index dataset and query logging | +| `DEEPSEEK_API_KEY` | Optional | DeepSeek API key - First fallback provider if Gemini fails | +| `ANTHROPIC_API_KEY` | Optional | Anthropic Claude API key - Second fallback provider | +| `GROQ_API_KEY` | Optional | Groq API key - Third fallback provider for fast inference | + +### Step-by-Step Configuration + +1. **Navigate to the Space Settings** + ``` + https://huggingface.co/spaces/sadickam/Pythermalcomfort-Chat/settings + ``` + +2. **Scroll to the "Repository secrets" section** + - This section is located under "Variables and secrets" in the Settings page + +3. **Add each secret** + - Click "New secret" + - Enter the secret name exactly as shown (e.g., `GEMINI_API_KEY`) + - Paste the API key value + - Click "Add" + - Repeat for each secret + +4. **Required secrets setup** + + **GEMINI_API_KEY** (Required): + ``` + # Obtain from: https://makersuite.google.com/app/apikey + # This is the primary LLM provider - the chatbot will not function without it + ``` + + **HF_TOKEN** (Required): + ``` + # Obtain from: https://huggingface.co/settings/tokens + # Required permissions: + # - Read access to sadickam/pytherm_index (prebuilt indexes) + # - Write access to sadickam/Pytherm_Qlog (query logging) + ``` + +5. **Optional fallback provider secrets** + + + + **DEEPSEEK_API_KEY** (Optional): + ``` + # Obtain from: https://platform.deepseek.com/ + # First fallback if Gemini is unavailable + ``` + + **ANTHROPIC_API_KEY** (Optional): + ``` + # Obtain from: https://console.anthropic.com/ + # Second fallback provider + ``` + + **GROQ_API_KEY** (Optional): + ``` + # Obtain from: https://console.groq.com/ + # Third fallback - offers fast inference times + ``` + +6. **Restart the Space** + - After adding all secrets, click "Restart" in the Space header + - The Space will rebuild and inject the new secrets + +--- + +## Hardware Settings + + + +### Recommended Configuration + +| Setting | Value | Reason | +|---------|-------|--------| +| **Hardware** | CPU Basic (Free) | No GPU required for inference | +| **Sleep timeout** | Default (48 hours) | Adjust based on usage patterns | + +### Why CPU is Sufficient + +1. **Prebuilt Indexes**: The FAISS and BM25 indexes are built offline with GPU acceleration and published to `sadickam/pytherm_index`. At startup, the Space downloads these prebuilt artifacts. + +2. **No Local Embedding**: Query embedding uses the same BGE model but runs efficiently on CPU for single queries. + +3. **External LLM Providers**: Response generation is handled by external API providers (Gemini, DeepSeek, etc.), not local models. + +4. **Cost Optimization**: The free CPU tier is sufficient for the expected load and keeps operational costs at zero. + +### Configuring Hardware + +1. Navigate to: `https://huggingface.co/spaces/sadickam/Pythermalcomfort-Chat/settings` +2. Find the "Space hardware" section +3. Select "CPU basic" from the dropdown +4. Click "Save" (the Space will restart automatically) + +--- + +## Deployment Verification Checklist + + + +### Pre-flight Checks + +- [ ] All required secrets are configured (`GEMINI_API_KEY`, `HF_TOKEN`) +- [ ] Hardware is set to "CPU Basic" +- [ ] Space is set to "Public" or "Private" as needed + +### Build Verification + +- [ ] **Space builds successfully** + - Check the "Logs" tab for build output + - Build should complete without errors in 5-10 minutes + - Look for "Application startup complete" in logs + +### Health Endpoint Checks + + + +1. **Basic Health Check** + ```bash + curl https://sadickam-pythermalcomfort-chat.hf.space/health + ``` + Expected response: + ```json + {"status": "healthy"} + ``` + - [ ] Returns HTTP 200 + +2. **Readiness Check** + ```bash + curl https://sadickam-pythermalcomfort-chat.hf.space/health/ready + ``` + Expected response: + ```json + { + "status": "ready", + "indexes_loaded": true, + "chunks_count": , + "faiss_index_size": + } + ``` + - [ ] Returns HTTP 200 + - [ ] `indexes_loaded` is `true` + - [ ] `chunks_count` is greater than 0 + +3. **Provider Availability** + ```bash + curl https://sadickam-pythermalcomfort-chat.hf.space/api/providers + ``` + Expected response: + ```json + { + "available": ["gemini", ...], + "primary": "gemini" + } + ``` + - [ ] Returns HTTP 200 + - [ ] At least one provider in `available` list + - [ ] `primary` is set (typically "gemini") + +### Functional Test + +- [ ] **Test a sample query through the UI** + 1. Open `https://sadickam-pythermalcomfort-chat.hf.space` + 2. Wait for the interface to load + 3. Enter a test question: "What is PMV?" + 4. Verify: + - Response streams in real-time + - Response includes relevant information about PMV (Predicted Mean Vote) + - Source citations are included + +--- + +## Troubleshooting + + + +### Space Stuck in "Building" + +**Symptoms:** +- Build process runs for more than 15 minutes +- Build log shows no progress or loops + +**Solutions:** +1. **Check Dockerfile syntax** + ```bash + # Validate locally before pushing + docker build -t test-build . + ``` + +2. **Review build logs** + - Click "Logs" tab in the Space + - Look for error messages or failed commands + - Common issues: missing files, dependency conflicts + +3. **Clear build cache** + - Go to Settings > "Factory reboot" + - This clears cached layers and rebuilds from scratch + +### Health Check Failing + +**Symptoms:** +- `/health` returns 500 or connection refused +- Space shows as "Running" but endpoints don't respond + +**Solutions:** +1. **Verify secrets are configured** + - Go to Settings > "Repository secrets" + - Confirm `GEMINI_API_KEY` and `HF_TOKEN` are present + - Note: You cannot see secret values, only that they exist + +2. **Check application logs** + ``` + # Look for startup errors in the Logs tab + # Common messages: + # - "Missing required environment variable" + # - "Failed to initialize provider" + ``` + +3. **Restart the Space** + - Click the three-dot menu > "Restart" + - Wait 2-3 minutes for full startup + +### No Providers Available + +**Symptoms:** +- `/api/providers` returns `{"available": [], "primary": null}` +- Chat interface shows "No providers available" error + +**Solutions:** +1. **Verify API keys are correct** + - Regenerate the API key from the provider's console + - Update the secret in HuggingFace Space settings + - Restart the Space + +2. **Check provider status** + - Verify the provider's API is operational + - Check for rate limiting or account issues + +3. **Review provider logs** + ``` + # Look for these patterns in logs: + # - "API key invalid" + # - "Rate limit exceeded" + # - "Provider initialization failed" + ``` + +### Index Loading Failures + +**Symptoms:** +- `/health/ready` returns `{"indexes_loaded": false}` +- Logs show "Failed to download artifacts" + +**Solutions:** +1. **Verify HF_TOKEN permissions** + - Go to https://huggingface.co/settings/tokens + - Ensure the token has "Read" access to `sadickam/pytherm_index` + - If using a fine-grained token, add explicit repo access + +2. **Check dataset availability** + - Visit https://huggingface.co/datasets/sadickam/pytherm_index + - Verify the dataset exists and is accessible + - Check if the dataset is private and token has access + +3. **Manual verification** + ```bash + # Test token access locally + curl -H "Authorization: Bearer $HF_TOKEN" \ + https://huggingface.co/api/datasets/sadickam/pytherm_index + ``` + +4. **Check disk space** + - The index files require ~500MB of storage + - HuggingFace Spaces have limited ephemeral storage + - Consider reducing index size if this is an issue + +### Slow Response Times + +**Symptoms:** +- Queries take more than 30 seconds +- Responses time out frequently + +**Solutions:** +1. **Check provider latency** + - The primary provider (Gemini) may be experiencing high load + - Fallback providers will be tried automatically + +2. **Verify hybrid retrieval settings** + ``` + # In environment or settings: + USE_HYBRID=true # Enable both FAISS and BM25 + TOP_K=6 # Reduce if responses are slow + ``` + +3. **Monitor Space resources** + - Check the "Metrics" tab for CPU/memory usage + - Consider upgrading hardware if consistently maxed out + +--- + +## Environment Variables Reference + + + +### Secrets (Configure in Space Settings) + +| Variable | Required | Description | +|----------|----------|-------------| +| `GEMINI_API_KEY` | Yes | Google Gemini API key | +| `HF_TOKEN` | Yes | HuggingFace access token | +| `DEEPSEEK_API_KEY` | No | DeepSeek API key | +| `ANTHROPIC_API_KEY` | No | Anthropic API key | +| `GROQ_API_KEY` | No | Groq API key | + +### Configuration (Can be set in Dockerfile) + +| Variable | Default | Description | +|----------|---------|-------------| +| `USE_HYBRID` | `true` | Enable hybrid retrieval (FAISS + BM25) | +| `TOP_K` | `6` | Number of chunks to retrieve | +| `PROVIDER_TIMEOUT_MS` | `30000` | Timeout before trying fallback provider | +| `LOG_LEVEL` | `INFO` | Application log level | + +--- + +## Additional Resources + +- [HuggingFace Spaces Documentation](https://huggingface.co/docs/hub/spaces) +- [Docker SDK Reference](https://huggingface.co/docs/hub/spaces-sdks-docker) +- [Space Secrets Documentation](https://huggingface.co/docs/hub/spaces-overview#managing-secrets) +- [pythermalcomfort Library](https://pythermalcomfort.readthedocs.io/) diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..f80eaa8e3510297d8873564f517803a62f668705 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,221 @@ +# ============================================================================= +# Docker Ignore File for Next.js Frontend +# ============================================================================= +# This file specifies patterns for files and directories that should be +# excluded from the Docker build context. Proper exclusions: +# - Reduce build context size (faster docker build) +# - Prevent unnecessary cache invalidation +# - Avoid including sensitive or development-only files +# - Keep the final image smaller and more secure +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Dependencies +# ----------------------------------------------------------------------------- +# Exclude node_modules as dependencies are installed fresh during build. +# This ensures consistent, reproducible builds and prevents platform-specific +# native modules from causing issues (e.g., macOS binaries on Linux). +node_modules/ +.pnp/ +.pnp.js + +# ----------------------------------------------------------------------------- +# Build Outputs +# ----------------------------------------------------------------------------- +# Exclude local build artifacts. The Docker build process will generate +# fresh build outputs. Including these would: +# - Bloat the build context unnecessarily +# - Potentially cause cache conflicts +# - Include development build artifacts in production +.next/ +out/ +build/ +dist/ + +# ----------------------------------------------------------------------------- +# Version Control +# ----------------------------------------------------------------------------- +# Git history and metadata are not needed in the container. +# Excluding .git significantly reduces build context size. +.git/ +.gitignore +.gitattributes + +# ----------------------------------------------------------------------------- +# Test Files and Coverage +# ----------------------------------------------------------------------------- +# Testing infrastructure is not needed in production images. +# Exclude test files, coverage reports, and testing configurations. +coverage/ +.nyc_output/ +*.test.ts +*.test.tsx +*.test.js +*.test.jsx +*.spec.ts +*.spec.tsx +*.spec.js +*.spec.jsx +__tests__/ +__mocks__/ +jest.config.* +vitest.config.* +playwright.config.* +cypress/ +cypress.config.* + +# ----------------------------------------------------------------------------- +# Documentation +# ----------------------------------------------------------------------------- +# Documentation files are not needed for runtime. +# Keep the image focused on application code only. +*.md +!README.md +docs/ +CHANGELOG* +LICENSE* +CONTRIBUTING* + +# ----------------------------------------------------------------------------- +# IDE and Editor Configurations +# ----------------------------------------------------------------------------- +# Editor-specific files and directories should not be in the image. +# These are developer-specific and vary between team members. +.idea/ +.vscode/ +*.swp +*.swo +*~ +.project +.classpath +.settings/ +*.sublime-* + +# ----------------------------------------------------------------------------- +# OS-Generated Files +# ----------------------------------------------------------------------------- +# Operating system metadata files are never needed in containers. +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +desktop.ini + +# ----------------------------------------------------------------------------- +# Debug and Log Files +# ----------------------------------------------------------------------------- +# Debug logs from package managers and build tools. +# These can contain sensitive information and are not needed in production. +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +.pnpm-debug.log* + +# ----------------------------------------------------------------------------- +# Environment Files +# ----------------------------------------------------------------------------- +# Local environment files often contain secrets. +# Production secrets should be injected via Docker secrets or env vars. +# IMPORTANT: Never include .env files with real credentials in images. +.env +.env.local +.env.development +.env.development.local +.env.test +.env.test.local +.env.production.local + +# Note: .env.production is intentionally NOT excluded as it may contain +# non-sensitive build-time configuration. Review before building. + +# ----------------------------------------------------------------------------- +# TypeScript Build Info +# ----------------------------------------------------------------------------- +# TypeScript incremental compilation cache. +# Not needed in the container; TypeScript compiles fresh during build. +*.tsbuildinfo +tsconfig.tsbuildinfo + +# ----------------------------------------------------------------------------- +# Package Manager Lock Files (Alternative) +# ----------------------------------------------------------------------------- +# If using npm, exclude yarn/pnpm lock files and vice versa. +# Uncomment the appropriate lines based on your package manager. +# yarn.lock +# pnpm-lock.yaml +# package-lock.json + +# ----------------------------------------------------------------------------- +# Storybook +# ----------------------------------------------------------------------------- +# Storybook is a development tool for UI component documentation. +# Not needed in production runtime. +.storybook/ +storybook-static/ + +# ----------------------------------------------------------------------------- +# Docker Files +# ----------------------------------------------------------------------------- +# Dockerfiles themselves don't need to be in the build context +# (Docker reads them separately). Including them can leak build strategy info. +Dockerfile* +docker-compose* +.dockerignore + +# ----------------------------------------------------------------------------- +# CI/CD Configuration +# ----------------------------------------------------------------------------- +# Continuous integration configs are not needed in the runtime image. +.github/ +.gitlab-ci.yml +.travis.yml +.circleci/ +azure-pipelines.yml +Jenkinsfile +.buildkite/ + +# ----------------------------------------------------------------------------- +# Linting and Formatting Configs +# ----------------------------------------------------------------------------- +# These are development tools; linting happens before build, not at runtime. +.eslintrc* +.eslintignore +eslint.config.* +.prettierrc* +.prettierignore +prettier.config.* +.stylelintrc* +.editorconfig + +# ----------------------------------------------------------------------------- +# Husky and Git Hooks +# ----------------------------------------------------------------------------- +# Git hooks are for development workflow, not needed in containers. +.husky/ +.git-hooks/ + +# ----------------------------------------------------------------------------- +# Temporary Files +# ----------------------------------------------------------------------------- +# Temporary files from various tools and processes. +tmp/ +temp/ +*.tmp +*.temp +*.bak +*.backup + +# ----------------------------------------------------------------------------- +# Miscellaneous +# ----------------------------------------------------------------------------- +# Other files that don't belong in production images. +Makefile +*.log +*.pid +*.seed +*.pid.lock diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5ef6a520780202a1d6addd833d800ccb1ecac0bb --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,41 @@ +# 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/frontend/.gitkeep b/frontend/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..29e2bcd32c8243051609b3231fa9d24ae0e35578 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,21 @@ +# Dependencies +node_modules + +# Build output +.next +out + +# Coverage +coverage + +# Cache +.cache + +# Lock files +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Generated files +*.min.js +*.min.css diff --git a/frontend/README.md b/frontend/README.md index 38e65147ff9b7e3ddacff533a8da5ff6c6716fa8..e215bc4ccf138bbc38ad58ad57e92135484b3c0f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1 +1,36 @@ -Next.js frontend - to be implemented in Phase 8 +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..99e076eac5325138d9656538611b826806e78f50 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,120 @@ +/** + * ESLint Configuration for Next.js Frontend + * + * This configuration enforces strict TypeScript and React rules for + * production-quality code in the RAG Chatbot frontend. + * + * @see https://eslint.org/docs/latest/use/configure/configuration-files + */ + +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextVitals from 'eslint-config-next/core-web-vitals'; +import nextTs from 'eslint-config-next/typescript'; +import eslintConfigPrettier from 'eslint-config-prettier'; + +const eslintConfig = defineConfig([ + // Extend Next.js recommended configs + ...nextVitals, + ...nextTs, + + // Prettier config to disable formatting rules that conflict + eslintConfigPrettier, + + // Global configuration with strict rules for source files + { + files: ['src/**/*.{ts,tsx}'], + rules: { + // ============================================= + // TypeScript Strict Rules + // ============================================= + + // Enforce explicit return types on functions + '@typescript-eslint/explicit-function-return-type': [ + 'warn', + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + }, + ], + + // Require explicit accessibility modifiers + '@typescript-eslint/explicit-member-accessibility': [ + 'error', + { + accessibility: 'explicit', + overrides: { + constructors: 'no-public', + }, + }, + ], + + // Disallow 'any' type usage + '@typescript-eslint/no-explicit-any': 'error', + + // Require consistent type assertions + '@typescript-eslint/consistent-type-assertions': [ + 'error', + { + assertionStyle: 'as', + objectLiteralTypeAssertions: 'never', + }, + ], + + // No unused variables (with underscore exception) + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + + // ============================================= + // React & Next.js Rules + // ============================================= + + // Enforce hooks rules strictly + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + + // Prevent missing key props in iterators + 'react/jsx-key': ['error', { checkFragmentShorthand: true }], + + // No array index as key + 'react/no-array-index-key': 'warn', + + // ============================================= + // General Best Practices + // ============================================= + + // No console statements in production + 'no-console': ['warn', { allow: ['warn', 'error'] }], + + // Enforce strict equality + eqeqeq: ['error', 'always'], + + // No debugger statements + 'no-debugger': 'error', + + // Prefer const over let when possible + 'prefer-const': 'error', + + // No var declarations + 'no-var': 'error', + }, + }, + + // Override default ignores of eslint-config-next + globalIgnores([ + // Default ignores of eslint-config-next: + '.next/**', + 'out/**', + 'build/**', + 'next-env.d.ts', + // Additional ignores + 'node_modules/**', + 'coverage/**', + ]), +]); + +export default eslintConfig; diff --git a/frontend/next.config.ts b/frontend/next.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..27846f455b0254279cfb7bc8074655ebecb15847 --- /dev/null +++ b/frontend/next.config.ts @@ -0,0 +1,263 @@ +import type { NextConfig } from "next"; + +/** + * Next.js Configuration for Production Docker Deployment + * + * This configuration is optimized for containerized deployments on platforms + * like HuggingFace Spaces, Vercel, or any Docker-based hosting. + * + * Key features: + * - Standalone output for minimal Docker image size + * - Security hardening (no x-powered-by header) + * - Optimized image handling with modern formats + * - Environment variable support for dynamic API configuration + */ +const nextConfig: NextConfig = { + /** + * Output Mode: Standalone + * + * Generates a standalone folder with only the necessary files for production. + * This dramatically reduces Docker image size by: + * - Including only required node_modules (not devDependencies) + * - Creating a minimal server.js that doesn't require the full Next.js installation + * - Copying only the files needed for production + * + * The standalone output is located at `.next/standalone/` after build. + * To run: `node .next/standalone/server.js` + * + * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/output + */ + output: "standalone", + + /** + * React Strict Mode + * + * Enables React's Strict Mode for the entire application. + * Benefits: + * - Identifies components with unsafe lifecycles + * - Warns about deprecated API usage + * - Detects unexpected side effects by double-invoking certain functions + * - Ensures reusable state (important for React 18+ concurrent features) + * + * Note: This only affects development mode; production builds are not impacted. + * + * @see https://react.dev/reference/react/StrictMode + */ + reactStrictMode: true, + + /** + * Security: Disable x-powered-by Header + * + * Removes the "X-Powered-By: Next.js" HTTP response header. + * Security benefits: + * - Reduces information disclosure about the tech stack + * - Makes it slightly harder for attackers to target Next.js-specific vulnerabilities + * - Follows security best practice of minimizing fingerprinting + * + * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/poweredByHeader + */ + poweredByHeader: false, + + /** + * Image Optimization Configuration + * + * Configures Next.js Image component optimization settings. + * This affects how images are processed, cached, and served. + */ + images: { + /** + * Image Formats + * + * Specifies the output formats for optimized images, in order of preference. + * - AVIF: Best compression, ~50% smaller than JPEG, but slower to encode + * - WebP: Good compression, ~30% smaller than JPEG, widely supported + * + * The browser's Accept header determines which format is served. + * Modern browsers get AVIF/WebP; older browsers fall back to original format. + * + * Note: AVIF encoding is CPU-intensive; consider removing if build times are critical. + */ + formats: ["image/avif", "image/webp"], + + /** + * Remote Patterns + * + * Defines allowed external image sources for security. + * Only images from these patterns can be optimized by Next.js. + * Add patterns here if you need to serve images from external CDNs or APIs. + * + * Example: + * remotePatterns: [ + * { protocol: 'https', hostname: 'cdn.example.com', pathname: '/images/**' } + * ] + */ + remotePatterns: [], + + /** + * Unoptimized Mode + * + * When true, disables image optimization entirely. + * Set to true if: + * - Deploying to a platform without image optimization support + * - Using an external image CDN (Cloudinary, imgix, etc.) + * - Debugging image-related issues + * + * Default: false (optimization enabled) + */ + unoptimized: false, + }, + + /** + * Environment Variables Configuration + * + * Exposes environment variables to the browser (client-side). + * Variables listed here are inlined at build time. + * + * IMPORTANT: Only expose non-sensitive values here. + * Never expose API keys, secrets, or credentials. + * + * For runtime configuration (not inlined at build), use: + * - NEXT_PUBLIC_* prefix for client-side runtime vars + * - Server-side API routes for sensitive operations + */ + env: { + /** + * API Base URL + * + * The base URL for the backend API server. + * - Development: Usually http://localhost:8000 + * - Production: The deployed backend URL or relative path + * + * Falls back to empty string for same-origin API calls (relative URLs). + * This allows the frontend to work with both: + * - Separate backend deployment (absolute URL) + * - Same-origin deployment (relative URL like /api) + */ + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || "", + }, + + /** + * Experimental Features + * + * Enable experimental Next.js features. + * Use with caution in production as these may change between versions. + */ + experimental: { + /** + * Optimize Package Imports + * + * Enables automatic tree-shaking for specific packages. + * Reduces bundle size by only importing used exports. + * Particularly useful for large UI libraries. + * + * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/optimizePackageImports + */ + optimizePackageImports: ["lucide-react"], + }, + + /** + * Webpack Configuration Override + * + * Custom webpack configuration for advanced build customization. + * Use sparingly as it can complicate upgrades and debugging. + * + * @param config - The existing webpack configuration + * @param context - Build context including isServer, dev, etc. + * @returns Modified webpack configuration + */ + webpack: (config, { isServer }) => { + /** + * Suppress Critical Dependency Warnings + * + * Some packages (especially those using dynamic requires) trigger + * webpack warnings that are not actionable. This filter suppresses + * known false-positive warnings to keep build output clean. + */ + if (!isServer) { + // Client-side specific webpack modifications can go here + // Example: config.resolve.fallback = { fs: false, path: false }; + } + + return config; + }, + + /** + * HTTP Headers Configuration + * + * Custom HTTP headers for all routes. + * Used for security headers, caching policies, and CORS. + * + * @returns Array of header configurations + */ + async headers() { + return [ + { + // Apply to all routes + source: "/:path*", + headers: [ + /** + * Content Security Policy + * + * Restricts resource loading to prevent XSS attacks. + * Customize based on your application's needs. + * Note: This is a basic policy; adjust for production. + */ + // Uncomment and customize for production: + // { + // key: 'Content-Security-Policy', + // value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';", + // }, + + /** + * X-Content-Type-Options + * + * Prevents MIME type sniffing attacks. + * Forces browser to respect declared Content-Type. + */ + { + key: "X-Content-Type-Options", + value: "nosniff", + }, + + /** + * X-Frame-Options + * + * Prevents clickjacking by controlling iframe embedding. + * DENY: Cannot be embedded in any iframe + * SAMEORIGIN: Can only be embedded by same-origin pages + */ + { + key: "X-Frame-Options", + value: "SAMEORIGIN", + }, + + /** + * X-XSS-Protection + * + * Enables browser's built-in XSS filtering. + * Note: Modern browsers rely more on CSP, but this provides + * additional protection for older browsers. + */ + { + key: "X-XSS-Protection", + value: "1; mode=block", + }, + + /** + * Referrer-Policy + * + * Controls how much referrer information is sent with requests. + * strict-origin-when-cross-origin: Full path for same-origin, + * only origin for cross-origin, nothing for downgrade to HTTP. + */ + { + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin", + }, + ], + }, + ]; + }, +}; + +export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..07020b4afde0570ac565c0c75175eb1b8c376485 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,11333 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.1.0", + "dependencies": { + "@tailwindcss/typography": "^0.5.19", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.26.2", + "lucide-react": "^0.562.0", + "next": "16.1.2", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-markdown": "^10.1.0", + "rehype-highlight": "^7.0.2", + "remark-gfm": "^4.0.1", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.53.0", + "@typescript-eslint/parser": "^8.53.0", + "@vitejs/plugin-react": "^4.5.2", + "@vitest/coverage-v8": "^3.2.4", + "eslint": "^9", + "eslint-config-next": "16.1.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "jsdom": "^26.1.0", + "prettier": "^3.8.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "tailwindcss": "^4", + "typescript": "^5", + "vitest": "^3.2.4" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "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.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "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": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.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.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.2.tgz", + "integrity": "sha512-r6TpLovDTvWtzw11UubUQxEK6IduT8rSAHbGX68yeFpA/1Oq9R4ovi5nqMUMgPN0jr2SpfeyFRbTZg3Inuuv3g==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.2.tgz", + "integrity": "sha512-jjO5BKDxZEXt2VCAnAG/ldULnpxeXspjCo9AZErV3Lm5HmNj8r2rS+eUMIAAj6mXPAOiPqAMgVPGnkyhPyDx4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.2.tgz", + "integrity": "sha512-0N2baysDpTXASTVxTV+DkBnD97bo9PatUj8sHlKA+oR9CyvReaPQchQyhCbH0Jm0mC/Oka5F52intN+lNOhSlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.2.tgz", + "integrity": "sha512-Q0wnSK0lmeC9ps+/w/bDsMSF3iWS45WEwF1bg8dvMH3CmKB2BV4346tVrjWxAkrZq20Ro6Of3R19IgrEJkXKyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.2.tgz", + "integrity": "sha512-4twW+h7ZatGKWq+2pUQ9SDiin6kfZE/mY+D8jOhSZ0NDzKhQfAPReXqwTDWVrNjvLzHzOcDL5kYjADHfXL/b/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.2.tgz", + "integrity": "sha512-Sn6LxPIZcADe5AnqqMCfwBv6vRtDikhtrjwhu+19WM6jHZe31JDRcGuPZAlJrDk6aEbNBPUUAKmySJELkBOesg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.2.tgz", + "integrity": "sha512-nwzesEQBfQIOOnQ7JArzB08w9qwvBQ7nC1i8gb0tiEFH94apzQM3IRpY19MlE8RBHxc9ArG26t1DEg2aaLaqVQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.2.tgz", + "integrity": "sha512-s60bLf16BDoICQHeKEm0lDgUNMsL1UpQCkRNZk08ZNnRpK0QUV+6TvVHuBcIA7oItzU0m7kVmXe8QjXngYxJVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.2.tgz", + "integrity": "sha512-Sq8k4SZd8Y8EokKdz304TvMO9HoiwGzo0CTacaiN1bBtbJSQ1BIwKzNFeFdxOe93SHn1YGnKXG6Mq3N+tVooyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.2.tgz", + "integrity": "sha512-KQDBwspSaNX5/wwt6p7ed5oINJWIxcgpuqJdDNubAyq7dD+ZM76NuEjg8yUxNOl5R4NNgbMfqE/RyNrsbYmOKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.1.tgz", + "integrity": "sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", + "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "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, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", + "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/type-utils": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.53.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz", + "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3" + }, + "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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz", + "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.0", + "@typescript-eslint/types": "^8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", + "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz", + "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz", + "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", + "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", + "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.53.0", + "@typescript-eslint/tsconfig-utils": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz", + "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0" + }, + "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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", + "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "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==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "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/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/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" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.2.tgz", + "integrity": "sha512-y97rpFfUsaXdXlQc2FMl/yqRc5yfVVKtKRcv+7LeyBrKh83INFegJuZBE28dc9Chp4iKXwmjaW4sHHx/mgyDyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.1.2", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.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" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "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", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "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" + }, + "peerDependencies": { + "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/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/framer-motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.26.2.tgz", + "integrity": "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.26.2", + "motion-utils": "^12.24.10", + "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/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "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", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/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/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "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", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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==", + "dev": true, + "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", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "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" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/motion-dom": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.26.2.tgz", + "integrity": "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.24.10" + } + }, + "node_modules/motion-utils": { + "version": "12.24.10", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz", + "integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==", + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.2.tgz", + "integrity": "sha512-SVSWX7wjUUDrIDVqhl4xm/jiOrvYGMG7NzVE/dGzzgs7r3dFGm4V19ia0xn3GDNtHCKM7C9h+5BoimnJBhmt9A==", + "license": "MIT", + "dependencies": { + "@next/env": "16.1.2", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.2", + "@next/swc-darwin-x64": "16.1.2", + "@next/swc-linux-arm64-gnu": "16.1.2", + "@next/swc-linux-arm64-musl": "16.1.2", + "@next/swc-linux-x64-gnu": "16.1.2", + "@next/swc-linux-x64-musl": "16.1.2", + "@next/swc-win32-arm64-msvc": "16.1.2", + "@next/swc-win32-x64-msvc": "16.1.2", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "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" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "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" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", + "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", + "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": 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", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "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-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rehype-highlight": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz", + "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-text": "^4.0.0", + "lowlight": "^3.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "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", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "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", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "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==", + "dev": true, + "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-cjs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.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", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.0.tgz", + "integrity": "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.53.0", + "@typescript-eslint/parser": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0" + }, + "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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "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", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "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/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "dev": true, + "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" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fe465a384812500ca83a681a37098b86870448cc --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,56 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint", + "lint:fix": "next lint --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"", + "typecheck": "tsc --noEmit", + "test": "vitest", + "test:coverage": "vitest --coverage" + }, + "dependencies": { + "@tailwindcss/typography": "^0.5.19", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.26.2", + "lucide-react": "^0.562.0", + "next": "16.1.2", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-markdown": "^10.1.0", + "rehype-highlight": "^7.0.2", + "remark-gfm": "^4.0.1", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.53.0", + "@typescript-eslint/parser": "^8.53.0", + "@vitejs/plugin-react": "^4.5.2", + "@vitest/coverage-v8": "^3.2.4", + "eslint": "^9", + "eslint-config-next": "16.1.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "jsdom": "^26.1.0", + "prettier": "^3.8.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "tailwindcss": "^4", + "typescript": "^5", + "vitest": "^3.2.4" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..61e36849cf7cfa9f1f71b4a3964a4953e3e243d3 --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/frontend/prettier.config.js b/frontend/prettier.config.js new file mode 100644 index 0000000000000000000000000000000000000000..f2c5daa4748cf36414b3e0542347a2b3cf12ff73 --- /dev/null +++ b/frontend/prettier.config.js @@ -0,0 +1,81 @@ +/** + * Prettier Configuration for Next.js Frontend + * + * This configuration ensures consistent code formatting across the + * RAG Chatbot frontend codebase. + * + * @see https://prettier.io/docs/en/configuration.html + */ + +/** @type {import('prettier').Config} */ +const config = { + // ============================================= + // Basic Formatting Rules + // ============================================= + + // Use single quotes for strings + singleQuote: true, + + // Add semicolons at the end of statements + semi: true, + + // Use 2 spaces for indentation + tabWidth: 2, + + // Don't use tabs, use spaces + useTabs: false, + + // Maximum line length before wrapping + printWidth: 80, + + // ============================================= + // Trailing Commas & Brackets + // ============================================= + + // Add trailing commas where valid in ES5 (objects, arrays, etc.) + trailingComma: 'es5', + + // Put the > of a multi-line element at the end of the last line + bracketSameLine: false, + + // Include parentheses around a sole arrow function parameter + arrowParens: 'always', + + // ============================================= + // JSX Formatting + // ============================================= + + // Use single quotes in JSX + jsxSingleQuote: false, + + // ============================================= + // Plugins + // ============================================= + + // Tailwind CSS class sorting plugin + plugins: ['prettier-plugin-tailwindcss'], + + // ============================================= + // File-specific Overrides + // ============================================= + + overrides: [ + { + // JSON files should use 2-space indentation + files: '*.json', + options: { + tabWidth: 2, + }, + }, + { + // Markdown files have wider print width + files: '*.md', + options: { + printWidth: 100, + proseWrap: 'always', + }, + }, + ], +}; + +export default config; diff --git a/frontend/public/file.svg b/frontend/public/file.svg new file mode 100644 index 0000000000000000000000000000000000000000..004145cddf3f9db91b57b9cb596683c8eb420862 --- /dev/null +++ b/frontend/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg new file mode 100644 index 0000000000000000000000000000000000000000..567f17b0d7c7fb662c16d4357dd74830caf2dccb --- /dev/null +++ b/frontend/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/next.svg b/frontend/public/next.svg new file mode 100644 index 0000000000000000000000000000000000000000..5174b28c565c285e3e312ec5178be64fbeca8398 --- /dev/null +++ b/frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg new file mode 100644 index 0000000000000000000000000000000000000000..77053960334e2e34dc584dea8019925c3b4ccca9 --- /dev/null +++ b/frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/window.svg b/frontend/public/window.svg new file mode 100644 index 0000000000000000000000000000000000000000..b2b2a44f6ebc70c450043c05a002e7a93ba5d651 --- /dev/null +++ b/frontend/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c Binary files /dev/null and b/frontend/src/app/favicon.ico differ diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..9dc3c100283603ddad69219b9f43aa2ff3f1f6ed --- /dev/null +++ b/frontend/src/app/globals.css @@ -0,0 +1,438 @@ +/** + * Global Styles for RAG Chatbot Frontend + * + * This file contains global CSS variables, base styles, and + * utility classes that complement Tailwind CSS. + * + * Color Theme: Modern Purple AI-Assistant Aesthetic + * ================================================ + * The purple color palette was chosen to convey intelligence, creativity, + * and technological sophistication - qualities often associated with AI assistants. + * Purple combines the stability of blue with the energy of red, creating a + * balanced, professional appearance suitable for a chatbot interface. + * + * @see https://tailwindcss.com/docs/adding-custom-styles + */ + +@import 'tailwindcss'; +@plugin "@tailwindcss/typography"; +@import "highlight.js/styles/atom-one-dark.css"; + +/** + * CSS Custom Properties (Design Tokens) + * + * These variables define the core design tokens for the application, + * enabling easy theming and dark mode support. + * + * Theme Selection Logic: + * - When `.dark` class is present on html -> dark mode (explicit override) + * - When `.light` class is present on html -> light mode (explicit override) + * - When neither class is present -> follow system preference + */ + +/** + * Light Mode Theme (default or explicit via .light class) + * + * Applied by default and when the user explicitly selects light mode. + * + * Purple Palette Accessibility Notes: + * ----------------------------------- + * - Primary 500 (#a855f7): Main brand purple, vibrant and modern + * - Primary 700 (#7c3aed): Used for text on white - achieves 5.0:1 contrast (WCAG AA) + * - Button text uses white (#ffffff) on purple-500 for 4.58:1 contrast (WCAG AA for large text) + * - For critical small text on purple backgrounds, use darker purple shades + */ +:root, +:root.light { + /** + * Primary Brand Colors - Modern Purple Palette + * + * This gradient from light lavender (50) to deep purple (900) provides + * versatility for backgrounds, borders, text, and interactive states. + * The purple family evokes creativity, wisdom, and technological innovation. + */ + --color-primary-50: #faf5ff; /* Very light lavender - subtle backgrounds, hover states */ + --color-primary-100: #f3e8ff; /* Light lavender - secondary backgrounds */ + --color-primary-200: #e9d5ff; /* Soft purple - selection backgrounds in light mode */ + --color-primary-300: #d8b4fe; /* Medium-light purple - decorative elements */ + --color-primary-400: #c084fc; /* Medium purple - icons, secondary elements */ + --color-primary-500: #a855f7; /* Main purple - primary buttons, key accents */ + --color-primary-600: #9333ea; /* Rich purple - active states */ + --color-primary-700: #7c3aed; /* Dark purple - hover states, accessible text */ + --color-primary-800: #6b21a8; /* Deep purple - pressed states */ + --color-primary-900: #581c87; /* Darkest purple - text on light backgrounds */ + + /** + * Accessible Primary Text Color + * + * #7c3aed (purple-700) provides 5.0:1 contrast ratio on white (#ffffff), + * exceeding the WCAG AA requirement of 4.5:1 for normal text. + * This ensures readability for links, labels, and highlighted text + * while maintaining the purple brand identity. + * + * Contrast verification: https://webaim.org/resources/contrastchecker/ + * - #7c3aed on #ffffff = 5.0:1 (passes WCAG AA for normal text) + * - #7c3aed on #f8fafc = 4.85:1 (passes WCAG AA for large text, AAA for UI components) + */ + --color-primary-text: #7c3aed; + /* WCAG AA: 5.0:1 on white */ + + /** + * Primary Button Text Color + * + * White (#ffffff) text on purple-500 (#a855f7) background provides + * 4.58:1 contrast ratio. This passes WCAG AA for large text (18pt+) + * and UI components. For buttons with smaller text, consider using + * a darker purple background (600 or 700) to achieve higher contrast. + * + * Contrast verification: + * - #ffffff on #a855f7 = 4.58:1 (passes WCAG AA for large text/UI) + * - #ffffff on #9333ea = 5.69:1 (passes WCAG AA for all text sizes) + */ + --color-primary-button-text: #ffffff; + /* WCAG AA: 4.58:1 on primary-500 (large text/UI), 5.69:1 on primary-600 */ + + /* Background colors */ + --background: #ffffff; + --background-secondary: #f8fafc; + --background-tertiary: #f1f5f9; + + /* Text colors */ + --foreground: #0f172a; + --foreground-secondary: #475569; + --foreground-muted: #64748b; + /* WCAG AA: 4.76:1 on white, 4.55:1 on bg-secondary */ + + /** + * Border Colors + * + * --border: Decorative borders for visual separation (cards, dividers) + * --border-ui: Essential UI component borders (inputs, selects) - higher contrast + * --border-focus: Focus ring color using purple-500 for brand consistency + */ + --border: #e2e8f0; + /* Decorative borders - use --border-ui for essential UI */ + --border-ui: #767f8c; + /* WCAG AA: 4.05:1 on white for essential UI components */ + --border-focus: #a855f7; + /* Purple-500: Visible focus indicator matching brand color */ + + /* Status colors */ + --success: #22c55e; + --warning: #f59e0b; + --error: #ef4444; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + + /* Border radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-full: 9999px; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 200ms ease; + --transition-slow: 300ms ease; + + /* Easing functions */ + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in: cubic-bezier(0.4, 0, 1, 1); +} + +/** + * Dark Mode Theme via System Preference + * + * Automatically activated when the user's OS prefers dark color scheme + * AND no explicit theme class (.light or .dark) is set on the html element. + * This respects system preference while allowing manual override. + * + * Dark Mode Purple Adjustments: + * ----------------------------- + * - Focus border uses purple-400 (#c084fc) for better visibility on dark backgrounds + * - Lighter purple shades become more prominent to maintain visual hierarchy + * - Shadows are intensified for depth perception on dark surfaces + */ +@media (prefers-color-scheme: dark) { + :root:not(.light):not(.dark) { + --background: #0f172a; + --background-secondary: #1e293b; + --background-tertiary: #334155; + + --foreground: #f8fafc; + --foreground-secondary: #cbd5e1; + --foreground-muted: #a3afc0; + /* WCAG AA: 8.03:1 on bg, 6.58:1 on bg-secondary, 4.66:1 on bg-tertiary */ + + --border: #334155; + /* Decorative borders - use --border-ui for essential UI */ + --border-ui: #64748b; + /* WCAG AA: 3.75:1 on dark bg for essential UI components */ + --border-focus: #c084fc; + /* Purple-400: Brighter purple for visibility on dark backgrounds */ + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5); + } +} + +/** + * Dark Mode Theme via Explicit Class + * + * Applied when the user explicitly selects dark mode via the theme toggle. + * This overrides the system preference, allowing users to choose dark mode + * even when their OS is set to light mode. + * + * Matches the system-preference dark mode settings for consistency. + */ +:root.dark { + --background: #0f172a; + --background-secondary: #1e293b; + --background-tertiary: #334155; + + --foreground: #f8fafc; + --foreground-secondary: #cbd5e1; + --foreground-muted: #a3afc0; + /* WCAG AA: 8.03:1 on bg, 6.58:1 on bg-secondary, 4.66:1 on bg-tertiary */ + + --border: #334155; + /* Decorative borders - use --border-ui for essential UI */ + --border-ui: #64748b; + /* WCAG AA: 3.75:1 on dark bg for essential UI components */ + --border-focus: #c084fc; + /* Purple-400: Brighter purple for visibility on dark backgrounds */ + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5); +} + +/** + * Base Element Styles + * + * Apply consistent base styles to HTML elements. + */ +html { + /* Smooth scrolling for anchor links */ + scroll-behavior: smooth; +} + +body { + /* Apply design tokens to body */ + background-color: var(--background); + color: var(--foreground); + + /* Optimize text rendering */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/** + * Focus Styles + * + * Consistent focus ring for accessibility. + * Uses the purple brand color for cohesive visual identity + * while maintaining clear focus indication for keyboard navigation. + */ +:focus-visible { + outline: 2px solid var(--border-focus); + outline-offset: 2px; +} + +/** + * Selection Styles + * + * Custom text selection colors using the purple palette. + * + * Light mode: Light purple background (#e9d5ff / primary-200) with dark purple text + * Dark mode: Dark purple background (#7c3aed / primary-700) with light lavender text + * + * These combinations ensure selected text remains readable while + * reinforcing the purple brand identity throughout the interface. + */ +::selection { + background-color: var(--color-primary-200); + /* #e9d5ff - soft purple selection background */ + color: var(--color-primary-900); + /* #581c87 - dark purple text for contrast */ +} + +/** + * Dark mode selection styles via system preference + * Applied when no explicit theme class is set and system prefers dark mode + */ +@media (prefers-color-scheme: dark) { + :root:not(.light):not(.dark) ::selection { + background-color: var(--color-primary-700); + /* #7c3aed - rich purple background */ + color: var(--color-primary-50); + /* #faf5ff - light lavender text */ + } +} + +/** + * Dark mode selection styles via explicit class + * Applied when .dark class is set on html element + */ +:root.dark ::selection { + background-color: var(--color-primary-700); + /* #7c3aed - rich purple background */ + color: var(--color-primary-50); + /* #faf5ff - light lavender text */ +} + +/** + * Scrollbar Styles (Webkit) + * + * Custom scrollbar appearance for webkit browsers. + */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--background-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--foreground-muted); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--foreground-secondary); +} + +/** + * Custom Animation Keyframes + */ +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +/** + * Fade and slide in animation for staggered list items. + * Used by SourceCitations component for smooth entrance effects. + */ +@keyframes fadeSlideIn { + from { + opacity: 0; + transform: translateY(0.5rem); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +/** + * Utility Classes + */ +.animate-fade-in { + animation: fadeIn var(--transition-normal) ease-out; +} + +.animate-slide-up { + animation: slideUp var(--transition-slow) ease-out; +} + +.animate-pulse-custom { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +/** + * Thermal Comfort Illustration Animations + * + * Custom keyframes for the ThermalComfortIllustration component. + * These provide subtle, professional animations that enhance the + * visual appeal without being distracting. + */ + +/** + * Gentle floating animation for the main illustration container. + * Creates a subtle up-and-down motion suggesting thermal air currents. + */ +@keyframes thermalFloat { + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-4px); + } +} + +/** + * Soft pulse animation for comfort zone elements. + * Provides a gentle "breathing" effect to indicate active thermal monitoring. + */ +@keyframes thermalPulse { + + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + + 50% { + opacity: 0.7; + transform: scale(0.98); + } +} + +/** + * Wave animation for air flow lines. + * Creates a gentle horizontal shift suggesting air movement. + */ +@keyframes thermalWave { + + 0%, + 100% { + transform: translateX(0); + opacity: 0.6; + } + + 50% { + transform: translateX(3px); + opacity: 0.9; + } +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2cfd1c00b070b95b27a467d2540c8586f13f2e02 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,124 @@ +/** + * Root Layout Component + * + * This is the root layout for the Next.js App Router. It wraps all pages + * and provides the base HTML structure, fonts, and global styles. + * + * @see https://nextjs.org/docs/app/building-your-application/routing/layouts-and-templates + */ + +import type { Metadata, Viewport } from 'next'; +import { Inter, JetBrains_Mono } from 'next/font/google'; +import './globals.css'; +import { Providers } from './providers'; + +/** + * Inter font configuration. + * + * Inter is used as the primary sans-serif font for its excellent + * readability and modern appearance. + */ +const inter = Inter({ + variable: '--font-inter', + subsets: ['latin'], + display: 'swap', +}); + +/** + * JetBrains Mono font configuration. + * + * Used for code blocks and monospace text to ensure + * consistent character widths. + */ +const jetbrainsMono = JetBrains_Mono({ + variable: '--font-mono', + subsets: ['latin'], + display: 'swap', +}); + +/** + * Application metadata for SEO and social sharing. + */ +export const metadata: Metadata = { + title: { + default: 'pythermalcomfort Chat', + template: '%s | pythermalcomfort Chat', + }, + description: + 'Ask questions about thermal comfort standards and the pythermalcomfort Python library. Powered by RAG with multiple LLM providers.', + keywords: [ + 'thermal comfort', + 'pythermalcomfort', + 'ASHRAE', + 'PMV', + 'PPD', + 'adaptive comfort', + 'building science', + 'HVAC', + ], + authors: [{ name: 'pythermalcomfort Team' }], + creator: 'pythermalcomfort', + openGraph: { + type: 'website', + locale: 'en_US', + title: 'pythermalcomfort Chat', + description: + 'AI-powered assistant for thermal comfort standards and pythermalcomfort library.', + siteName: 'pythermalcomfort Chat', + }, + robots: { + index: true, + follow: true, + }, +}; + +/** + * Viewport configuration for responsive design. + */ +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + themeColor: [ + { media: '(prefers-color-scheme: light)', color: '#ffffff' }, + { media: '(prefers-color-scheme: dark)', color: '#0f172a' }, + ], +}; + +/** + * Root Layout Props + */ +interface RootLayoutProps { + /** Page content to render */ + children: React.ReactNode; +} + +/** + * Root Layout Component + * + * Provides the base HTML structure for all pages in the application. + * Includes font loading, global styles, and common meta tags. + * + * @param props - Component props containing children to render + * @returns The root HTML structure wrapping all page content + */ +export default function RootLayout({ + children, +}: RootLayoutProps): React.ReactElement { + return ( + + + {/* + Main application content + The flex layout ensures footer stays at bottom + */} + +
{children}
+
+ + + ); +} diff --git a/frontend/src/app/loading.tsx b/frontend/src/app/loading.tsx new file mode 100644 index 0000000000000000000000000000000000000000..58d6545db0581c71a0182e7a033f2152492f792e --- /dev/null +++ b/frontend/src/app/loading.tsx @@ -0,0 +1,30 @@ +/** + * Loading Component + * + * This component is displayed while page content is loading. + * Next.js automatically shows this during navigation. + * + * @see https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming + */ + +/** + * Loading Skeleton + * + * Displays a loading animation while the page content loads. + * Uses a pulsing skeleton pattern for visual feedback. + * + * @returns Loading state UI + */ +export default function Loading(): React.ReactElement { + return ( +
+
+ {/* Spinning loader */} +
+ + {/* Loading text */} +

Loading...

+
+
+ ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9fd84073671fbc5139b460a777b484274590fc40 --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,107 @@ +/** + * Home Page Component + * + * The main landing page for the pythermalcomfort RAG Chatbot. + * Displays the full chat interface for interacting with the + * AI assistant about thermal comfort standards. + * + * @see https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts + */ + +'use client'; + +import { MessageSquare } from 'lucide-react'; +import { ChatContainer, Sidebar } from '@/components/chat'; + +/** + * Home Page + * + * Renders the complete chat interface for the pythermalcomfort + * RAG chatbot. The page uses a full-height layout with the + * ChatContainer taking up the entire viewport. + * + * @remarks + * ## Layout Structure + * + * The page uses a Gemini-style layout with: + * - App title fixed in the top-left corner (like "Gemini" branding) + * - Collapsible sidebar showing current provider/model + * - Chat container centered in the remaining space + * - Full viewport height + * + * ## Responsive Design + * + * - On mobile: Sidebar hidden, chat fills screen + * - On tablet/desktop: Sidebar visible, collapsible + * + * ## Chat Configuration + * + * The ChatContainer is configured with: + * - No internal header (title is at page level) + * - Empty initial messages (fresh conversation) + * - No persistence callback (can be added for localStorage support) + * + * @returns The home page with chat interface + */ +export default function HomePage(): React.ReactElement { + return ( +
+ {/* + Page Header - Gemini-style branding in top-left + + Fixed position at top-left of the page, outside the chat area. + This mimics how Gemini shows its logo/name. + */} +
+
+
+

+ AI-powered answers from scientific sources and official documentation +

+
+ + {/* + Main Content Area - Sidebar + Chat + + Horizontal layout with collapsible sidebar on left + and chat container filling the remaining space. + */} +
+ {/* + Left Sidebar - Provider/Model Info + + Shows current LLM provider and model name. + Collapsible to icon-only mode. + Hidden on mobile for better space utilization. + */} + + + {/* + ChatContainer - Main chat interface + + Fills the remaining space after sidebar. + No internal header - the title is shown at page level above. + */} + +
+
+ ); +} diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx new file mode 100644 index 0000000000000000000000000000000000000000..075677a08dba7822e5ccfa73367013b28c9d7313 --- /dev/null +++ b/frontend/src/app/providers.tsx @@ -0,0 +1,28 @@ +/** + * Client-side Providers Wrapper + * + * Wraps children with all client-side context providers. + * This component is used by the root layout to provide + * shared state to all pages. + * + * @module app/providers + */ + +'use client'; + +import type { ReactNode } from 'react'; +import { ProviderProvider } from '@/contexts/provider-context'; + +interface ProvidersProps { + children: ReactNode; +} + +/** + * Providers Component + * + * Wraps the application with all necessary context providers. + * Add additional providers here as needed. + */ +export function Providers({ children }: ProvidersProps): React.ReactElement { + return {children}; +} diff --git a/frontend/src/components/chat/__tests__/__snapshots__/empty-state.test.tsx.snap b/frontend/src/components/chat/__tests__/__snapshots__/empty-state.test.tsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..f0f4a74f9cd9d51f2b0a941e5d9704c09e4e0d4f --- /dev/null +++ b/frontend/src/components/chat/__tests__/__snapshots__/empty-state.test.tsx.snap @@ -0,0 +1,368 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`EmptyState > snapshots > should render correctly (snapshot) 1`] = ` +
+
+
+
+ +
+
+
+

+ Ask about thermal comfort and pythermalcomfort +

+

+ Get answers about thermal comfort models, concepts, standards and the pythermalcomfort library. Your questions are answered using scientific sources and official documentations. +

+
+
+

+ Try asking +

+
+ + + + +
+
+
+
+`; diff --git a/frontend/src/components/chat/__tests__/__snapshots__/error-state.test.tsx.snap b/frontend/src/components/chat/__tests__/__snapshots__/error-state.test.tsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..31a7d71b0bdac0e7979b2a254b6ac769245a7ff7 --- /dev/null +++ b/frontend/src/components/chat/__tests__/__snapshots__/error-state.test.tsx.snap @@ -0,0 +1,222 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ErrorState > snapshots > should render general type correctly (snapshot) 1`] = ` + +`; + +exports[`ErrorState > snapshots > should render network type correctly (snapshot) 1`] = ` + +`; + +exports[`ErrorState > snapshots > should render quota type correctly (snapshot) 1`] = ` + +`; diff --git a/frontend/src/components/chat/__tests__/empty-state.test.tsx b/frontend/src/components/chat/__tests__/empty-state.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1a859652787590950ba895b195adf9eb589c377f --- /dev/null +++ b/frontend/src/components/chat/__tests__/empty-state.test.tsx @@ -0,0 +1,375 @@ +/** + * Unit Tests for EmptyState Component + * + * Comprehensive test coverage for the empty state welcome component. + * Tests rendering, example question interactions, accessibility features, + * and edge cases. + * + * @module components/chat/__tests__/empty-state.test + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { EmptyState } from '../empty-state'; + +// ============================================================================ +// Mocks +// ============================================================================ + +/** + * Mock lucide-react icons for faster tests and to avoid lazy loading issues. + */ +vi.mock('lucide-react', () => ({ + MessageSquare: ({ + className, + 'aria-hidden': ariaHidden, + }: { + className?: string; + 'aria-hidden'?: boolean | 'true' | 'false'; + }) => ( + + ), +})); + +// ============================================================================ +// Test Fixtures +// ============================================================================ + +/** + * Example questions that should be displayed in the component. + */ +const EXPECTED_QUESTIONS = [ + 'What is the PMV model and how do I calculate it?', + 'How do I use the adaptive comfort model in pythermalcomfort?', + 'What are the inputs for calculating thermal comfort indices?', + 'Explain the difference between SET and PMV thermal comfort models.', +]; + +// ============================================================================ +// Test Suite +// ============================================================================ + +describe('EmptyState', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + // ========================================================================== + // Rendering Tests + // ========================================================================== + + describe('rendering', () => { + it('should render the main title', () => { + render(); + + expect( + screen.getByText('Ask about thermal comfort and pythermalcomfort') + ).toBeInTheDocument(); + }); + + it('should render the subtitle description', () => { + render(); + + expect( + screen.getByText(/get answers about thermal comfort standards/i) + ).toBeInTheDocument(); + }); + + it('should render "Try asking" label', () => { + render(); + + expect(screen.getByText('Try asking')).toBeInTheDocument(); + }); + + it('should render all example questions', () => { + render(); + + EXPECTED_QUESTIONS.forEach((question) => { + expect(screen.getByText(question)).toBeInTheDocument(); + }); + }); + + it('should render MessageSquare icon for each question', () => { + render(); + + const icons = screen.getAllByTestId('message-square-icon'); + expect(icons).toHaveLength(EXPECTED_QUESTIONS.length); + }); + + it('should render thermal comfort illustration (SVG)', () => { + const { container } = render(); + + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('should hide illustration from screen readers', () => { + const { container } = render(); + + const svg = container.querySelector('svg'); + expect(svg).toHaveAttribute('aria-hidden', 'true'); + }); + + it('should render example questions as buttons', () => { + render(); + + const buttons = screen.getAllByRole('button'); + expect(buttons).toHaveLength(EXPECTED_QUESTIONS.length); + }); + }); + + // ========================================================================== + // Interaction Tests + // ========================================================================== + + describe('interactions', () => { + it('should call onExampleClick when first question is clicked', () => { + const mockClick = vi.fn(); + + render(); + + fireEvent.click(screen.getByText(EXPECTED_QUESTIONS[0])); + + expect(mockClick).toHaveBeenCalledTimes(1); + expect(mockClick).toHaveBeenCalledWith(EXPECTED_QUESTIONS[0]); + }); + + it('should call onExampleClick with correct question for each button', () => { + const mockClick = vi.fn(); + + render(); + + EXPECTED_QUESTIONS.forEach((question, index) => { + fireEvent.click(screen.getByText(question)); + expect(mockClick).toHaveBeenNthCalledWith(index + 1, question); + }); + + expect(mockClick).toHaveBeenCalledTimes(EXPECTED_QUESTIONS.length); + }); + + it('should call onExampleClick multiple times when same question clicked repeatedly', () => { + const mockClick = vi.fn(); + + render(); + + const firstQuestion = screen.getByText(EXPECTED_QUESTIONS[0]); + + fireEvent.click(firstQuestion); + fireEvent.click(firstQuestion); + fireEvent.click(firstQuestion); + + expect(mockClick).toHaveBeenCalledTimes(3); + }); + }); + + // ========================================================================== + // Accessibility Tests + // ========================================================================== + + describe('accessibility', () => { + it('should have semantic heading hierarchy with h2', () => { + render(); + + const heading = screen.getByRole('heading', { level: 2 }); + expect(heading).toBeInTheDocument(); + expect(heading).toHaveTextContent('Ask about thermal comfort and pythermalcomfort'); + }); + + it('should have focusable question buttons', () => { + render(); + + const buttons = screen.getAllByRole('button'); + buttons.forEach((button) => { + button.focus(); + expect(button).toHaveFocus(); + }); + }); + + it('should have visible focus styles on buttons', () => { + render(); + + const buttons = screen.getAllByRole('button'); + buttons.forEach((button) => { + expect(button.className).toMatch(/focus-visible:ring/); + }); + }); + + it('should have icons with aria-hidden for decorative elements', () => { + render(); + + const icons = screen.getAllByTestId('message-square-icon'); + icons.forEach((icon) => { + expect(icon).toHaveAttribute('aria-hidden', 'true'); + }); + }); + + it('should support keyboard Enter to activate question buttons', () => { + const mockClick = vi.fn(); + + render(); + + const firstButton = screen.getAllByRole('button')[0]; + firstButton.focus(); + + // Simulate Enter key (buttons natively handle this) + fireEvent.keyDown(firstButton, { key: 'Enter', code: 'Enter' }); + fireEvent.click(firstButton); + + expect(mockClick).toHaveBeenCalled(); + }); + + it('should support keyboard Space to activate question buttons', () => { + const mockClick = vi.fn(); + + render(); + + const firstButton = screen.getAllByRole('button')[0]; + firstButton.focus(); + + // Simulate Space key (buttons natively handle this) + fireEvent.keyDown(firstButton, { key: ' ', code: 'Space' }); + fireEvent.click(firstButton); + + expect(mockClick).toHaveBeenCalled(); + }); + }); + + // ========================================================================== + // Styling Tests + // ========================================================================== + + describe('styling', () => { + it('should apply custom className to container', () => { + const { container } = render( + + ); + + const outerDiv = container.firstChild; + expect(outerDiv).toHaveClass('custom-class', 'mt-8'); + }); + + it('should have animation class for entrance effect', () => { + const { container } = render(); + + const outerDiv = container.firstChild; + expect(outerDiv).toHaveClass('animate-slide-up'); + }); + + it('should have glassmorphism styling on card', () => { + const { container } = render(); + + // Find the card container (child of outer div) + const card = container.querySelector('.backdrop-blur-sm'); + expect(card).toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Edge Cases Tests + // ========================================================================== + + describe('edge cases', () => { + it('should render without crashing when onExampleClick is a no-op', () => { + render( {}} />); + + expect( + screen.getByText('Ask about thermal comfort and pythermalcomfort') + ).toBeInTheDocument(); + }); + + it('should forward additional HTML attributes', () => { + render( + + ); + + const container = screen.getByTestId('empty-state'); + expect(container).toHaveAttribute('title', 'Welcome state'); + }); + + it('should render questions in a grid layout', () => { + const { container } = render(); + + const grid = container.querySelector('.grid'); + expect(grid).toBeInTheDocument(); + expect(grid).toHaveClass('sm:grid-cols-2'); + }); + + it('should maintain button type as "button"', () => { + render(); + + const buttons = screen.getAllByRole('button'); + buttons.forEach((button) => { + expect(button).toHaveAttribute('type', 'button'); + }); + }); + }); + + // ========================================================================== + // Snapshot Tests + // ========================================================================== + + describe('snapshots', () => { + it('should render correctly (snapshot)', () => { + const { container } = render(); + + expect(container.firstChild).toMatchSnapshot(); + }); + }); + + // ========================================================================== + // Integration Tests + // ========================================================================== + + describe('integration', () => { + it('should render complete empty state with all elements', () => { + render(); + + // Title and subtitle + expect( + screen.getByText('Ask about thermal comfort and pythermalcomfort') + ).toBeInTheDocument(); + expect( + screen.getByText(/get answers about thermal comfort/i) + ).toBeInTheDocument(); + + // "Try asking" label + expect(screen.getByText('Try asking')).toBeInTheDocument(); + + // All questions as buttons + const buttons = screen.getAllByRole('button'); + expect(buttons).toHaveLength(4); + + // All icons + const icons = screen.getAllByTestId('message-square-icon'); + expect(icons).toHaveLength(4); + }); + + it('should work correctly through a complete user interaction flow', () => { + const mockClick = vi.fn(); + + render(); + + // Verify initial state + expect(mockClick).not.toHaveBeenCalled(); + + // Click first question + fireEvent.click(screen.getByText(EXPECTED_QUESTIONS[0])); + expect(mockClick).toHaveBeenLastCalledWith(EXPECTED_QUESTIONS[0]); + + // Click last question + fireEvent.click(screen.getByText(EXPECTED_QUESTIONS[3])); + expect(mockClick).toHaveBeenLastCalledWith(EXPECTED_QUESTIONS[3]); + + // Total clicks + expect(mockClick).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/frontend/src/components/chat/__tests__/error-state.test.tsx b/frontend/src/components/chat/__tests__/error-state.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d54ecd1973ed3f18543661434cda6f747a8ae937 --- /dev/null +++ b/frontend/src/components/chat/__tests__/error-state.test.tsx @@ -0,0 +1,797 @@ +/** + * Unit Tests for ErrorState Component + * + * Comprehensive test coverage for the error display component. + * Tests rendering states, countdown timer functionality, interactions, + * accessibility features, and visual/snapshot tests. + * + * @module components/chat/__tests__/error-state.test + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, fireEvent, act } from '@testing-library/react'; +import { ErrorState, type ErrorType } from '../error-state'; + +// ============================================================================ +// Mocks +// ============================================================================ + +/** + * Mock lucide-react icons for faster tests and to avoid lazy loading issues. + * Each icon is replaced with a simple span containing a test ID and class. + */ +vi.mock('lucide-react', () => ({ + AlertTriangle: ({ + className, + 'aria-hidden': ariaHidden, + }: { + className?: string; + 'aria-hidden'?: boolean | 'true' | 'false'; + }) => ( + + ), + WifiOff: ({ + className, + 'aria-hidden': ariaHidden, + }: { + className?: string; + 'aria-hidden'?: boolean | 'true' | 'false'; + }) => ( + + ), + Clock: ({ + className, + 'aria-hidden': ariaHidden, + }: { + className?: string; + 'aria-hidden'?: boolean | 'true' | 'false'; + }) => ( + + ), + RefreshCw: ({ + className, + 'aria-hidden': ariaHidden, + }: { + className?: string; + 'aria-hidden'?: boolean | 'true' | 'false'; + }) => ( + + ), + X: ({ + className, + 'aria-hidden': ariaHidden, + }: { + className?: string; + 'aria-hidden'?: boolean | 'true' | 'false'; + }) => ( + + ), +})); + +// ============================================================================ +// Test Helpers +// ============================================================================ + +/** + * Default error messages for each error type. + */ +const DEFAULT_MESSAGES: Record = { + quota: 'Our service is currently at capacity. Please wait a moment.', + network: 'Unable to connect. Please check your internet connection.', + general: 'Something went wrong. Please try again.', +}; + +/** + * Titles displayed for each error type. + */ +const ERROR_TITLES: Record = { + quota: 'Service Temporarily Unavailable', + network: 'Connection Lost', + general: 'Oops! Something Went Wrong', +}; + +// ============================================================================ +// Test Suite +// ============================================================================ + +describe('ErrorState', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + afterEach(() => { + vi.clearAllTimers(); + vi.useRealTimers(); + }); + + // ========================================================================== + // Rendering Tests + // ========================================================================== + + describe('rendering', () => { + it('should render quota error type correctly with Clock icon', () => { + render(); + + // Should show quota-specific title + expect( + screen.getByText('Service Temporarily Unavailable') + ).toBeInTheDocument(); + + // Should show default quota message + expect( + screen.getByText(DEFAULT_MESSAGES.quota) + ).toBeInTheDocument(); + + // Clock icon should be used for quota type (main icon) + const clockIcons = screen.getAllByTestId('clock-icon'); + expect(clockIcons.length).toBeGreaterThan(0); + }); + + it('should render network error type correctly with WifiOff icon', () => { + render(); + + // Should show network-specific title + expect(screen.getByText('Connection Lost')).toBeInTheDocument(); + + // Should show default network message + expect( + screen.getByText(DEFAULT_MESSAGES.network) + ).toBeInTheDocument(); + + // WifiOff icon should be present + expect(screen.getByTestId('wifi-off-icon')).toBeInTheDocument(); + }); + + it('should render general error type correctly with AlertTriangle icon', () => { + render(); + + // Should show general-specific title + expect( + screen.getByText('Oops! Something Went Wrong') + ).toBeInTheDocument(); + + // Should show default general message + expect( + screen.getByText(DEFAULT_MESSAGES.general) + ).toBeInTheDocument(); + + // AlertTriangle icon should be present + expect(screen.getByTestId('alert-triangle-icon')).toBeInTheDocument(); + }); + + it('should show default message when no custom message provided', () => { + render(); + + expect( + screen.getByText(DEFAULT_MESSAGES.quota) + ).toBeInTheDocument(); + }); + + it('should show custom message when provided', () => { + const customMessage = 'A custom error message for testing purposes.'; + + render(); + + expect(screen.getByText(customMessage)).toBeInTheDocument(); + // Default message should not be present + expect( + screen.queryByText(DEFAULT_MESSAGES.quota) + ).not.toBeInTheDocument(); + }); + + it('should show dismiss button when onDismiss callback provided', () => { + const mockDismiss = vi.fn(); + + render(); + + const dismissButton = screen.getByLabelText('Dismiss error'); + expect(dismissButton).toBeInTheDocument(); + expect(screen.getByTestId('x-icon')).toBeInTheDocument(); + }); + + it('should hide dismiss button when onDismiss not provided', () => { + render(); + + expect(screen.queryByLabelText('Dismiss error')).not.toBeInTheDocument(); + expect(screen.queryByTestId('x-icon')).not.toBeInTheDocument(); + }); + + it('should show retry button when onRetry callback provided', () => { + const mockRetry = vi.fn(); + + render(); + + const retryButton = screen.getByRole('button', { name: /try again/i }); + expect(retryButton).toBeInTheDocument(); + expect(screen.getByTestId('refresh-icon')).toBeInTheDocument(); + }); + + it('should hide retry button when onRetry not provided', () => { + render(); + + expect( + screen.queryByRole('button', { name: /try again/i }) + ).not.toBeInTheDocument(); + expect(screen.queryByTestId('refresh-icon')).not.toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Countdown Timer Tests + // ========================================================================== + + describe('countdown timer', () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should display countdown timer for quota errors with retryAfter', () => { + render(); + + // Should show the countdown timer text + expect(screen.getByText(/try again in/i)).toBeInTheDocument(); + }); + + it('should format countdown correctly for seconds (e.g., "45s")', () => { + render(); + + expect(screen.getByText(/45s/)).toBeInTheDocument(); + }); + + it('should format countdown correctly for minutes (e.g., "1:30")', () => { + render(); + + expect(screen.getByText(/1:30/)).toBeInTheDocument(); + }); + + it('should format countdown correctly for exact minutes (e.g., "2:00")', () => { + render(); + + expect(screen.getByText(/2:00/)).toBeInTheDocument(); + }); + + it('should decrement countdown every second', async () => { + render(); + + // Initially shows 5s + expect(screen.getByText(/5s/)).toBeInTheDocument(); + + // Advance by 1 second + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText(/4s/)).toBeInTheDocument(); + + // Advance by another second + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText(/3s/)).toBeInTheDocument(); + + // Advance by another second + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText(/2s/)).toBeInTheDocument(); + }); + + it('should disable retry button during countdown', () => { + const mockRetry = vi.fn(); + + render(); + + const retryButton = screen.getByRole('button', { name: /please wait/i }); + expect(retryButton).toBeDisabled(); + }); + + it('should enable retry button when countdown reaches zero', async () => { + const mockRetry = vi.fn(); + + render(); + + // Initially disabled + expect( + screen.getByRole('button', { name: /please wait/i }) + ).toBeDisabled(); + + // Advance past countdown + await act(async () => { + vi.advanceTimersByTime(3000); + }); + + // Should now be enabled with "Try Again" text + const retryButton = screen.getByRole('button', { name: /try again/i }); + expect(retryButton).not.toBeDisabled(); + }); + + it('should show "Ready to retry" text when countdown reaches zero', async () => { + const mockRetry = vi.fn(); + + render(); + + // Advance past countdown + await act(async () => { + vi.advanceTimersByTime(3000); + }); + + expect(screen.getByText(/ready to retry/i)).toBeInTheDocument(); + }); + + it('should not show countdown for network errors even with retryAfter', () => { + render(); + + // Network errors should not show countdown timer area + expect(screen.queryByText(/try again in/i)).not.toBeInTheDocument(); + }); + + it('should not show countdown for general errors even with retryAfter', () => { + render(); + + // General errors should not show countdown timer area + expect(screen.queryByText(/try again in/i)).not.toBeInTheDocument(); + }); + + it('should not disable retry button for network errors', () => { + const mockRetry = vi.fn(); + + render(); + + // Network errors should have enabled retry button + const retryButton = screen.getByRole('button', { name: /try again/i }); + expect(retryButton).not.toBeDisabled(); + }); + }); + + // ========================================================================== + // Interaction Tests + // ========================================================================== + + describe('interactions', () => { + it('should call onRetry when retry button is clicked', () => { + const mockRetry = vi.fn(); + + render(); + + const retryButton = screen.getByRole('button', { name: /try again/i }); + fireEvent.click(retryButton); + + expect(mockRetry).toHaveBeenCalledTimes(1); + }); + + it('should call onDismiss when dismiss button is clicked', () => { + const mockDismiss = vi.fn(); + + render(); + + const dismissButton = screen.getByLabelText('Dismiss error'); + fireEvent.click(dismissButton); + + expect(mockDismiss).toHaveBeenCalledTimes(1); + }); + + it('should not call onRetry when retry is disabled during countdown', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + const mockRetry = vi.fn(); + + render(); + + const retryButton = screen.getByRole('button', { name: /please wait/i }); + + // Try to click the disabled button + fireEvent.click(retryButton); + + expect(mockRetry).not.toHaveBeenCalled(); + + vi.useRealTimers(); + }); + + it('should call onRetry after countdown completes', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + const mockRetry = vi.fn(); + + render(); + + // Wait for countdown to complete + await act(async () => { + vi.advanceTimersByTime(3000); + }); + + // Now click should work + const retryButton = screen.getByRole('button', { name: /try again/i }); + fireEvent.click(retryButton); + + expect(mockRetry).toHaveBeenCalledTimes(1); + + vi.useRealTimers(); + }); + }); + + // ========================================================================== + // Accessibility Tests + // ========================================================================== + + describe('accessibility', () => { + it('should have role="alert" attribute', () => { + render(); + + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + }); + + it('should have aria-live="assertive" attribute', () => { + render(); + + const alert = screen.getByRole('alert'); + expect(alert).toHaveAttribute('aria-live', 'assertive'); + }); + + it('should have icons with aria-hidden="true"', () => { + render( + + ); + + // Main icon (AlertTriangle for general) + const alertIcon = screen.getByTestId('alert-triangle-icon'); + expect(alertIcon).toHaveAttribute('aria-hidden', 'true'); + + // Refresh icon in retry button + const refreshIcon = screen.getByTestId('refresh-icon'); + expect(refreshIcon).toHaveAttribute('aria-hidden', 'true'); + + // X icon in dismiss button + const xIcon = screen.getByTestId('x-icon'); + expect(xIcon).toHaveAttribute('aria-hidden', 'true'); + }); + + it('should have countdown with aria-live="polite" for updates', () => { + render(); + + // Find the countdown container - it should have aria-live="polite" + const countdownContainer = screen.getByText(/try again in/i).closest('div'); + expect(countdownContainer).toHaveAttribute('aria-live', 'polite'); + }); + + it('should have dismiss button with aria-label', () => { + render(); + + const dismissButton = screen.getByLabelText('Dismiss error'); + expect(dismissButton).toBeInTheDocument(); + expect(dismissButton).toHaveAttribute('aria-label', 'Dismiss error'); + }); + + it('should be keyboard navigable', () => { + const mockRetry = vi.fn(); + const mockDismiss = vi.fn(); + + render( + + ); + + // Dismiss button should be focusable + const dismissButton = screen.getByLabelText('Dismiss error'); + dismissButton.focus(); + expect(dismissButton).toHaveFocus(); + + // Retry button should be focusable + const retryButton = screen.getByRole('button', { name: /try again/i }); + retryButton.focus(); + expect(retryButton).toHaveFocus(); + + // Press Enter to activate retry (using keyboard event) + fireEvent.keyDown(retryButton, { key: 'Enter', code: 'Enter' }); + fireEvent.keyUp(retryButton, { key: 'Enter', code: 'Enter' }); + // Note: button click events are triggered by Enter key natively in tests, + // but we verify focus works correctly + }); + + it('should have visible focus styles on buttons', () => { + render( + + ); + + const retryButton = screen.getByRole('button', { name: /try again/i }); + const dismissButton = screen.getByLabelText('Dismiss error'); + + // Check for focus-visible ring classes + expect(retryButton.className).toMatch(/focus-visible:ring/); + expect(dismissButton.className).toMatch(/focus-visible:ring/); + }); + }); + + // ========================================================================== + // Snapshot/Visual Tests + // ========================================================================== + + describe('snapshots', () => { + it('should render quota type correctly (snapshot)', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('should render network type correctly (snapshot)', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('should render general type correctly (snapshot)', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + }); + + // ========================================================================== + // Edge Cases Tests + // ========================================================================== + + describe('edge cases', () => { + it('should handle retryAfter of 0 correctly', () => { + const mockRetry = vi.fn(); + + render(); + + // Retry button should be enabled immediately + const retryButton = screen.getByRole('button', { name: /try again/i }); + expect(retryButton).not.toBeDisabled(); + }); + + it('should handle undefined retryAfter correctly', () => { + const mockRetry = vi.fn(); + + render(); + + // Should not show countdown area when retryAfter is undefined + expect(screen.queryByText(/try again in/i)).not.toBeInTheDocument(); + }); + + it('should apply custom className', () => { + render(); + + const alert = screen.getByRole('alert'); + expect(alert).toHaveClass('custom-test-class', 'mt-8'); + }); + + it('should render both retry and dismiss buttons together', () => { + render( + + ); + + expect( + screen.getByRole('button', { name: /try again/i }) + ).toBeInTheDocument(); + expect(screen.getByLabelText('Dismiss error')).toBeInTheDocument(); + }); + + it('should handle very large retryAfter values', () => { + render(); + + // Should format large values correctly (60:00 for 1 hour) + expect(screen.getByText(/60:00/)).toBeInTheDocument(); + }); + + it('should handle countdown reset when retryAfter prop changes', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + + const { rerender } = render( + + ); + + expect(screen.getByText(/10s/)).toBeInTheDocument(); + + // Advance some time + await act(async () => { + vi.advanceTimersByTime(3000); + }); + expect(screen.getByText(/7s/)).toBeInTheDocument(); + + // Change retryAfter prop + rerender(); + + // Should reset to new value + expect(screen.getByText(/20s/)).toBeInTheDocument(); + + vi.useRealTimers(); + }); + + it('should forward additional HTML attributes', () => { + render( + + ); + + const alert = screen.getByTestId('custom-error'); + expect(alert).toHaveAttribute('title', 'Error notification'); + }); + + it('should render with minimal props (just type)', () => { + render(); + + expect(screen.getByRole('alert')).toBeInTheDocument(); + expect( + screen.getByText('Oops! Something Went Wrong') + ).toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Integration Tests + // ========================================================================== + + describe('integration', () => { + it('should render complete quota error with all features', () => { + const customMessage = + 'Rate limit exceeded. Please wait before sending more requests.'; + + render( + + ); + + // Title + expect( + screen.getByText('Service Temporarily Unavailable') + ).toBeInTheDocument(); + + // Custom message + expect(screen.getByText(customMessage)).toBeInTheDocument(); + + // Countdown + expect(screen.getByText(/45s/)).toBeInTheDocument(); + + // Buttons + expect( + screen.getByRole('button', { name: /please wait/i }) + ).toBeInTheDocument(); + expect(screen.getByLabelText('Dismiss error')).toBeInTheDocument(); + + // Icons + const clockIcons = screen.getAllByTestId('clock-icon'); + expect(clockIcons.length).toBeGreaterThan(0); + }); + + it('should render complete network error with all features', () => { + render( + + ); + + // Title + expect(screen.getByText('Connection Lost')).toBeInTheDocument(); + + // Default message + expect( + screen.getByText(DEFAULT_MESSAGES.network) + ).toBeInTheDocument(); + + // Buttons (not disabled for network type) + expect( + screen.getByRole('button', { name: /try again/i }) + ).not.toBeDisabled(); + + // Icon + expect(screen.getByTestId('wifi-off-icon')).toBeInTheDocument(); + }); + + it('should work correctly through a complete user flow', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + const mockRetry = vi.fn(); + const mockDismiss = vi.fn(); + + render( + + ); + + // Initially button is disabled + expect( + screen.getByRole('button', { name: /please wait/i }) + ).toBeDisabled(); + + // Wait for countdown + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText(/2s/)).toBeInTheDocument(); + + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText(/1s/)).toBeInTheDocument(); + + await act(async () => { + vi.advanceTimersByTime(1000); + }); + + // Now button should be enabled + const retryButton = screen.getByRole('button', { name: /try again/i }); + expect(retryButton).not.toBeDisabled(); + + // Click retry + fireEvent.click(retryButton); + expect(mockRetry).toHaveBeenCalledTimes(1); + + // Click dismiss + fireEvent.click(screen.getByLabelText('Dismiss error')); + expect(mockDismiss).toHaveBeenCalledTimes(1); + + vi.useRealTimers(); + }); + }); +}); diff --git a/frontend/src/components/chat/__tests__/provider-toggle.test.tsx b/frontend/src/components/chat/__tests__/provider-toggle.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d0274eafbd5c2c067ce5fe688b10dc963d8880df --- /dev/null +++ b/frontend/src/components/chat/__tests__/provider-toggle.test.tsx @@ -0,0 +1,1376 @@ +/** + * Unit Tests for ProviderToggle Component + * + * Comprehensive test coverage for the provider selection component. + * Tests rendering states, provider pills, selection behavior, + * cooldown timers, accessibility, and compact mode. + * + * @module components/chat/__tests__/provider-toggle.test + */ + +import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest'; +import { render, screen, fireEvent, waitFor, within, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ProviderToggle } from '../provider-toggle'; +import { useProviders, type ProviderStatus } from '@/hooks'; + +// ============================================================================ +// Mocks +// ============================================================================ + +vi.mock('@/hooks', () => ({ + useProviders: vi.fn(), +})); + +// ============================================================================ +// Test Helpers +// ============================================================================ + +/** + * Create a mock provider status object. + * + * @param overrides - Properties to override in the default provider + * @returns Mock provider status + */ +function createMockProvider( + overrides: Partial = {} +): ProviderStatus { + return { + id: 'gemini', + name: 'Gemini', + description: 'Gemini Flash / Gemma', + isAvailable: true, + cooldownSeconds: null, + totalRequestsRemaining: 10, + primaryModel: 'gemini-2.5-flash-lite', + allModels: ['gemini-2.5-flash-lite', 'gemini-2.5-flash', 'gemini-3-flash', 'gemma-3-27b-it'], + ...overrides, + }; +} + +/** + * Create default mock return value for useProviders hook. + * + * @param overrides - Properties to override in the default return value + * @returns Mock useProviders return value + */ +function createMockUseProvidersReturn( + overrides: Partial> = {} +): ReturnType { + return { + providers: [], + selectedProvider: null, + selectProvider: vi.fn(), + isLoading: false, + error: null, + refresh: vi.fn(), + lastUpdated: new Date(), + ...overrides, + }; +} + +/** + * Set up the useProviders mock with the given return value. + * + * @param mockReturn - Mock return value for useProviders + */ +function setupMock(mockReturn: ReturnType): void { + (useProviders as Mock).mockReturnValue(mockReturn); +} + +// ============================================================================ +// Test Suite +// ============================================================================ + +describe('ProviderToggle', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + afterEach(() => { + vi.clearAllTimers(); + vi.useRealTimers(); + }); + + // ========================================================================== + // Rendering States Tests + // ========================================================================== + + describe('rendering states', () => { + it('should render loading skeleton when isLoading is true', () => { + setupMock( + createMockUseProvidersReturn({ + isLoading: true, + providers: [], + }) + ); + + render(); + + // Should have skeleton elements with aria-label for loading + const loadingGroup = screen.getByRole('group', { + name: /loading provider options/i, + }); + expect(loadingGroup).toBeInTheDocument(); + + // Should have 3 skeleton pills (Auto + 2 providers) + const skeletons = loadingGroup.querySelectorAll('.animate-pulse'); + expect(skeletons).toHaveLength(3); + }); + + it('should render error state with refresh button when error and no providers', () => { + const mockRefresh = vi.fn(); + setupMock( + createMockUseProvidersReturn({ + isLoading: false, + error: 'Failed to load providers', + providers: [], + refresh: mockRefresh, + }) + ); + + render(); + + // Should show error alert + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent('Failed to load providers'); + + // Should have retry button + const retryButton = screen.getByRole('button', { + name: /retry loading providers/i, + }); + expect(retryButton).toBeInTheDocument(); + }); + + it('should render provider pills when providers are available', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + // Should have radiogroup role + const radiogroup = screen.getByRole('radiogroup', { + name: /select llm provider/i, + }); + expect(radiogroup).toBeInTheDocument(); + + // Should have provider pills (Auto + 2 providers) + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(3); + }); + + it('should always render "Auto" option first', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const radios = screen.getAllByRole('radio'); + + // First radio should be Auto + expect(radios[0]).toHaveAccessibleName(/auto/i); + }); + + it('should show providers even when there is an error if providers exist', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + error: 'Refresh failed', + }) + ); + + render(); + + // Should show providers, not error + const radiogroup = screen.getByRole('radiogroup'); + expect(radiogroup).toBeInTheDocument(); + + // Should not show error alert when providers exist + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Provider Pills Tests + // ========================================================================== + + describe('provider pills', () => { + it('should display provider name', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect(screen.getByText('Auto')).toBeInTheDocument(); + expect(screen.getByText('Gemini')).toBeInTheDocument(); + expect(screen.getByText('Groq')).toBeInTheDocument(); + }); + + it('should show green status indicator for available providers', () => { + const mockProviders = [ + createMockProvider({ + id: 'gemini', + name: 'Gemini', + isAvailable: true, + cooldownSeconds: null, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Find the Gemini button and check for green indicator + const geminiButton = screen.getByRole('radio', { name: /gemini/i }); + const indicator = geminiButton.querySelector('.bg-\\[var\\(--success\\)\\]'); + expect(indicator).toBeInTheDocument(); + }); + + it('should show red status indicator for providers in cooldown', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 45, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Find the Groq button and check for red indicator + const groqButton = screen.getByRole('radio', { name: /groq/i }); + const indicator = groqButton.querySelector('.bg-\\[var\\(--error\\)\\]'); + expect(indicator).toBeInTheDocument(); + }); + + it('should show gray status indicator for unavailable providers without cooldown', () => { + const mockProviders = [ + createMockProvider({ + id: 'deepseek', + name: 'DeepSeek', + isAvailable: false, + cooldownSeconds: null, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Find the DeepSeek button and check for gray indicator + const deepseekButton = screen.getByRole('radio', { name: /deepseek/i }); + const indicator = deepseekButton.querySelector( + '.bg-\\[var\\(--foreground-muted\\)\\]' + ); + expect(indicator).toBeInTheDocument(); + }); + + it('should mark selected provider with aria-checked true', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'gemini', + }) + ); + + render(); + + const geminiButton = screen.getByRole('radio', { name: /gemini.*selected/i }); + expect(geminiButton).toHaveAttribute('aria-checked', 'true'); + + const groqButton = screen.getByRole('radio', { name: /groq/i }); + expect(groqButton).toHaveAttribute('aria-checked', 'false'); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + expect(autoButton).toHaveAttribute('aria-checked', 'false'); + }); + + it('should mark Auto as selected when selectedProvider is null', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: null, + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto.*selected/i }); + expect(autoButton).toHaveAttribute('aria-checked', 'true'); + }); + }); + + // ========================================================================== + // Provider Selection Tests + // ========================================================================== + + describe('provider selection', () => { + it('should call selectProvider when a pill is clicked', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const geminiButton = screen.getByRole('radio', { name: /gemini/i }); + await user.click(geminiButton); + + expect(mockSelectProvider).toHaveBeenCalledTimes(1); + expect(mockSelectProvider).toHaveBeenCalledWith('gemini'); + }); + + it('should update aria-checked attribute for selected provider', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'groq', + }) + ); + + render(); + + const groqButton = screen.getByRole('radio', { name: /groq.*selected/i }); + expect(groqButton).toHaveAttribute('aria-checked', 'true'); + + const geminiButton = screen.getByRole('radio', { name: /gemini/i }); + expect(geminiButton).toHaveAttribute('aria-checked', 'false'); + }); + + it('should allow selecting Auto (null) option', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'gemini', + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + await user.click(autoButton); + + expect(mockSelectProvider).toHaveBeenCalledWith(null); + }); + + it('should allow selecting unavailable providers (user choice)', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 60, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const groqButton = screen.getByRole('radio', { name: /groq/i }); + await user.click(groqButton); + + // Should still allow selection even if unavailable + expect(mockSelectProvider).toHaveBeenCalledWith('groq'); + }); + }); + + // ========================================================================== + // Cooldown Timer Tests + // ========================================================================== + + describe('cooldown timer', () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should display cooldown time for providers with cooldownSeconds', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 45, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + // Should display the countdown + expect(screen.getByText('45s')).toBeInTheDocument(); + }); + + it('should format time as "Xm Ys" for times with minutes and seconds', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 130, // 2m 10s + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect(screen.getByText('2m 10s')).toBeInTheDocument(); + }); + + it('should format time as "Xm" for exact minutes', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 120, // 2m 0s + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect(screen.getByText('2m')).toBeInTheDocument(); + }); + + it('should format time as "Xs" for times under a minute', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 30, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect(screen.getByText('30s')).toBeInTheDocument(); + }); + + it('should decrement countdown every second', async () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 5, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect(screen.getByText('5s')).toBeInTheDocument(); + + // Advance by 1 second using act to wrap the state update + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText('4s')).toBeInTheDocument(); + + // Advance by another second + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText('3s')).toBeInTheDocument(); + }); + + it('should stop countdown and hide when reaching 0', async () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 2, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect(screen.getByText('2s')).toBeInTheDocument(); + + // Advance to 1s + await act(async () => { + vi.advanceTimersByTime(1000); + }); + expect(screen.getByText('1s')).toBeInTheDocument(); + + // Advance past 0 + await act(async () => { + vi.advanceTimersByTime(1000); + }); + + // Timer should be hidden + expect(screen.queryByText('0s')).not.toBeInTheDocument(); + expect(screen.queryByText('1s')).not.toBeInTheDocument(); + }); + + it('should not display timer when cooldownSeconds is null', () => { + const mockProviders = [ + createMockProvider({ + id: 'gemini', + name: 'Gemini', + isAvailable: true, + cooldownSeconds: null, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Should not have any time elements in Gemini button + const geminiButton = screen.getByRole('radio', { name: /gemini/i }); + expect(geminiButton.textContent).not.toMatch(/\d+s/); + expect(geminiButton.textContent).not.toMatch(/\d+m/); + }); + + it('should not display timer when cooldownSeconds is 0', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 0, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Should not display any countdown + const groqButton = screen.getByRole('radio', { name: /groq/i }); + expect(groqButton.textContent).not.toMatch(/\d+s/); + }); + }); + + // ========================================================================== + // Accessibility Tests + // ========================================================================== + + describe('accessibility', () => { + it('should have radiogroup role on container', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const radiogroup = screen.getByRole('radiogroup', { + name: /select llm provider/i, + }); + expect(radiogroup).toBeInTheDocument(); + }); + + it('should have radio role on each pill', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(3); // Auto + 2 providers + }); + + it('should support keyboard navigation with arrow keys', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: null, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + // Focus the first (Auto) button + const autoButton = screen.getByRole('radio', { name: /auto.*selected/i }); + autoButton.focus(); + + // Press ArrowRight to move to next option + await user.keyboard('{ArrowRight}'); + + expect(mockSelectProvider).toHaveBeenCalledWith('gemini'); + }); + + it('should support ArrowDown as alternative to ArrowRight', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: null, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto.*selected/i }); + autoButton.focus(); + + await user.keyboard('{ArrowDown}'); + + expect(mockSelectProvider).toHaveBeenCalledWith('gemini'); + }); + + it('should support ArrowLeft/ArrowUp to move backwards', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'gemini', + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const geminiButton = screen.getByRole('radio', { name: /gemini.*selected/i }); + geminiButton.focus(); + + // Press ArrowLeft to go back to Auto + await user.keyboard('{ArrowLeft}'); + + expect(mockSelectProvider).toHaveBeenCalledWith(null); + }); + + it('should support Home key to go to first option', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'groq', + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const groqButton = screen.getByRole('radio', { name: /groq.*selected/i }); + groqButton.focus(); + + await user.keyboard('{Home}'); + + // Should select Auto (first option, id = null) + expect(mockSelectProvider).toHaveBeenCalledWith(null); + }); + + it('should support End key to go to last option', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: null, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto.*selected/i }); + autoButton.focus(); + + await user.keyboard('{End}'); + + // Should select last option (groq) + expect(mockSelectProvider).toHaveBeenCalledWith('groq'); + }); + + it('should use roving tabindex pattern', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'gemini', + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + const geminiButton = screen.getByRole('radio', { name: /gemini.*selected/i }); + const groqButton = screen.getByRole('radio', { name: /groq/i }); + + // Only the selected option should have tabindex=0 + expect(geminiButton).toHaveAttribute('tabindex', '0'); + expect(autoButton).toHaveAttribute('tabindex', '-1'); + expect(groqButton).toHaveAttribute('tabindex', '-1'); + }); + + it('should wrap around when navigating past the end', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'gemini', + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const geminiButton = screen.getByRole('radio', { name: /gemini.*selected/i }); + geminiButton.focus(); + + // Press ArrowRight to wrap to Auto (first option) + await user.keyboard('{ArrowRight}'); + + expect(mockSelectProvider).toHaveBeenCalledWith(null); + }); + + it('should wrap around when navigating before the start', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: null, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto.*selected/i }); + autoButton.focus(); + + // Press ArrowLeft to wrap to last option (gemini) + await user.keyboard('{ArrowLeft}'); + + expect(mockSelectProvider).toHaveBeenCalledWith('gemini'); + }); + + it('should have proper aria-label for unavailable providers', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: null, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const groqButton = screen.getByRole('radio', { name: /groq.*unavailable/i }); + expect(groqButton).toBeInTheDocument(); + }); + + it('should have proper aria-label for providers in cooldown', () => { + const mockProviders = [ + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 45, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const groqButton = screen.getByRole('radio', { + name: /groq.*cooling down/i, + }); + expect(groqButton).toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Refresh Button Tests + // ========================================================================== + + describe('refresh button', () => { + it('should call refresh when refresh button is clicked', async () => { + const user = userEvent.setup(); + const mockRefresh = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + refresh: mockRefresh, + }) + ); + + render(); + + const refreshButton = screen.getByRole('button', { + name: /refresh provider status/i, + }); + await user.click(refreshButton); + + expect(mockRefresh).toHaveBeenCalledTimes(1); + }); + + it('should be accessible with proper aria-label', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const refreshButton = screen.getByRole('button', { + name: /refresh provider status/i, + }); + expect(refreshButton).toHaveAttribute('title', 'Refresh provider status'); + }); + + it('should call refresh in error state', async () => { + const user = userEvent.setup(); + const mockRefresh = vi.fn(); + + setupMock( + createMockUseProvidersReturn({ + providers: [], + error: 'Failed to load', + refresh: mockRefresh, + }) + ); + + render(); + + const retryButton = screen.getByRole('button', { + name: /retry loading providers/i, + }); + await user.click(retryButton); + + expect(mockRefresh).toHaveBeenCalledTimes(1); + }); + }); + + // ========================================================================== + // Compact Mode Tests + // ========================================================================== + + describe('compact mode', () => { + it('should render with smaller sizes when compact prop is true', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Check that compact classes are applied + const radiogroup = screen.getByRole('radiogroup'); + + // Container should have smaller gap + expect(radiogroup).toHaveClass('gap-1.5'); + }); + + it('should render smaller refresh button in compact mode', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const refreshButton = screen.getByRole('button', { + name: /refresh provider status/i, + }); + + // Compact refresh button should have smaller dimensions + expect(refreshButton).toHaveClass('h-6', 'w-6'); + }); + + it('should render normal size without compact prop', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const refreshButton = screen.getByRole('button', { + name: /refresh provider status/i, + }); + + // Normal refresh button should have larger dimensions + expect(refreshButton).toHaveClass('h-7', 'w-7'); + }); + + it('should render smaller skeleton in compact mode when loading', () => { + setupMock( + createMockUseProvidersReturn({ + isLoading: true, + providers: [], + }) + ); + + const { container } = render(); + + const skeletons = container.querySelectorAll('.animate-pulse'); + + // Check that skeletons have compact height + skeletons.forEach((skeleton) => { + expect(skeleton).toHaveClass('h-7'); + }); + }); + + it('should render smaller error state in compact mode', () => { + setupMock( + createMockUseProvidersReturn({ + providers: [], + error: 'Error message', + }) + ); + + render(); + + const alert = screen.getByRole('alert'); + expect(alert).toHaveClass('text-xs'); + }); + + it('should apply custom className', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const radiogroup = screen.getByRole('radiogroup'); + expect(radiogroup).toHaveClass('custom-class', 'mt-4'); + }); + }); + + // ========================================================================== + // Edge Cases Tests + // ========================================================================== + + describe('edge cases', () => { + it('should handle empty providers array', () => { + setupMock( + createMockUseProvidersReturn({ + providers: [], + isLoading: false, + error: null, + }) + ); + + render(); + + // Should still render radiogroup with just Auto option + const radiogroup = screen.getByRole('radiogroup'); + expect(radiogroup).toBeInTheDocument(); + + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(1); // Just Auto + }); + + it('should handle provider with very long name', () => { + const mockProviders = [ + createMockProvider({ + id: 'long-name-provider', + name: 'Very Long Provider Name That Might Overflow', + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + expect( + screen.getByText('Very Long Provider Name That Might Overflow') + ).toBeInTheDocument(); + }); + + it('should handle multiple providers with mixed availability', () => { + const mockProviders = [ + createMockProvider({ + id: 'gemini', + name: 'Gemini', + isAvailable: true, + }), + createMockProvider({ + id: 'groq', + name: 'Groq', + isAvailable: false, + cooldownSeconds: 30, + }), + createMockProvider({ + id: 'deepseek', + name: 'DeepSeek', + isAvailable: false, + cooldownSeconds: null, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + render(); + + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(4); // Auto + 3 providers + + // Each should render correctly + expect(screen.getByText('Gemini')).toBeInTheDocument(); + expect(screen.getByText('Groq')).toBeInTheDocument(); + expect(screen.getByText('DeepSeek')).toBeInTheDocument(); + expect(screen.getByText('30s')).toBeInTheDocument(); // Groq cooldown + }); + + it('should handle very long error message with truncation', () => { + const longError = + 'This is a very long error message that should be truncated to prevent layout issues in the UI when displaying error states'; + + setupMock( + createMockUseProvidersReturn({ + providers: [], + error: longError, + }) + ); + + render(); + + const errorSpan = screen.getByText(longError); + expect(errorSpan).toHaveClass('truncate'); + expect(errorSpan).toHaveAttribute('title', longError); + }); + + it('should maintain focus after keyboard navigation', async () => { + const user = userEvent.setup(); + const mockSelectProvider = vi.fn(); + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + createMockProvider({ id: 'groq', name: 'Groq' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: null, + selectProvider: mockSelectProvider, + }) + ); + + render(); + + const autoButton = screen.getByRole('radio', { name: /auto.*selected/i }); + autoButton.focus(); + + // Navigate to Gemini + await user.keyboard('{ArrowRight}'); + + // selectProvider should have been called + expect(mockSelectProvider).toHaveBeenCalledWith('gemini'); + }); + }); + + // ========================================================================== + // Integration Tests + // ========================================================================== + + describe('integration', () => { + it('should work correctly with a complete provider list', () => { + const mockProviders = [ + createMockProvider({ + id: 'gemini', + name: 'Gemini', + description: 'Google Gemini Pro', + isAvailable: true, + cooldownSeconds: null, + totalRequestsRemaining: 10, + }), + createMockProvider({ + id: 'groq', + name: 'Groq', + description: 'Groq LLM', + isAvailable: false, + cooldownSeconds: 45, + totalRequestsRemaining: 0, + }), + createMockProvider({ + id: 'deepseek', + name: 'DeepSeek', + description: 'DeepSeek Chat', + isAvailable: true, + cooldownSeconds: null, + totalRequestsRemaining: 5, + }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + selectedProvider: 'gemini', + }) + ); + + render(); + + // All providers should be rendered + expect(screen.getByText('Auto')).toBeInTheDocument(); + expect(screen.getByText('Gemini')).toBeInTheDocument(); + expect(screen.getByText('Groq')).toBeInTheDocument(); + expect(screen.getByText('DeepSeek')).toBeInTheDocument(); + + // Cooldown should be shown + expect(screen.getByText('45s')).toBeInTheDocument(); + + // Selection should be correct + const geminiButton = screen.getByRole('radio', { name: /gemini.*selected/i }); + expect(geminiButton).toHaveAttribute('aria-checked', 'true'); + }); + + it('should render all elements with correct structure', () => { + const mockProviders = [ + createMockProvider({ id: 'gemini', name: 'Gemini' }), + ]; + + setupMock( + createMockUseProvidersReturn({ + providers: mockProviders, + }) + ); + + const { container } = render(); + + // Check structure + const radiogroup = screen.getByRole('radiogroup'); + expect(radiogroup).toBeInTheDocument(); + + // Radio buttons should be inside the radiogroup + const radios = within(radiogroup).getAllByRole('radio'); + expect(radios).toHaveLength(2); + + // Refresh button should be present + const refreshButton = screen.getByRole('button', { + name: /refresh provider status/i, + }); + expect(refreshButton).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/components/chat/__tests__/source-card.test.tsx b/frontend/src/components/chat/__tests__/source-card.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..52ae8f9cfe17bfdec1a572be1b7d2f9025517290 --- /dev/null +++ b/frontend/src/components/chat/__tests__/source-card.test.tsx @@ -0,0 +1,805 @@ +/** + * Unit Tests for SourceCard Component + * + * Comprehensive test coverage for the source citation card component. + * Tests rendering, expand/collapse functionality, accessibility features, + * and edge cases for text truncation and special characters. + * + * @module components/chat/__tests__/source-card.test + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SourceCard } from '../source-card'; +import type { Source } from '@/types'; + +// ============================================================================ +// Mocks +// ============================================================================ + +/** + * Mock lucide-react icons for faster tests and to avoid lazy loading issues. + * Each icon is replaced with a simple span containing a test ID. + */ +vi.mock('lucide-react', () => ({ + FileText: ({ className }: { className?: string }) => ( + + ), + ChevronDown: ({ className }: { className?: string }) => ( + + ), + ChevronUp: ({ className }: { className?: string }) => ( + + ), +})); + +// ============================================================================ +// Test Fixtures +// ============================================================================ + +/** + * Create a mock source object for testing. + * + * @param overrides - Properties to override in the default source + * @returns Mock source object with sensible defaults + */ +function createMockSource(overrides: Partial = {}): Source { + return { + id: 'test-source-1', + headingPath: 'Chapter 1 > Section 2 > Subsection A', + page: 42, + text: 'This is the source text content from the pythermalcomfort documentation.', + score: 0.85, + ...overrides, + }; +} + +/** + * Long text fixture for testing truncation behavior. + * Contains more than 150 characters to trigger truncation. + */ +const LONG_TEXT = + 'This is a very long text that exceeds the default truncation length of 150 characters. ' + + 'It contains multiple sentences to test the word-boundary truncation behavior. ' + + 'The truncation should happen at a word boundary to avoid cutting words in the middle. ' + + 'This ensures a clean user experience when displaying source excerpts.'; + +/** + * Short text fixture for testing non-truncation behavior. + * Contains fewer than 150 characters. + */ +const SHORT_TEXT = 'This is short text that fits within the truncation limit.'; + +/** + * Text with special characters for edge case testing. + */ +const SPECIAL_CHARS_TEXT = + 'Temperature formula: T = (a + b) / 2 where a > 0 & b < 100. Use calc() for computation.'; + +// ============================================================================ +// Test Suite +// ============================================================================ + +describe('SourceCard', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + // ========================================================================== + // Rendering Tests + // ========================================================================== + + describe('rendering', () => { + it('should render heading path correctly', () => { + // Test that the heading path is displayed with proper hierarchy + const source = createMockSource({ + headingPath: 'Introduction > Thermal Comfort > PMV Model', + }); + + render(); + + expect( + screen.getByText('Introduction > Thermal Comfort > PMV Model') + ).toBeInTheDocument(); + }); + + it('should render page number badge', () => { + // Test that page number is shown in a badge format + const source = createMockSource({ page: 78 }); + + render(); + + // Should show "Page X" format + expect(screen.getByText('Page 78')).toBeInTheDocument(); + }); + + it('should render page badge with correct aria-label', () => { + // Test accessibility of page badge + const source = createMockSource({ page: 123 }); + + render(); + + const pageBadge = screen.getByLabelText('Page 123'); + expect(pageBadge).toBeInTheDocument(); + }); + + it('should render source text (truncated when long)', () => { + // Test that long text is truncated by default + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + // Should show truncated text with ellipsis + const textElement = screen.getByText(/This is a very long text/); + expect(textElement).toBeInTheDocument(); + // Full text should not be visible + expect(screen.queryByText(LONG_TEXT)).not.toBeInTheDocument(); + }); + + it('should render full text when short enough', () => { + // Test that short text is not truncated + const source = createMockSource({ text: SHORT_TEXT }); + + render(); + + expect(screen.getByText(SHORT_TEXT)).toBeInTheDocument(); + }); + + it('should render score badge when showScore is true', () => { + // Test score badge rendering with showScore prop + const source = createMockSource({ score: 0.85 }); + + render(); + + // Score should be displayed as percentage + expect(screen.getByText('85%')).toBeInTheDocument(); + }); + + it('should render score badge with correct aria-label', () => { + // Test accessibility of score badge + const source = createMockSource({ score: 0.92 }); + + render(); + + expect( + screen.getByLabelText('Relevance score: 92 percent') + ).toBeInTheDocument(); + }); + + it('should not render score badge when showScore is false', () => { + // Test that score is hidden by default + const source = createMockSource({ score: 0.75 }); + + render(); + + expect(screen.queryByText('75%')).not.toBeInTheDocument(); + }); + + it('should not render score badge when showScore is true but score is undefined', () => { + // Test handling of undefined score + const source = createMockSource({ score: undefined }); + + render(); + + // No percentage should be rendered + expect(screen.queryByText(/%/)).not.toBeInTheDocument(); + }); + + it('should not render score badge by default (showScore defaults to false)', () => { + // Test default behavior + const source = createMockSource({ score: 0.88 }); + + render(); + + expect(screen.queryByText('88%')).not.toBeInTheDocument(); + }); + + it('should render file icon in header', () => { + // Test that FileText icon is present + const source = createMockSource(); + + render(); + + expect(screen.getByTestId('file-icon')).toBeInTheDocument(); + }); + + it('should apply high score color styling (>= 80%)', () => { + // Test that high scores get success color + const source = createMockSource({ score: 0.85 }); + + render(); + + const scoreBadge = screen.getByLabelText('Relevance score: 85 percent'); + expect(scoreBadge.className).toMatch(/success/); + }); + + it('should apply medium score color styling (60-79%)', () => { + // Test that medium scores get primary color + const source = createMockSource({ score: 0.65 }); + + render(); + + const scoreBadge = screen.getByLabelText('Relevance score: 65 percent'); + expect(scoreBadge.className).toMatch(/primary/); + }); + + it('should apply low score color styling (< 60%)', () => { + // Test that low scores get muted color + const source = createMockSource({ score: 0.45 }); + + render(); + + const scoreBadge = screen.getByLabelText('Relevance score: 45 percent'); + expect(scoreBadge.className).toMatch(/foreground-muted|background-tertiary/); + }); + }); + + // ========================================================================== + // Expand/Collapse Tests + // ========================================================================== + + describe('expand/collapse', () => { + it('should start collapsed by default', () => { + // Test default collapsed state + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + // Should show "Show more" button indicating collapsed state + expect(screen.getByText('Show more')).toBeInTheDocument(); + }); + + it('should start expanded when defaultExpanded is true', () => { + // Test defaultExpanded prop + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + // Should show "Show less" button indicating expanded state + expect(screen.getByText('Show less')).toBeInTheDocument(); + // Full text should be visible + expect(screen.getByText(LONG_TEXT)).toBeInTheDocument(); + }); + + it('should toggle expand/collapse on button click', async () => { + // Test click interaction + const user = userEvent.setup(); + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + // Initially collapsed + expect(screen.getByText('Show more')).toBeInTheDocument(); + + // Click to expand + await user.click(screen.getByText('Show more')); + + // Should now be expanded + expect(screen.getByText('Show less')).toBeInTheDocument(); + expect(screen.getByText(LONG_TEXT)).toBeInTheDocument(); + + // Click to collapse + await user.click(screen.getByText('Show less')); + + // Should be collapsed again + expect(screen.getByText('Show more')).toBeInTheDocument(); + }); + + it('should show full text when expanded', () => { + // Test that full text is visible when expanded + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + expect(screen.getByText(LONG_TEXT)).toBeInTheDocument(); + }); + + it('should show truncated text when collapsed', () => { + // Test truncation in collapsed state + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + // Full text should not be present + expect(screen.queryByText(LONG_TEXT)).not.toBeInTheDocument(); + // Truncated version should be present (with ellipsis) + expect(screen.getByText(/\.\.\./)).toBeInTheDocument(); + }); + + it('should change button text from "Show more" to "Show less"', async () => { + // Test button label changes + const user = userEvent.setup(); + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + expect(screen.getByText('Show more')).toBeInTheDocument(); + expect(screen.queryByText('Show less')).not.toBeInTheDocument(); + + await user.click(screen.getByText('Show more')); + + expect(screen.queryByText('Show more')).not.toBeInTheDocument(); + expect(screen.getByText('Show less')).toBeInTheDocument(); + }); + + it('should show ChevronDown icon when collapsed', () => { + // Test chevron icon in collapsed state + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + expect(screen.getByTestId('chevron-down-icon')).toBeInTheDocument(); + }); + + it('should show ChevronUp icon when expanded', () => { + // Test chevron icon in expanded state + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + expect(screen.getByTestId('chevron-up-icon')).toBeInTheDocument(); + }); + + it('should not show expand button for short text', () => { + // Test that button is hidden when not needed + const source = createMockSource({ text: SHORT_TEXT }); + + render(); + + expect(screen.queryByText('Show more')).not.toBeInTheDocument(); + expect(screen.queryByText('Show less')).not.toBeInTheDocument(); + }); + + it('should respect custom truncateLength prop', () => { + // Test custom truncation length + const source = createMockSource({ text: SHORT_TEXT }); + + // Set truncateLength shorter than the text + render(); + + // Should now show expand button since text exceeds custom limit + expect(screen.getByText('Show more')).toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Accessibility Tests + // ========================================================================== + + describe('accessibility', () => { + it('should have correct aria-expanded attribute when collapsed', () => { + // Test aria-expanded state for collapsed + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + }); + + it('should have correct aria-expanded attribute when expanded', () => { + // Test aria-expanded state for expanded + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should update aria-expanded attribute on toggle', async () => { + // Test aria-expanded updates dynamically + const user = userEvent.setup(); + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + + await user.click(button); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should have aria-controls linking to text region', () => { + // Test aria-controls relationship + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + const controlsId = button.getAttribute('aria-controls'); + + expect(controlsId).toBe('source-text-region'); + expect(document.getElementById('source-text-region')).toBeInTheDocument(); + }); + + it('should respond to Enter key for toggle', async () => { + // Test keyboard navigation with Enter + const user = userEvent.setup(); + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + button.focus(); + + // Press Enter to expand + await user.keyboard('{Enter}'); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should respond to Space key for toggle', async () => { + // Test keyboard navigation with Space + const user = userEvent.setup(); + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + button.focus(); + + // Press Space to expand + await user.keyboard(' '); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should have proper semantic structure with article element', () => { + // Test that card uses article element for semantic grouping + const source = createMockSource(); + + render(); + + const article = screen.getByRole('article'); + expect(article).toBeInTheDocument(); + }); + + it('should have header element for card header content', () => { + // Test semantic header structure + const source = createMockSource(); + + const { container } = render(); + + const header = container.querySelector('header'); + expect(header).toBeInTheDocument(); + }); + + it('should have descriptive aria-label on article element', () => { + // Test article has accessible name + const source = createMockSource({ + headingPath: 'Thermal Comfort > PMV', + page: 15, + }); + + render(); + + const article = screen.getByRole('article'); + expect(article).toHaveAttribute( + 'aria-label', + 'Source from Thermal Comfort > PMV, page 15' + ); + }); + + it('should be keyboard focusable on expand button', () => { + // Test focus is possible + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + button.focus(); + + expect(document.activeElement).toBe(button); + }); + + it('should have visible focus styles', () => { + // Test that focus ring classes are present + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + const button = screen.getByRole('button'); + expect(button.className).toMatch(/focus-visible:ring/); + }); + }); + + // ========================================================================== + // Edge Cases Tests + // ========================================================================== + + describe('edge cases', () => { + it('should handle very long text correctly', () => { + // Test with extremely long text + const veryLongText = 'A'.repeat(1000); + const source = createMockSource({ text: veryLongText }); + + render(); + + // Should truncate and show expand button + expect(screen.getByText('Show more')).toBeInTheDocument(); + // Full text should not be visible + expect(screen.queryByText(veryLongText)).not.toBeInTheDocument(); + }); + + it('should handle short text (no truncation needed)', () => { + // Test that short text shows in full without button + const source = createMockSource({ text: 'Short.' }); + + render(); + + expect(screen.getByText('Short.')).toBeInTheDocument(); + expect(screen.queryByText('Show more')).not.toBeInTheDocument(); + }); + + it('should handle text at exact truncation boundary', () => { + // Test text that is exactly at the truncation limit + const exactLengthText = 'X'.repeat(150); + const source = createMockSource({ text: exactLengthText }); + + render(); + + // Should show full text since it's exactly at the limit + expect(screen.getByText(exactLengthText)).toBeInTheDocument(); + expect(screen.queryByText('Show more')).not.toBeInTheDocument(); + }); + + it('should handle text just over truncation boundary', () => { + // Test text that is just over the truncation limit + const overLimitText = 'X'.repeat(151); + const source = createMockSource({ text: overLimitText }); + + render(); + + // Should be truncated + expect(screen.queryByText(overLimitText)).not.toBeInTheDocument(); + expect(screen.getByText('Show more')).toBeInTheDocument(); + }); + + it('should handle special characters in heading path', () => { + // Test special characters in heading path + const source = createMockSource({ + headingPath: 'Chapter <1> & Section "2" > Sub\'section', + }); + + render(); + + expect( + screen.getByText('Chapter <1> & Section "2" > Sub\'section') + ).toBeInTheDocument(); + }); + + it('should handle special characters in text content', () => { + // Test special characters in text + const source = createMockSource({ text: SPECIAL_CHARS_TEXT }); + + render(); + + expect(screen.getByText(SPECIAL_CHARS_TEXT)).toBeInTheDocument(); + }); + + it('should handle empty heading path', () => { + // Test empty heading path edge case + const source = createMockSource({ headingPath: '' }); + + render(); + + // Should still render without crashing + expect(screen.getByRole('article')).toBeInTheDocument(); + }); + + it('should handle zero score correctly', () => { + // Test zero score display + const source = createMockSource({ score: 0 }); + + render(); + + expect(screen.getByText('0%')).toBeInTheDocument(); + }); + + it('should handle perfect score (1.0) correctly', () => { + // Test maximum score display + const source = createMockSource({ score: 1 }); + + render(); + + expect(screen.getByText('100%')).toBeInTheDocument(); + }); + + it('should round score to nearest integer', () => { + // Test score rounding + const source = createMockSource({ score: 0.876 }); + + render(); + + expect(screen.getByText('88%')).toBeInTheDocument(); + }); + + it('should handle page number 0', () => { + // Test edge case of page 0 + const source = createMockSource({ page: 0 }); + + render(); + + expect(screen.getByText('Page 0')).toBeInTheDocument(); + }); + + it('should handle very large page numbers', () => { + // Test large page number display + const source = createMockSource({ page: 99999 }); + + render(); + + expect(screen.getByText('Page 99999')).toBeInTheDocument(); + }); + + it('should apply custom className to article element', () => { + // Test className prop is passed through + const source = createMockSource(); + + render(); + + const article = screen.getByRole('article'); + expect(article).toHaveClass('custom-class', 'mt-4'); + }); + + it('should forward ref to article element', () => { + // Test ref forwarding + const source = createMockSource(); + const ref = vi.fn(); + + render(); + + expect(ref).toHaveBeenCalled(); + expect(ref.mock.calls[0][0]).toBeInstanceOf(HTMLElement); + }); + + it('should preserve whitespace in text content', () => { + // Test that whitespace is preserved + const textWithSpaces = 'Line 1\n\nLine 2\n Indented line'; + const source = createMockSource({ text: textWithSpaces }); + + const { container } = render( + + ); + + // Check whitespace-pre-wrap class is applied + const textElement = container.querySelector('p'); + expect(textElement).toHaveClass('whitespace-pre-wrap'); + }); + + it('should handle Unicode characters correctly', () => { + // Test Unicode text content + const unicodeText = + 'Temperature: 25\u00B0C, PMV: \u00B10.5, Humidity: 50%'; + const source = createMockSource({ text: unicodeText }); + + render(); + + expect(screen.getByText(unicodeText)).toBeInTheDocument(); + }); + + it('should handle multi-byte characters in heading path', () => { + // Test international characters + const source = createMockSource({ + headingPath: '\u65E5\u672C\u8A9E > \u7B2C1\u7AE0', + }); + + render(); + + expect( + screen.getByText('\u65E5\u672C\u8A9E > \u7B2C1\u7AE0') + ).toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Props Tests + // ========================================================================== + + describe('props', () => { + it('should use default truncateLength of 150', () => { + // Test default truncation behavior + const text149 = 'A'.repeat(149); + const text151 = 'A'.repeat(151); + + const source149 = createMockSource({ text: text149 }); + const source151 = createMockSource({ text: text151 }); + + const { rerender } = render(); + expect(screen.queryByText('Show more')).not.toBeInTheDocument(); + + rerender(); + expect(screen.getByText('Show more')).toBeInTheDocument(); + }); + + it('should accept custom truncateLength', () => { + // Test custom truncation length + const text = 'A'.repeat(100); + const source = createMockSource({ text }); + + render(); + + // Should be truncated with custom length + expect(screen.getByText('Show more')).toBeInTheDocument(); + }); + + it('should pass additional HTML attributes to article', () => { + // Test that other props are spread to article + const source = createMockSource(); + + render( + + ); + + const article = screen.getByTestId('custom-card'); + expect(article).toHaveAttribute('title', 'Test'); + }); + }); + + // ========================================================================== + // Integration Tests + // ========================================================================== + + describe('integration', () => { + it('should render complete source card with all elements', () => { + // Test full rendering with all features + const source = createMockSource({ + id: 'complete-source', + headingPath: 'Documentation > API Reference > Functions', + page: 42, + text: LONG_TEXT, + score: 0.92, + }); + + render(); + + // All elements should be present + expect( + screen.getByText('Documentation > API Reference > Functions') + ).toBeInTheDocument(); + expect(screen.getByText('Page 42')).toBeInTheDocument(); + expect(screen.getByText('92%')).toBeInTheDocument(); + expect(screen.getByText(LONG_TEXT)).toBeInTheDocument(); + expect(screen.getByText('Show less')).toBeInTheDocument(); + expect(screen.getByTestId('file-icon')).toBeInTheDocument(); + expect(screen.getByTestId('chevron-up-icon')).toBeInTheDocument(); + }); + + it('should maintain state across multiple expand/collapse cycles', async () => { + // Test state persistence + const user = userEvent.setup(); + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + // Initial state + expect(screen.getByText('Show more')).toBeInTheDocument(); + + // Cycle through states + for (let i = 0; i < 3; i++) { + await user.click(screen.getByRole('button')); + expect(screen.getByText('Show less')).toBeInTheDocument(); + + await user.click(screen.getByRole('button')); + expect(screen.getByText('Show more')).toBeInTheDocument(); + } + }); + + it('should work with fireEvent as alternative to userEvent', () => { + // Test with fireEvent for synchronous testing + const source = createMockSource({ text: LONG_TEXT }); + + render(); + + expect(screen.getByText('Show more')).toBeInTheDocument(); + + fireEvent.click(screen.getByRole('button')); + + expect(screen.getByText('Show less')).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/components/chat/__tests__/source-citations.test.tsx b/frontend/src/components/chat/__tests__/source-citations.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9fc65d0c224e9ae5a21ef9c041a629dff816918e --- /dev/null +++ b/frontend/src/components/chat/__tests__/source-citations.test.tsx @@ -0,0 +1,852 @@ +/** + * Unit Tests for SourceCitations Component + * + * Comprehensive test coverage for the collapsible source citations container. + * Tests rendering states, expand/collapse animations, accessibility features, + * and integration with SourceCard components. + * + * @module components/chat/__tests__/source-citations.test + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SourceCitations } from '../source-citations'; +import type { Source } from '@/types'; + +// ============================================================================ +// Test Fixtures +// ============================================================================ + +/** + * Create a mock source object for testing. + * + * @param overrides - Properties to override in the default source + * @returns Mock source object with sensible defaults + */ +function createMockSource(overrides: Partial = {}): Source { + return { + id: `source-${Math.random().toString(36).substring(7)}`, + headingPath: 'Chapter 1 > Section 2', + page: 10, + text: 'Sample source text for testing purposes.', + score: 0.8, + ...overrides, + }; +} + +/** + * Create an array of mock sources for testing. + * + * @param count - Number of sources to create + * @returns Array of mock source objects + */ +function createMockSources(count: number): Source[] { + return Array.from({ length: count }, (_, index) => + createMockSource({ + id: `source-${index + 1}`, + headingPath: `Chapter ${index + 1} > Section ${index + 1}`, + page: (index + 1) * 10, + text: `This is the text content for source ${index + 1}.`, + score: 0.9 - index * 0.1, + }) + ); +} + +// ============================================================================ +// Test Suite +// ============================================================================ + +describe('SourceCitations', () => { + // ========================================================================== + // Rendering Tests + // ========================================================================== + + describe('rendering', () => { + it('should render correct source count', () => { + // Test that source count is displayed correctly + const sources = createMockSources(5); + + render(); + + expect(screen.getByText('5 Sources')).toBeInTheDocument(); + }); + + it('should render singular "Source" when count is 1', () => { + // Test singular grammar + const sources = createMockSources(1); + + render(); + + expect(screen.getByText('1 Source')).toBeInTheDocument(); + }); + + it('should render plural "Sources" when count > 1', () => { + // Test plural grammar + const sources = createMockSources(2); + + render(); + + expect(screen.getByText('2 Sources')).toBeInTheDocument(); + }); + + it('should render plural "Sources" for large count', () => { + // Test plural grammar with larger numbers + const sources = createMockSources(10); + + render(); + + expect(screen.getByText('10 Sources')).toBeInTheDocument(); + }); + + it('should not render when sources array is empty', () => { + // Test that component returns null for empty sources + const { container } = render(); + + // Container should be empty (only the root div from render) + expect(container.firstChild).toBeNull(); + }); + + it('should not render when sources is an empty array', () => { + // Explicitly test empty array + const sources: Source[] = []; + + const { container } = render(); + + expect(container.firstChild).toBeNull(); + }); + + it('should render all SourceCard components when expanded', () => { + // Test that all source cards are rendered + const sources = createMockSources(3); + + render(); + + // Each source should have its heading path rendered + expect(screen.getByText('Chapter 1 > Section 1')).toBeInTheDocument(); + expect(screen.getByText('Chapter 2 > Section 2')).toBeInTheDocument(); + expect(screen.getByText('Chapter 3 > Section 3')).toBeInTheDocument(); + }); + + it('should render toggle button in header', () => { + // Test toggle button presence + const sources = createMockSources(1); + + render(); + + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('should render source count in toggle button', () => { + // Test source count in button + const sources = createMockSources(3); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveTextContent('3 Sources'); + }); + }); + + // ========================================================================== + // Expand/Collapse Tests + // ========================================================================== + + describe('expand/collapse', () => { + it('should start collapsed by default', () => { + // Test default collapsed state + const sources = createMockSources(3); + + const { container } = render(); + + // Content region should be hidden (use container query since aria-hidden elements are not accessible) + const region = container.querySelector('[role="region"]'); + expect(region).toHaveAttribute('aria-hidden', 'true'); + }); + + it('should start expanded when defaultExpanded is true', () => { + // Test defaultExpanded prop + const sources = createMockSources(3); + + render(); + + // Content region should be visible (accessible when expanded) + const region = screen.getByRole('region'); + expect(region).toHaveAttribute('aria-hidden', 'false'); + }); + + it('should toggle on button click', async () => { + // Test click interaction + const user = userEvent.setup(); + const sources = createMockSources(2); + + const { container } = render(); + + const button = screen.getByRole('button'); + const region = container.querySelector('[role="region"]'); + + // Initially collapsed + expect(region).toHaveAttribute('aria-hidden', 'true'); + + // Click to expand + await user.click(button); + expect(region).toHaveAttribute('aria-hidden', 'false'); + + // Click to collapse + await user.click(button); + expect(region).toHaveAttribute('aria-hidden', 'true'); + }); + + it('should apply rotation class to chevron when expanded', () => { + // Test chevron rotation class - check the button contains rotated element + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + // The rotated element should have rotate-180 class when expanded + const rotatedElement = button.querySelector('.rotate-180'); + expect(rotatedElement).toBeInTheDocument(); + }); + + it('should not have rotation class when collapsed', () => { + // Test chevron not rotated when collapsed + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + // Should not have rotate-180 when collapsed + const rotatedElement = button.querySelector('.rotate-180'); + expect(rotatedElement).toBeNull(); + }); + + it('should apply grid-rows-[0fr] when collapsed', () => { + // Test CSS grid animation class for collapsed state + const sources = createMockSources(1); + + const { container } = render(); + + const region = container.querySelector('[role="region"]'); + expect(region?.className).toMatch(/grid-rows-\[0fr\]/); + }); + + it('should apply grid-rows-[1fr] when expanded', () => { + // Test CSS grid animation class for expanded state + const sources = createMockSources(1); + + render(); + + const region = screen.getByRole('region'); + expect(region.className).toMatch(/grid-rows-\[1fr\]/); + }); + + it('should hide content when collapsed', () => { + // Test that content is hidden in collapsed state + const sources = createMockSources(1); + + const { container } = render(); + + const region = container.querySelector('[role="region"]'); + expect(region).toHaveAttribute('aria-hidden', 'true'); + }); + + it('should show content when expanded', () => { + // Test that content is visible in expanded state + const sources = createMockSources(1); + + render(); + + const region = screen.getByRole('region'); + expect(region).toHaveAttribute('aria-hidden', 'false'); + // Source text should be visible + expect( + screen.getByText('This is the text content for source 1.') + ).toBeInTheDocument(); + }); + }); + + // ========================================================================== + // Accessibility Tests + // ========================================================================== + + describe('accessibility', () => { + it('should have correct aria-expanded attribute when collapsed', () => { + // Test aria-expanded in collapsed state + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + }); + + it('should have correct aria-expanded attribute when expanded', () => { + // Test aria-expanded in expanded state + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should update aria-expanded on toggle', async () => { + // Test dynamic aria-expanded updates + const user = userEvent.setup(); + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + + await user.click(button); + expect(button).toHaveAttribute('aria-expanded', 'true'); + + await user.click(button); + expect(button).toHaveAttribute('aria-expanded', 'false'); + }); + + it('should have aria-controls linking to content region', () => { + // Test aria-controls relationship + const sources = createMockSources(1); + + const { container } = render(); + + const button = screen.getByRole('button'); + const region = container.querySelector('[role="region"]'); + + const controlsId = button.getAttribute('aria-controls'); + expect(controlsId).toBeTruthy(); + expect(region).toHaveAttribute('id', controlsId); + }); + + it('should have content region with role="region"', () => { + // Test region role on content + const sources = createMockSources(1); + + const { container } = render(); + + const region = container.querySelector('[role="region"]'); + expect(region).toBeInTheDocument(); + }); + + it('should have region with aria-labelledby pointing to button', () => { + // Test aria-labelledby relationship + const sources = createMockSources(1); + + const { container } = render(); + + const button = screen.getByRole('button'); + const region = container.querySelector('[role="region"]'); + + const buttonId = button.getAttribute('id'); + expect(buttonId).toBeTruthy(); + expect(region).toHaveAttribute('aria-labelledby', buttonId); + }); + + it('should have descriptive aria-label on toggle button', () => { + // Test button has accessible name + const sources = createMockSources(3); + + render(); + + const button = screen.getByRole('button'); + const ariaLabel = button.getAttribute('aria-label'); + + expect(ariaLabel).toMatch(/3 Sources/); + expect(ariaLabel).toMatch(/Expand/); + }); + + it('should update aria-label when expanded', () => { + // Test aria-label changes based on state + const sources = createMockSources(3); + + render(); + + const button = screen.getByRole('button'); + const ariaLabel = button.getAttribute('aria-label'); + + expect(ariaLabel).toMatch(/3 Sources/); + expect(ariaLabel).toMatch(/Collapse/); + }); + + it('should respond to Enter key for toggle', async () => { + // Test keyboard navigation with Enter + const user = userEvent.setup(); + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + button.focus(); + + await user.keyboard('{Enter}'); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should respond to Space key for toggle', async () => { + // Test keyboard navigation with Space + const user = userEvent.setup(); + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + button.focus(); + + await user.keyboard(' '); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should be keyboard focusable on toggle button', () => { + // Test focus is possible on button + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + button.focus(); + + expect(document.activeElement).toBe(button); + }); + + it('should have visible focus styles', () => { + // Test focus ring classes are present + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button.className).toMatch(/focus-visible:ring/); + }); + + it('should have unique IDs for multiple instances', () => { + // Test that multiple instances have unique IDs (using useId) + const sources1 = createMockSources(1); + const sources2 = createMockSources(2); + + const { container } = render( + <> + + + + ); + + const buttons = screen.getAllByRole('button'); + const regions = container.querySelectorAll('[role="region"]'); + + // IDs should be unique + expect(buttons[0].id).not.toBe(buttons[1].id); + expect(regions[0].id).not.toBe(regions[1].id); + }); + }); + + // ========================================================================== + // Props Tests + // ========================================================================== + + describe('props', () => { + it('should pass showScores prop to SourceCards', () => { + // Test that showScores propagates to child components + const sources = createMockSources(1); + sources[0].score = 0.95; + + render(); + + // Score should be visible on the source card + expect(screen.getByText('95%')).toBeInTheDocument(); + }); + + it('should not show scores when showScores is false', () => { + // Test default behavior (showScores = false) + const sources = createMockSources(1); + sources[0].score = 0.95; + + render( + + ); + + // Score should not be visible + expect(screen.queryByText('95%')).not.toBeInTheDocument(); + }); + + it('should not show scores by default', () => { + // Test that showScores defaults to false + const sources = createMockSources(1); + sources[0].score = 0.85; + + render(); + + expect(screen.queryByText('85%')).not.toBeInTheDocument(); + }); + + it('should apply className prop to container', () => { + // Test className is applied + const sources = createMockSources(1); + + const { container } = render( + + ); + + const mainContainer = container.firstChild as HTMLElement; + expect(mainContainer).toHaveClass('custom-class', 'mt-8'); + }); + + it('should apply defaultExpanded prop correctly', () => { + // Test defaultExpanded initial state + // Note: defaultExpanded only sets the initial state, it does not update when changed + // We need to test two separate renders + const sources = createMockSources(1); + + // Test collapsed by default + const { unmount } = render(); + let button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + unmount(); + + // Test expanded by default + render(); + button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should pass additional HTML attributes to container', () => { + // Test that other props spread to container div + const sources = createMockSources(1); + + render( + + ); + + const container = screen.getByTestId('citations-container'); + expect(container).toHaveAttribute('title', 'Source citations'); + }); + }); + + // ========================================================================== + // Edge Cases Tests + // ========================================================================== + + describe('edge cases', () => { + it('should handle single source correctly', () => { + // Test with exactly one source + const sources = createMockSources(1); + + render(); + + expect(screen.getByText('1 Source')).toBeInTheDocument(); + expect(screen.getByText('Chapter 1 > Section 1')).toBeInTheDocument(); + }); + + it('should handle many sources correctly', () => { + // Test with large number of sources + const sources = createMockSources(20); + + render(); + + expect(screen.getByText('20 Sources')).toBeInTheDocument(); + }); + + it('should preserve source order', () => { + // Test that sources are rendered in provided order + const sources = [ + createMockSource({ id: '1', headingPath: 'First Source' }), + createMockSource({ id: '2', headingPath: 'Second Source' }), + createMockSource({ id: '3', headingPath: 'Third Source' }), + ]; + + render(); + + const articles = screen.getAllByRole('article'); + + // Check order is preserved + expect(articles[0]).toHaveAttribute( + 'aria-label', + expect.stringContaining('First Source') + ); + expect(articles[1]).toHaveAttribute( + 'aria-label', + expect.stringContaining('Second Source') + ); + expect(articles[2]).toHaveAttribute( + 'aria-label', + expect.stringContaining('Third Source') + ); + }); + + it('should handle sources with missing optional fields', () => { + // Test sources without score + const sources = [ + createMockSource({ id: '1', score: undefined }), + createMockSource({ id: '2', score: undefined }), + ]; + + render( + + ); + + // Should render without crashing + expect(screen.getByText('2 Sources')).toBeInTheDocument(); + // No percentages should be visible + expect(screen.queryByText(/%/)).not.toBeInTheDocument(); + }); + + it('should handle sources with special characters', () => { + // Test sources with special characters in text + const sources = [ + createMockSource({ + id: '1', + headingPath: 'Chapter <1> & Section "2"', + text: 'Formula: T = (a + b) / 2 where a > 0 & b < 100', + }), + ]; + + render(); + + expect(screen.getByText('Chapter <1> & Section "2"')).toBeInTheDocument(); + }); + + it('should apply staggered animation delays to source cards', () => { + // Test animation delay styles + const sources = createMockSources(3); + + render(); + + const articles = screen.getAllByRole('article'); + + // Check animation delays are applied (0ms, 50ms, 100ms) + expect(articles[0]).toHaveStyle({ animationDelay: '0ms' }); + expect(articles[1]).toHaveStyle({ animationDelay: '50ms' }); + expect(articles[2]).toHaveStyle({ animationDelay: '100ms' }); + }); + + it('should cap animation delay at 200ms', () => { + // Test animation delay cap for many sources + const sources = createMockSources(10); + + render(); + + const articles = screen.getAllByRole('article'); + + // Fifth and later sources should have 200ms delay (index 4+ = 200ms) + expect(articles[4]).toHaveStyle({ animationDelay: '200ms' }); + expect(articles[9]).toHaveStyle({ animationDelay: '200ms' }); + }); + + it('should not apply animation styles when collapsed', () => { + // Test that animation is disabled when collapsed + const sources = createMockSources(3); + + const { container } = render(); + + // When collapsed, articles are inside aria-hidden region so we need to query directly + const articles = container.querySelectorAll('article'); + + // Animation should be none when collapsed + articles.forEach((article) => { + expect(article.className).toMatch(/animate-none/); + }); + }); + + it('should handle undefined sources gracefully', () => { + // Test with undefined-like values (empty array) + // Note: TypeScript prevents passing undefined, but empty array is allowed + const { container } = render(); + + expect(container.firstChild).toBeNull(); + }); + + it('should use unique keys for source cards', () => { + // Test that source IDs are used as keys (no key warnings in console) + const sources = createMockSources(3); + + // This should not produce any React key warnings + render(); + + expect(screen.getAllByRole('article')).toHaveLength(3); + }); + }); + + // ========================================================================== + // Integration Tests + // ========================================================================== + + describe('integration', () => { + it('should render complete component with all features enabled', () => { + // Test full rendering with all props + const sources = createMockSources(3); + + render( + + ); + + // Count label + expect(screen.getByText('3 Sources')).toBeInTheDocument(); + + // All source cards + expect(screen.getAllByRole('article')).toHaveLength(3); + + // Scores should be visible + expect(screen.getByText('90%')).toBeInTheDocument(); + }); + + it('should maintain state across multiple expand/collapse cycles', async () => { + // Test state persistence + const user = userEvent.setup(); + const sources = createMockSources(2); + + render(); + + const button = screen.getByRole('button'); + + // Cycle through states multiple times + for (let i = 0; i < 3; i++) { + expect(button).toHaveAttribute('aria-expanded', 'false'); + + await user.click(button); + expect(button).toHaveAttribute('aria-expanded', 'true'); + + await user.click(button); + expect(button).toHaveAttribute('aria-expanded', 'false'); + } + }); + + it('should work with fireEvent as alternative to userEvent', () => { + // Test with synchronous fireEvent + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + + fireEvent.click(button); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + }); + + it('should correctly toggle aria-hidden on content region', async () => { + // Test aria-hidden updates on content + const user = userEvent.setup(); + const sources = createMockSources(1); + + const { container } = render(); + + const region = container.querySelector('[role="region"]'); + + expect(region).toHaveAttribute('aria-hidden', 'true'); + + await user.click(screen.getByRole('button')); + expect(region).toHaveAttribute('aria-hidden', 'false'); + + await user.click(screen.getByRole('button')); + expect(region).toHaveAttribute('aria-hidden', 'true'); + }); + + it('should render source cards that are individually expandable', async () => { + // Test that nested SourceCards work correctly + const sources = [ + createMockSource({ + id: '1', + text: 'A'.repeat(200), // Long enough to need truncation + }), + ]; + + render(); + + // SourceCard should have its own expand button + const buttons = screen.getAllByRole('button'); + // One for SourceCitations, one for SourceCard + expect(buttons.length).toBeGreaterThanOrEqual(2); + }); + + it('should handle rapid toggle clicks', async () => { + // Test rapid clicking doesn't break state + const user = userEvent.setup(); + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + + // Rapid clicks + await user.click(button); + await user.click(button); + await user.click(button); + await user.click(button); + + // Should be in stable state (4 clicks = collapsed) + expect(button).toHaveAttribute('aria-expanded', 'false'); + }); + }); + + // ========================================================================== + // Visual Tests + // ========================================================================== + + describe('visual styling', () => { + it('should have top border separator', () => { + // Test border styling + const sources = createMockSources(1); + + const { container } = render(); + + const mainContainer = container.firstChild as HTMLElement; + expect(mainContainer.className).toMatch(/border-t/); + }); + + it('should have proper padding and margin', () => { + // Test spacing classes + const sources = createMockSources(1); + + const { container } = render(); + + const mainContainer = container.firstChild as HTMLElement; + expect(mainContainer.className).toMatch(/mt-4/); + expect(mainContainer.className).toMatch(/pt-4/); + }); + + it('should have hover styles on toggle button', () => { + // Test hover classes are present + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button.className).toMatch(/hover:/); + }); + + it('should have transition classes for smooth animations', () => { + // Test transition classes are present + const sources = createMockSources(1); + + const { container } = render(); + + const region = container.querySelector('[role="region"]'); + expect(region?.className).toMatch(/transition/); + }); + + it('should have group class for coordinated hover states', () => { + // Test group class on button for child hover coordination + const sources = createMockSources(1); + + render(); + + const button = screen.getByRole('button'); + expect(button.className).toMatch(/group/); + }); + }); +}); diff --git a/frontend/src/components/chat/chat-container.tsx b/frontend/src/components/chat/chat-container.tsx new file mode 100644 index 0000000000000000000000000000000000000000..eacd8fe15acba7cefe21d78ef8fd9e2eded51f38 --- /dev/null +++ b/frontend/src/components/chat/chat-container.tsx @@ -0,0 +1,905 @@ +/** + * ChatContainer Component + * + * The main orchestrating component for the chat interface following a + * true Claude-style minimal design philosophy. This component achieves + * a seamless, boundary-free aesthetic where the chat interface feels + * like a natural extension of the page itself rather than a contained widget. + * + * @module components/chat/chat-container + * @since 1.0.0 + * + * @design + * ## Design Philosophy + * + * This component follows Claude's true minimal design principles: + * - **No visible boundaries**: No borders, shadows, or rounded corners on the main container + * - **Seamless integration**: Chat interface blends directly into the page background + * - **Minimal chrome**: Headers and inputs use subtle separation, not heavy styling + * - **Content-first**: Messages flow naturally on the page without visual containment + * - **Restrained color**: Purple accent color used sparingly for emphasis + * - **Flat design**: Zero shadows for a completely uncluttered feel + * + * @example + * // Basic usage in a page + * + * + * @example + * // With custom styling + * + * + * @example + * // With initial messages (for session restoration) + * + */ + +'use client'; + +import { + forwardRef, + useCallback, + useEffect, + useRef, + useState, + type HTMLAttributes, +} from 'react'; +import { MessageSquare, AlertCircle, X } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { useChat, useSSE, useProviders } from '@/hooks'; +import type { SSEDoneResult, SSEError } from '@/hooks'; +import { + MemoizedChatMessage, + ChatInput, +} from '@/components/chat'; +import type { ChatInputHandle } from '@/components/chat'; +import { EmptyState } from './empty-state'; +import { ErrorState } from './error-state'; +import type { ErrorType } from './error-state'; +import { Button } from '@/components/ui/button'; +import { Spinner } from '@/components/ui/spinner'; +import type { HistoryMessage, Message } from '@/types'; +import { CHAT_CONFIG } from '@/config/constants'; + +/** + * Props for the ChatContainer component. + * + * Extends standard HTML div attributes for flexibility in styling + * and event handling, while providing chat-specific configuration. + */ +export interface ChatContainerProps extends Omit< + HTMLAttributes, + 'title' +> { + /** + * Title displayed in the chat header. + * Defaults to "pythermalcomfort Chat". + * + * @default "pythermalcomfort Chat" + */ + title?: string; + + /** + * Whether to show the internal chat header. + * Set to false when the title is rendered at the page level. + * + * @default true + */ + showHeader?: boolean; + + /** + * Initial messages to populate the chat. + * Useful for restoring conversation state from localStorage or URL params. + * + * @default [] + */ + initialMessages?: Message[]; + + /** + * Callback fired when messages change. + * Useful for persisting conversation to localStorage. + * + * @param messages - The updated messages array + */ + onMessagesChange?: (messages: Message[]) => void; +} + +/** + * ChatHeader Component + * + * Renders a minimal, unobtrusive header section that stays out of the way. + * Follows Claude-style design where the header is subtle and doesn't + * distract from the main chat content. + * + * Note: Provider selection has been moved to the input area (ChatInput) + * to match Claude's UI pattern where the model selector is near the + * send button. + * + * @design + * - Small icon and text size to minimize visual weight + * - Muted colors so it doesn't compete with chat content + * - Very subtle bottom separator (minimal opacity) + * - Left-aligned, compact layout + * + * @param title - The main title text + * + * @internal + */ +function ChatHeader({ + title, +}: { + title: string; +}): React.ReactElement { + return ( +
+ {/* + * Icon in purple - matches brand color. + */} +
+ ); +} + +/** + * ErrorBanner Component + * + * Displays a subtle error message that doesn't dominate the interface. + * Uses a lighter error styling with reduced opacity backgrounds and + * softer colors for a less alarming appearance. + * + * @design + * - Light error background (reduced opacity) for subtle presence + * - No border for cleaner appearance + * - Smaller, less intrusive icon + * - Gentle dismiss button + * + * @param message - The error message to display + * @param onDismiss - Callback fired when the dismiss button is clicked + * + * @internal + */ +function ErrorBanner({ + message, + onDismiss, +}: { + message: string; + onDismiss: () => void; +}): React.ReactElement { + return ( +
+ {/* Small error icon - less prominent for minimal design */} +
+ ); +} + +/** + * LoadingOverlay Component + * + * A minimal loading indicator using the purple accent color. + * Designed to be subtle and unobtrusive while still communicating + * that a response is being generated. + * + * @design + * - Purple spinner to match the brand color + * - Muted text for the loading message + * - Compact layout that doesn't disrupt the message flow + * - Fade-in animation for smooth appearance + * + * @internal + */ +function LoadingOverlay(): React.ReactElement { + return ( +
+ {/* Purple spinner - matches the minimal design accent color */} + + {/* Subtle loading text - doesn't demand attention */} + + Generating response... + +
+ ); +} + +/** + * Parsed error information for determining display type and retry behavior. + * + * @internal + */ +interface ParsedError { + /** The type of error for visual differentiation */ + type: ErrorType; + /** Seconds to wait before retrying (for quota errors) */ + retryAfterSeconds?: number; +} + +/** + * Parse an SSE error to determine its type and retry information. + * + * Maps SSE error properties to ErrorState types: + * - Network errors (TypeError, fetch failures) -> 'network' + * - Errors with retryAfter (503 quota exceeded) -> 'quota' + * - Other errors -> 'general' + * + * @param error - The SSE error to parse + * @returns Parsed error with type and optional retry delay + * + * @internal + */ +function parseErrorType(error: SSEError): ParsedError { + // Network errors get special styling + if (error.isNetworkError) { + return { type: 'network' }; + } + + // Errors with retryAfter are quota/rate limit errors (HTTP 503) + if (error.retryAfter !== undefined && error.retryAfter > 0) { + return { + type: 'quota', + // Convert milliseconds to seconds for display + retryAfterSeconds: Math.ceil(error.retryAfter / 1000), + }; + } + + // Default to general error + return { type: 'general' }; +} + +/** + * ChatContainer Component + * + * The main chat interface component following true Claude-style minimal design. + * This component orchestrates all chat functionality while achieving a seamless, + * boundary-free aesthetic where messages flow naturally on the page background. + * + * @remarks + * ## Architecture + * + * The ChatContainer is composed of several sub-components: + * + * 1. **ChatHeader**: Minimal header with icon, title, and provider toggle (very subtle border) + * 2. **Message List**: Spacious scrollable area with generous message spacing + * 3. **EmptyState**: Clean welcome state with example questions + * 4. **ChatInput**: Minimal input area with very subtle top separator + * 5. **ErrorBanner**: Unobtrusive error display above the input + * 6. **LoadingOverlay**: Purple-themed loading indicator + * + * ## Design Philosophy + * + * This component implements true Claude-style minimal design: + * + * - **No visible boundaries**: Main container has no borders, shadows, or rounded corners + * - **Seamless integration**: Chat interface blends directly into the page background + * - **Minimal chrome**: Headers and inputs use very subtle separators (20% opacity borders) + * - **Content-first**: Messages flow naturally without visual containment + * - **Zero shadows**: Completely flat design with no elevation effects + * - **Generous spacing**: gap-6 between messages for content breathing room + * - **Purple accents**: Brand color used sparingly for icons and spinners + * + * ## State Management + * + * Uses the `useChat` hook for all state management: + * - `messages`: Array of all chat messages + * - `isLoading`: Whether a response is being generated + * - `error`: Current error message (if any) + * + * ## Auto-Scroll Behavior + * + * The message list automatically scrolls to the bottom when: + * - A new message is added + * - The assistant response is updated (streaming) + * + * Smooth scroll behavior is used for a polished feel. The scroll + * is triggered via a `useEffect` that watches the messages array. + * + * ## SSE Streaming Integration + * + * The submit handler uses the useSSE hook to stream responses from the + * backend. Tokens are appended to the assistant message in real-time, + * and the final response includes source citations from RAG retrieval. + * + * ## Performance Optimizations + * + * - Uses `MemoizedChatMessage` to prevent unnecessary re-renders + * - Callbacks are memoized with `useCallback` + * - Refs are used for direct DOM manipulation (auto-scroll) + * - Lazy loading patterns preserved for heavy dependencies + * + * ## Accessibility + * + * - Main region is marked with `role="region"` and proper aria-label + * - Error messages use `role="alert"` for screen reader announcements + * - Loading state is announced via `aria-live` region + * - All interactive elements are keyboard accessible + * + * ## Responsive Design + * + * The chat container is designed to work across all screen sizes: + * - Full viewport height on mobile + * - Constrained max-width on larger screens + * - Proper padding and spacing at all breakpoints + * + * @param props - ChatContainer component props + * @returns React element containing the complete chat interface + * + * @see {@link ChatContainerProps} for full prop documentation + * @see {@link useChat} for state management details + */ +const ChatContainer = forwardRef( + ( + { + title = 'pythermalcomfort Chat', + showHeader = true, + initialMessages = [], + onMessagesChange, + className, + ...props + }, + ref + ) => { + /** + * Initialize the useChat hook for state management. + * + * This hook provides all message state and actions needed + * for the complete chat functionality. + */ + const { + messages, + isLoading, + error, + addMessage, + updateLastMessage, + setIsLoading, + setError, + clearError, + } = useChat({ + initialMessages, + onMessagesChange, + }); + + /** + * Initialize the useProviders hook for provider selection. + * + * This hook provides the selected provider to pass to the SSE stream. + * When selectedProvider is null, auto mode is active and the backend + * will select the best available provider. + */ + const { selectedProvider } = useProviders(); + + /** + * Error type state for determining which error UI to show. + * + * - 'quota': HTTP 503 errors with retry-after header + * - 'network': Connection/fetch failures + * - 'general': All other errors + * + * Used to show appropriate ErrorState styling and behavior. + */ + const [errorType, setErrorType] = useState(null); + + /** + * Seconds until retry is allowed (for quota errors). + * + * Populated when the server returns a 503 with Retry-After header. + * The ErrorState component uses this to show a countdown timer. + */ + const [retryAfterSeconds, setRetryAfterSeconds] = useState( + null + ); + + /** + * Ref to track if the user manually aborted the stream. + * Used to distinguish user cancellation from error states. + */ + const userAbortedRef = useRef(false); + + /** + * Ref to store the last submitted query for retry functionality. + * + * When an error occurs and the user clicks retry, we need to re-submit + * the same query. This ref preserves the query across renders. + */ + const lastQueryRef = useRef(null); + + /** + * Initialize the useSSE hook for streaming responses. + * + * Callbacks are wired to update the chat state as tokens + * arrive and when the stream completes or errors. + */ + const { startStream, abort, isStreaming } = useSSE({ + /** + * Handle incoming tokens during streaming. + * Appends each token to the last message content. + */ + onToken: useCallback( + (content: string) => { + updateLastMessage((prev) => prev + content); + }, + [updateLastMessage] + ), + + /** + * Handle successful stream completion. + * Updates the message with the final response and sources. + */ + onDone: useCallback( + (result: SSEDoneResult) => { + updateLastMessage(result.response, { + isStreaming: false, + sources: result.sources, + }); + setIsLoading(false); + }, + [updateLastMessage, setIsLoading] + ), + + /** + * Handle stream errors. + * + * This callback processes errors from the SSE stream and updates the UI + * appropriately based on the error type: + * + * 1. If user manually aborted, skip error handling entirely + * 2. Parse the error to determine type (network, quota, or general) + * 3. Update error state (type, message, retry delay) + * 4. If there are existing messages, update the last assistant message + * to show an error occurred. The ErrorBanner will handle display. + * 5. If no messages exist, the ErrorState component will be shown + * in place of the EmptyState (handled in render logic) + */ + onError: useCallback( + (error: SSEError) => { + // Don't show error if user manually aborted + if (userAbortedRef.current) { + userAbortedRef.current = false; + return; + } + + // Parse the error to determine type and retry behavior + const parsed = parseErrorType(error); + + // Update error state for UI rendering decisions + setErrorType(parsed.type); + setRetryAfterSeconds(parsed.retryAfterSeconds ?? null); + setError(error.message); + setIsLoading(false); + + // Only update the assistant message if we have messages in the chat. + // For empty state errors, we show the full ErrorState component instead. + if (messages.length > 0) { + updateLastMessage( + 'Sorry, I encountered an error. Please try again.', + { + isStreaming: false, + } + ); + } + }, + [setError, setIsLoading, updateLastMessage, messages.length] + ), + }); + + /** + * Ref to the messages container for auto-scroll functionality. + * We scroll this container to the bottom when new messages arrive. + */ + const messagesContainerRef = useRef(null); + + /** + * Ref to the ChatInput component for programmatic control. + * Used to populate the input when example questions are clicked. + */ + const chatInputRef = useRef(null); + + /** + * Ref to track the end of the messages list. + * Used as the target for scroll-into-view behavior. + */ + const messagesEndRef = useRef(null); + + /** + * Auto-scroll effect that triggers when messages change. + * + * Scrolls the message list to the bottom with smooth behavior + * whenever the messages array is updated. This ensures users + * always see the latest message without manual scrolling. + * + * Implementation note: We use a slight delay to ensure the DOM + * has updated before scrolling. This prevents scroll jitter + * during rapid streaming updates. + */ + useEffect(() => { + // Only scroll if there are messages + if (messages.length === 0) return; + + // Use requestAnimationFrame for smooth scroll after DOM update + const timeoutId = requestAnimationFrame(() => { + messagesEndRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'end', + }); + }); + + return () => cancelAnimationFrame(timeoutId); + }, [messages]); + + /** + * Handle example question clicks from EmptyState. + * + * Populates the chat input with the clicked question, + * allowing users to submit it or modify before sending. + */ + const handleExampleClick = useCallback((question: string) => { + chatInputRef.current?.setValue(question); + chatInputRef.current?.focus(); + }, []); + + /** + * Clear all error state including type and retry information. + * + * This is an enhanced version of clearError that also resets + * the errorType and retryAfterSeconds state. Used when: + * - User dismisses an error + * - A new query is submitted + * - Retry is initiated + */ + const handleClearError = useCallback(() => { + clearError(); + setErrorType(null); + setRetryAfterSeconds(null); + }, [clearError]); + + /** + * Handle aborting the current stream. + * + * Called when the user wants to cancel an in-progress response. + * Marks the abort as user-initiated to prevent error display. + * + * Note: This function is available for future use when a cancel button + * is added to the UI. Currently prefixed with underscore to satisfy + * the linter, but the functionality is fully implemented. + * + * @example + * // Usage with a cancel button: + * + */ + const _handleAbort = useCallback(() => { + userAbortedRef.current = true; + abort(); + setIsLoading(false); + // Update the last message to show it was cancelled + updateLastMessage((prev) => prev || 'Response cancelled.', { + isStreaming: false, + }); + }, [abort, setIsLoading, updateLastMessage]); + + /** + * Handle message submission from ChatInput. + * + * Initiates an SSE stream to the backend: + * 1. Clears any existing errors (including type and retry info) + * 2. Stores the query for potential retry + * 3. Aborts any in-progress stream + * 4. Adds the user message to the conversation + * 5. Creates a placeholder assistant message + * 6. Starts the SSE stream with the selected provider + * + * @param content - The user's message content + */ + const handleSubmit = useCallback( + (content: string) => { + // Guard against empty submissions + if (!content.trim()) return; + + // Clear any existing errors (including error type and retry info) + handleClearError(); + + // Store the query for retry functionality + // This allows us to re-submit the same query if an error occurs + lastQueryRef.current = content; + + // If already streaming, abort the previous stream + if (isStreaming) { + userAbortedRef.current = true; + abort(); + } + + // ===================================================================== + // Build conversation history for multi-turn context + // ===================================================================== + // Extract the most recent messages (before adding the new user message) + // Filter out incomplete streaming messages and limit to maxHistoryForAPI + // This provides context to the LLM for follow-up questions + const history: HistoryMessage[] = messages + .filter((msg) => !msg.isStreaming && msg.content.trim()) + .slice(-CHAT_CONFIG.maxHistoryForAPI) + .map((msg) => ({ + role: msg.role, + content: msg.content, + })); + + // Add the user's message to the conversation + addMessage('user', content); + + // Add a placeholder for the assistant's response + // Mark it as streaming so it shows the loading cursor + addMessage('assistant', '', { isStreaming: true }); + + // Set loading state + setIsLoading(true); + + // Reset the abort flag before starting new stream + userAbortedRef.current = false; + + // Start the SSE stream with the selected provider and conversation history + // Pass undefined instead of null for auto mode + startStream(content, selectedProvider ?? undefined, history); + }, + [ + addMessage, + setIsLoading, + handleClearError, + isStreaming, + abort, + startStream, + selectedProvider, + messages, + ] + ); + + /** + * Handle retry after an error occurs. + * + * Clears the error state and re-submits the last query. + * For empty state errors (no messages), this will submit the query fresh. + * For mid-conversation errors, this will add a new message pair. + * + * Note: For network errors where the query never reached the server, + * this effectively gives the user a second chance to submit. + */ + const handleRetry = useCallback(() => { + handleClearError(); + if (lastQueryRef.current) { + handleSubmit(lastQueryRef.current); + } + }, [handleClearError, handleSubmit]); + + /** + * Determine if we should show the empty state. + * Only show when there are no messages in the conversation. + */ + const showEmptyState = messages.length === 0; + + return ( +
+ {/* Optional internal header - can be hidden when title is at page level */} + {showHeader && } + + {/* + * Messages Area - Scrollable container with generous spacing. + * Increased padding and gap for content breathing room. + */} +
+ {/* + * Content rendering logic: + * + * 1. Empty state WITH error -> Show full-page ErrorState component + * This provides a prominent error display when the user hasn't + * started a conversation yet (e.g., first query fails). + * + * 2. Empty state WITHOUT error -> Show EmptyState with examples + * The normal welcome state with example questions. + * + * 3. Messages exist -> Show message list + * The ErrorBanner handles errors during conversation. + */} + {showEmptyState && error ? ( + /* Full-page error state for empty chat with error */ + + ) : showEmptyState ? ( + /* Normal empty state with example questions */ + + ) : ( + <> + {/* Render all messages with increased spacing (gap-6) */} + {messages.map((message) => ( + + ))} + + {/* Minimal loading indicator with purple spinner */} + {isLoading && } + + {/* Scroll anchor - used for auto-scroll functionality */} + + + {/* + * Error Banner - Shown above input for mid-conversation errors. + * + * Only display the ErrorBanner when: + * - There IS an error + * - There ARE messages in the chat (not empty state) + * + * For empty state errors, we show the full ErrorState component + * instead (handled above), so we hide the banner in that case. + */} + {error && !showEmptyState && ( + + )} + + {/* + * Input Area - Fixed at bottom with minimal visual separation. + * True Claude-style: very subtle top border, no heavy containers. + * The input blends naturally into the page. + */} +
+ +
+
+ ); + } +); + +/* Display name for React DevTools debugging */ +ChatContainer.displayName = 'ChatContainer'; + +export { ChatContainer }; diff --git a/frontend/src/components/chat/chat-input.tsx b/frontend/src/components/chat/chat-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f63bf355c9063c0b3980aaf039f2e99400eec8fb --- /dev/null +++ b/frontend/src/components/chat/chat-input.tsx @@ -0,0 +1,721 @@ +/** + * ChatInput Component + * + * A refined, modern chat input component inspired by Claude's design language. + * Features a clean, minimal aesthetic with subtle purple accents and smooth + * transitions for a premium user experience. + * + * @module components/chat/chat-input + * @since 1.0.0 + * + * ## Design Philosophy + * + * This component follows Claude's design principles: + * - **Minimal and clean**: Reduced visual noise with purposeful whitespace + * - **Subtle interactions**: Gentle hover states and smooth transitions + * - **Focus on content**: The user's input is the hero, not the UI chrome + * - **Accessible by default**: High contrast ratios and clear focus states + * + * ## Visual Design Details + * + * ### Container + * - Clean white background with subtle border + * - Rounded corners (12px) for a friendly, modern feel + * - Soft shadow on focus for depth without being distracting + * - Purple glow effect on focus using box-shadow (not ring) + * + * ### Textarea + * - Borderless design that blends with the container + * - Muted placeholder text for visual hierarchy + * - Smooth auto-grow behavior without jarring size changes + * + * ### Send Button + * - Circular shape for visual distinction + * - Purple gradient background matching brand colors + * - Subtle scale animation on hover for tactile feedback + * - Clear disabled state without being visually jarring + * + * ### Supporting Elements + * - Character counter in subtle, smaller text + * - Keyboard hints in lighter color, smaller size + * - All supporting text uses muted colors to not compete with main input + * + * @example + * // Basic usage + * handleSubmit(message)} /> + * + * @example + * // With loading state + * + * + * @example + * // Custom placeholder and max length + * + */ + +'use client'; + +import { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, + type FormEvent, + type KeyboardEvent, + type ChangeEvent, +} from 'react'; +import { Send } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Spinner } from '@/components/ui/spinner'; +import { ProviderSelector } from './provider-selector'; + +/** + * Maximum height for the auto-growing textarea in pixels. + * After reaching this height, the textarea becomes scrollable. + * This value ensures the input doesn't dominate the viewport. + */ +const MAX_TEXTAREA_HEIGHT = 200; + +/** + * Minimum height for the textarea in pixels. + * Ensures consistent appearance even with empty content. + * Matches standard single-line input height for visual consistency. + */ +const MIN_TEXTAREA_HEIGHT = 56; + +/** + * Default character limit for messages. + * Can be overridden via the maxLength prop. + * 1000 characters balances detailed questions with API limits. + */ +const DEFAULT_MAX_LENGTH = 1000; + +/** + * Default placeholder text for the input. + * Encourages users to ask questions in a friendly tone. + */ +const DEFAULT_PLACEHOLDER = 'Ask your question...'; + +/** + * Ref handle exposed by ChatInput for imperative control. + * + * Allows parent components to programmatically control the input, + * useful for accessibility features or external form management. + */ +export interface ChatInputHandle { + /** Focus the textarea input */ + focus: () => void; + /** Clear the input value */ + clear: () => void; + /** Get the current input value */ + getValue: () => string; + /** Set the input value programmatically */ + setValue: (value: string) => void; +} + +/** + * Props for the ChatInput component. + * + * Designed for flexibility with sensible defaults, supporting + * both controlled and uncontrolled usage patterns. + */ +export interface ChatInputProps { + /** + * Callback fired when the user submits a message. + * Called with the trimmed message content. + * The input is automatically cleared after submission. + * + * @param message - The trimmed message text + */ + onSubmit: (message: string) => void; + + /** + * Whether the input is in a loading/processing state. + * Disables the input and shows a loading spinner in the submit button. + * + * @default false + */ + isLoading?: boolean; + + /** + * Whether the input is disabled. + * Separate from isLoading to allow disabling without loading indicator. + * + * @default false + */ + disabled?: boolean; + + /** + * Placeholder text for the textarea. + * + * @default "Ask a question about pythermalcomfort..." + */ + placeholder?: string; + + /** + * Maximum character length for the input. + * Shows a character counter when approaching the limit. + * + * @default 1000 + */ + maxLength?: number; + + /** + * Whether to show the character count indicator. + * Shows when the user has typed more than 80% of maxLength. + * + * @default true + */ + showCharacterCount?: boolean; + + /** + * Whether to auto-focus the input on mount. + * Useful for immediate input availability. + * + * @default true + */ + autoFocus?: boolean; + + /** + * Additional CSS classes for the container. + */ + className?: string; + + /** + * Additional CSS classes for the textarea element. + */ + textareaClassName?: string; +} + +/** + * ChatInput Component + * + * A feature-rich chat input component optimized for conversational interfaces + * with a refined, Claude-inspired visual design. + * + * @remarks + * ## Features + * + * ### Auto-Growing Textarea + * The textarea automatically grows as the user types, up to a maximum height + * of 200px. After reaching the max height, the content becomes scrollable. + * This provides a seamless typing experience without manual resizing. + * + * ### Keyboard Support + * - **Enter**: Submits the message (when not empty and not loading) + * - **Shift+Enter**: Inserts a new line (for multi-line messages) + * - Submission is blocked when the input is empty or whitespace-only + * + * ### Character Limit + * - Configurable maximum character limit (default: 1000) + * - Visual counter appears when approaching the limit (>80%) + * - Counter turns red when at or over the limit + * - Submission is blocked when over the limit + * + * ### Loading State + * - When isLoading is true, the textarea and button are disabled + * - The submit button shows a loading spinner instead of the send icon + * - Prevents accidental double-submission + * + * ### Accessibility + * - Proper labeling via aria-label on the textarea + * - Submit button has aria-label for screen readers + * - Disabled state is communicated via aria-disabled + * - Character counter is announced to screen readers + * + * ## Performance + * - Uses requestAnimationFrame for smooth height calculations + * - Memoized callbacks to prevent unnecessary re-renders + * - Auto-focus uses useEffect to avoid hydration mismatches + * + * @param props - ChatInput component props + * @returns React element containing the chat input form + * + * @see {@link ChatInputProps} for full prop documentation + * @see {@link ChatInputHandle} for imperative API + */ +const ChatInput = forwardRef( + ( + { + onSubmit, + isLoading = false, + disabled = false, + placeholder = DEFAULT_PLACEHOLDER, + maxLength = DEFAULT_MAX_LENGTH, + showCharacterCount = true, + autoFocus = true, + className, + textareaClassName, + }, + ref + ) => { + /** + * Internal state for the input value. + * Controlled internally with external access via ref handle. + */ + const [value, setValue] = useState(''); + + /** + * Track focus state for container styling. + * Used to apply the purple glow effect on focus. + */ + const [isFocused, setIsFocused] = useState(false); + + /** + * Ref to the textarea element for height calculations and focus. + */ + const textareaRef = useRef(null); + + /** + * Computed values for UI state + */ + const characterCount = value.length; + const isOverLimit = characterCount > maxLength; + const isNearLimit = characterCount > maxLength * 0.8; + const isEmpty = value.trim().length === 0; + const isDisabled = disabled || isLoading; + const canSubmit = !isEmpty && !isOverLimit && !isDisabled; + + /** + * Reset textarea height to minimum. + * Called after submission or clearing. + * + * Declared before useImperativeHandle since it's used in the handle. + */ + const resetTextareaHeight = useCallback(() => { + if (textareaRef.current) { + textareaRef.current.style.height = `${MIN_TEXTAREA_HEIGHT}px`; + } + }, []); + + /** + * Adjust textarea height based on content. + * + * Algorithm: + * 1. Reset height to auto to get accurate scrollHeight + * 2. Set height to scrollHeight, clamped to min/max bounds + * + * Uses direct style manipulation for performance (avoids re-render). + * Declared before useImperativeHandle since it's used in the handle. + */ + const adjustTextareaHeight = useCallback(() => { + const textarea = textareaRef.current; + if (!textarea) return; + + // Reset height to recalculate scrollHeight accurately + textarea.style.height = 'auto'; + + // Calculate new height within bounds + const newHeight = Math.min( + Math.max(textarea.scrollHeight, MIN_TEXTAREA_HEIGHT), + MAX_TEXTAREA_HEIGHT + ); + + textarea.style.height = `${newHeight}px`; + }, []); + + /** + * Expose imperative handle for parent component control. + * + * This allows parent components to: + * - Focus the input (e.g., after closing a modal) + * - Clear the input (e.g., on conversation reset) + * - Get/set value (e.g., for draft restoration) + */ + useImperativeHandle( + ref, + () => ({ + focus: () => { + textareaRef.current?.focus(); + }, + clear: () => { + setValue(''); + resetTextareaHeight(); + }, + getValue: () => value, + setValue: (newValue: string) => { + setValue(newValue); + // Defer height calculation to after state update + requestAnimationFrame(adjustTextareaHeight); + }, + }), + [value, resetTextareaHeight, adjustTextareaHeight] + ); + + /** + * Handle input value changes. + * Updates state and adjusts textarea height. + */ + const handleChange = useCallback( + (event: ChangeEvent) => { + setValue(event.target.value); + // Defer to next frame for accurate height calculation + requestAnimationFrame(adjustTextareaHeight); + }, + [adjustTextareaHeight] + ); + + /** + * Handle form submission. + * Validates input, calls onSubmit, and clears the input. + */ + const handleSubmit = useCallback( + (event?: FormEvent) => { + event?.preventDefault(); + + // Validate submission conditions + if (!canSubmit) return; + + // Get trimmed value for submission + const trimmedValue = value.trim(); + + // Call parent callback + onSubmit(trimmedValue); + + // Clear input and reset height + setValue(''); + resetTextareaHeight(); + + // Refocus textarea for continued conversation + requestAnimationFrame(() => { + textareaRef.current?.focus(); + }); + }, + [canSubmit, value, onSubmit, resetTextareaHeight] + ); + + /** + * Handle keyboard events for submission. + * + * - Enter without Shift: Submit the message + * - Shift+Enter: Insert new line (default behavior) + */ + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + // Only handle Enter key + if (event.key !== 'Enter') return; + + // Shift+Enter: Allow default new line behavior + if (event.shiftKey) return; + + // Prevent default to avoid new line insertion + event.preventDefault(); + + // Submit if conditions are met + handleSubmit(); + }, + [handleSubmit] + ); + + /** + * Auto-focus effect on mount. + * Wrapped in useEffect to avoid hydration mismatches + * and respect the autoFocus prop. + */ + useEffect(() => { + if (autoFocus && textareaRef.current) { + // Small delay to ensure DOM is ready + const timeoutId = setTimeout(() => { + textareaRef.current?.focus(); + }, 100); + + return () => clearTimeout(timeoutId); + } + }, [autoFocus]); + + /** + * Calculate character count display text. + * Only shown when approaching or exceeding the limit. + */ + const characterCountText = `${characterCount}/${maxLength}`; + + return ( +
+ {/* + * Main Input Container + * + * Design: A prominent, refined container that serves as the visual + * anchor for the input area. Uses subtle rounded borders and + * a clean background that elevates slightly on focus. + * + * Focus State: Instead of the typical focus ring, we use a soft + * purple glow effect via box-shadow. This creates a more refined, + * less jarring focus indicator that feels premium. + */} +
+ {/* + * Textarea + * + * Design: Clean, minimal textarea without internal borders. + * Blends seamlessly with the container to feel like one unit. + * Placeholder uses muted color for visual hierarchy. + */} +