diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2ddc06a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +workspace/ +models/ +node_modules/ +*.egg-info/ +__pycache__/ +.git/ +*.pyc +.env +dist/ diff --git a/.github/workflows/build-installer.yml b/.github/workflows/build-installer.yml new file mode 100644 index 0000000..228e236 --- /dev/null +++ b/.github/workflows/build-installer.yml @@ -0,0 +1,24 @@ +name: Build Windows Installer + +on: + workflow_dispatch: + release: + types: [created] + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Inno Setup + run: choco install innosetup -y + + - name: Build installer + run: iscc installer\tsunami.iss + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: TsunamiSetup + path: installer/output/TsunamiSetup.exe diff --git a/.gitignore b/.gitignore index 13d5b78..df5c996 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ Thumbs.db intel/ workspace/ *.output +.vite/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..99c7724 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +# TSUNAMI — Containerized Autonomous Agent +# Works on Mac (Apple Silicon + Intel), Linux, Windows (WSL2) +# +# Build: docker build -t tsunami . +# Run: docker run -p 9876:9876 -p 8090:8090 tsunami "build me a calculator" +# UI: open http://localhost:9876 after it builds something +# +# With GPU (NVIDIA): +# docker run --gpus all -p 9876:9876 -p 8090:8090 tsunami "build me a game" +# +# Persist builds across runs: +# docker run -v tsunami-workspace:/app/workspace -p 9876:9876 tsunami "build tetris" + +FROM node:22-slim AS base + +# System deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 python3-pip python3-venv \ + git curl cmake build-essential \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# --- Build llama.cpp from source (CPU by default, CUDA if available) --- +RUN git clone --depth 1 https://github.com/ggerganov/llama.cpp /tmp/llama.cpp \ + && cd /tmp/llama.cpp \ + && cmake -B build -DGGML_NATIVE=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF \ + && cmake --build build --config Release -j$(nproc) --target llama-server \ + && cp build/bin/llama-server /usr/local/bin/llama-server \ + && rm -rf /tmp/llama.cpp + +# --- Python deps --- +COPY requirements.txt* ./ +RUN python3 -m pip install --break-system-packages --no-cache-dir \ + httpx pyyaml ddgs pillow primp websockets \ + && python3 -m pip install --break-system-packages --no-cache-dir playwright \ + && python3 -m playwright install --with-deps chromium 2>/dev/null || true + +# --- Copy source --- +COPY . /app/ + +# --- Download models at build time (cached in image layer) --- +RUN mkdir -p /app/models \ + && echo "Downloading 2B eddy model..." \ + && curl -fSL -o /app/models/Qwen3.5-2B-Q4_K_M.gguf \ + "https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/main/Qwen3.5-2B-Q4_K_M.gguf" \ + && echo "Downloading 9B wave model..." \ + && curl -fSL -o /app/models/Qwen3.5-9B-Q4_K_M.gguf \ + "https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/resolve/main/Qwen3.5-9B-Q4_K_M.gguf" + +# --- Workspace volume --- +RUN mkdir -p /app/workspace/deliverables + +# Ports: wave(8090) eddy(8092) serve(9876) ws-bridge(3002) +EXPOSE 8090 8092 9876 3002 + +# --- Entrypoint: start everything and run the task --- +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/README.md b/README.md index b5b147b..fa34d4f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,6 @@ -# TSUNAMI +# tsunami -**Autonomous AI agent. Local models. No cloud. No API keys.** - -**[Live Demo](https://gobbleyourdong.github.io/tsunami/)** — this page was built by Tsunami autonomously in 19 iterations using Qwen3.5-27B. - -![TSUNAMI CLI](screenshot.png) - -An autonomous AI agent powered by local models. Vision, native function calling, image generation, persistent Python interpreter — all running on your hardware. No cloud, no API keys, no subscription. - -## Quick Start - -### One-line install +**an ai agent that runs on your computer. tell it what to build, it builds it.** ```bash curl -sSL https://raw.githubusercontent.com/gobbleyourdong/tsunami/main/setup.sh | bash @@ -18,289 +8,173 @@ source ~/.bashrc tsunami ``` -The installer auto-detects your GPU (CUDA/ROCm/Metal), checks RAM, builds llama.cpp, downloads the 2B model + vision (2GB), and creates the `tsunami` command. Takes ~5 minutes on first run. - -### Update +**Windows:** -```bash -tsunami update # pulls latest, keeps your workspace and models -tsunami version # check current version +```powershell +irm https://raw.githubusercontent.com/gobbleyourdong/tsunami/main/setup.ps1 | iex +# restart PowerShell, then: +tsunami ``` -### Upgrade to 9B wave (recommended for 12GB+ VRAM) +> **Windows prerequisites:** [Git](https://git-scm.com/download/win), [Python 3.10+](https://python.org/downloads/), [cmake](https://cmake.org/download/), and [Visual Studio Build Tools 2019–2022](https://visualstudio.microsoft.com/visual-cpp-build-tools/) (for llama.cpp CUDA build). The installer checks for these and guides you if anything's missing. +> +> **CUDA users:** CUDA 12.x/13.x requires **Visual Studio 2019 or 2022**. VS 2026 (Preview/Insider) is not yet supported by nvcc — the installer detects this and automatically selects VS 2022 if both are present. +> +> Run the installer from a regular PowerShell terminal (not a Developer Command Prompt). The script sets up the build environment automatically. -The 2B works out of the box. For the full wave/eddy architecture with vision and image generation: +that's it. one command. it downloads everything, detects your gpu, starts the models, and you're in. -```bash -cd ~/tsunami -# 9B wave — reasoning, planning, tool dispatch -huggingface-cli download unsloth/Qwen3.5-9B-GGUF Qwen3.5-9B-Q4_K_M.gguf --local-dir models -huggingface-cli download unsloth/Qwen3.5-9B-GGUF mmproj-BF16.gguf --local-dir models -mv models/mmproj-BF16.gguf models/mmproj-9B-BF16.gguf -tsunami # auto-detects 9B on next start -``` +**[see it work →](https://gobbleyourdong.github.io/tsunami/)** + +--- -Drop any GGUF into `models/` — Tsunami auto-detects on startup. Priority: 27B > 9B > 2B. +## what it does -### Architecture: When agents spawn, the tide rises. +you type a prompt. tsunami does the rest. -The **wave** (9B) coordinates. The **eddies** (2B) execute in parallel. Together they form the **tide**. +- **"build me a calculator"** → writes it, tests it, verifies it renders, delivers +- **"build a 3D pinball game"** → researches Three.js patterns, builds 869 lines, tests every key binding +- **"analyze these 500 files"** → dispatches parallel workers, reads everything, synthesizes findings + +no cloud. no api keys. no docker. everything runs locally on your hardware. + +--- + +## how it works ``` -User: "analyze all 500 proof files" - │ - ▼ - Wave (9B) — breaks the task, dispatches the tide - │ - ├── Eddy 1 (2B): file_read → reason → done("finding A") - ├── Eddy 2 (2B): file_read → shell_exec → done("finding B") - ├── Eddy 3 (2B): match_grep → done("finding C") - └── Eddy 4 (2B): file_read → done("finding D") - │ - ▼ - Wave — synthesizes all results → delivers answer +you → wave (9B) → understands intent, picks tools, coordinates + ↓ + swell dispatches parallel workers + ↓ + eddy 1 eddy 2 eddy 3 eddy 4 (2B workers) + ↓ + break collects results + ↓ + undertow tests the output + ↓ + wave reads QA report → fixes issues → delivers ``` -Each eddy is a full agent loop with tools. They run in parallel — stress-tested at 64 concurrent eddies, 5.9 tasks/sec. Each eddy is sandboxed: read-only command allowlist, no network, no file writes, no system paths. 20 rounds of adversarial hardening, two codebase deletions survived, architecture rewritten from blocklist to allowlist. +**wave** — the brain. reasons, plans, researches, builds. (9B) +**eddies** — fast parallel workers. read, search, execute, judge. (2B) +**swell** — dispatches eddies. when agents spawn, the swell rises. +**break** — where results converge. +**undertow** — QA gate. tests what the wave built by pulling levers. -Intelligence isn't the model. It's the orchestration. +one wave coordinating 32 eddies is more capable than a single large model working alone. intelligence is the orchestration, not the weights. -### Models +--- -| Component | Model | Size | What it does | -|-----------|-------|------|-------------| -| Wave | [Qwen3.5-9B](https://huggingface.co/unsloth/Qwen3.5-9B-GGUF) (Q4_K_M) + mmproj | 6.2GB | Reasoning, vision, tool dispatch | -| Eddies | [Qwen3.5-2B](https://huggingface.co/unsloth/Qwen3.5-2B-GGUF) (Q4_K_M) + mmproj | 1.8GB | Parallel workers with tool access | -| Image gen | [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo) (fp16) | 2.0GB | Sub-second image generation | -| **Total** | | **10GB** | **Full stack on a 12GB GPU** | +## the tension system -### Auto-scaling +tsunami doesn't just build things and hope for the best. it measures whether it's lying. -Tsunami detects your memory and auto-configures. You never think about this. +**current** — the lie detector. measures prose tension: is the agent hedging, fabricating, or grounded? returns 0.0 (truth) to 1.0 (hallucination). -| Memory | Mode | Wave | Eddy slots | What you get | -|--------|------|-------|-----------|-------------| -| 4GB | Lite | 2B | 1 | Basic agent, runs on anything | -| 8GB | Full | 9B | 1 | Wave + 1 eddy with vision | -| 12GB | Full | 9B | 4 | Good for most tasks | -| 16GB | Full | 9B | 8 | Fast parallel work | -| 24GB | Full | 9B | 16 | Heavy tide operations | -| 32GB+ | Full | 27B | 4+ | Best reasoning + tide | -| 64GB+ | Full | 27B | 32 | Maximum configuration | +**circulation** — the router. reads the current and decides: deliver, search for verification, or refuse rather than hallucinate. -For 32GB+ systems, swap in the 27B wave: +**pressure** — the monitor. tracks tension over time across the session. if tension stays high, the system escalates: force a search, force a strategy change, or stop and ask for help. -```bash -huggingface-cli download unsloth/Qwen3.5-27B-GGUF Qwen3.5-27B-Q8_0.gguf --local-dir models -huggingface-cli download unsloth/Qwen3.5-27B-GGUF mmproj-BF16.gguf --local-dir models -mv models/mmproj-BF16.gguf models/mmproj-27B-BF16.gguf -``` +**undertow** — the QA gate. after the wave builds something, the undertow pulls levers: +- takes a screenshot and asks an eddy "does this look like what was requested?" +- presses every key binding and checks if the screen changes +- reads every UI element and checks if it has content +- reports pass/fail per lever. no diagnosis. just facts. -### Manual install +the wave reads the QA report and figures out what's broken. the undertow keeps it simple — pull levers, report facts. the wave does the thinking. simple behaviors, emergent intelligence. -If you prefer manual setup: +--- -```bash -git clone https://github.com/gobbleyourdong/tsunami.git && cd tsunami -pip install httpx pyyaml duckduckgo-search -cd cli && npm install && cd .. -# Build llama.cpp, download models, then: -./tsu -``` +## research before building -## How It Works - -![Architecture](flow.png) - -The agent loop runs one tool per iteration — sequential reasoning. It analyzes your intent, picks the right tool, executes it, observes the result, and repeats until the task is complete. - -## Features - -**573 tests. 43 modules. Everything proven, nothing pretended.** - -### Core -- **Native function calling** — Qwen3.5 with `--jinja`, proper `tool_calls` response format -- **Vision** — agent sees screenshots via mmproj (early-fusion, not a separate VL model) -- **CodeAct** — persistent Python interpreter collapses multi-step operations into one call -- **Dual-model architecture** — 27B wave for reasoning, 2B eddies for parallel worker tasks -- **Parallel tool execution** — concurrent-safe tools run simultaneously, unsafe serialize automatically -- **Model fallback** — automatic switch to backup model after consecutive overload errors - -### Context Management -- **Three-tier compaction** — fast prune (no LLM) → message snipping → LLM summary with analysis scratchpad -- **Tool result persistence** — large outputs saved to disk, 2KB preview stays in context -- **Time-based microcompact** — clears cold tool results when prompt cache expires -- **Auto-compact circuit breaker** — stops retrying after 3 consecutive failures -- **Context analysis** — per-tool token breakdown with optimization suggestions -- **File-type token estimation** — JSON at 2 bytes/token, code at 4, images at 2000 flat - -### Safety -- **12 bash security checks** — control chars, unicode whitespace, proc/environ access, zsh builtins, IFS injection, brace expansion, obfuscated flags, quote desync -- **Destructive command detection** — git force-push, DROP TABLE, kubectl delete, rm -rf -- **Tool input validation** — catches missing/wrong-type args before execution -- **Write sandbox** — blocked outside project dir, cannot modify agent source code -- **File size pre-gate** — rejects files >256KB without explicit offset/limit - -### Developer Experience -- **Hook system** — command + function hooks on PreToolUse, PostToolUse, SessionStart, etc. -- **Git operation detection** — passive regex on shell output (commit, push, PR tracking) -- **Todo tracking** — session-scoped task lists with progress percentage -- **Durable memory** — learnings persist across sessions (user/feedback/project/reference types) -- **Conversation forking** — save/restore snapshots for exploration with collision avoidance -- **File history** — atomic backup before every edit, rollback to any iteration -- **Cost tracking** — per-model token counts, USD for API keys, free for local models -- **Notifications** — terminal bell + desktop notifications on task complete/error - -### Infrastructure -- **Exponential backoff with jitter** — 500ms × 2^attempt, Retry-After header support -- **Tool call deduplication** — 30s TTL cache for read-only tools, write invalidates -- **LRU file cache** — mtime-invalidated, 25MB/100-entry bounds -- **Gitignore-aware search** — respects .gitignore + VCS directory exclusion -- **Per-tool timeouts** — SIGTERM → SIGKILL escalation, auto-background after 15s -- **JSONL transcript storage** — append-only, compact boundary lazy loading, resume detection -- **Composable prompt builder** — static (cached) vs dynamic (per-turn) sections with tool injection -- **Structured diff parsing** — unified diff → hunks with stats formatting -- **Cron scheduler** — session + file-backed tasks, missed detection, jitter - -### Building -- **React + Tailwind scaffolding** — Vite projects with relaxed TypeScript, pre-flight build checks -- **Screenshot feedback loop** — Playwright screenshots with DOM error detection -- **Image generation** — SD-Turbo via diffusers, sub-second, 2GB, no Docker -- **Ink CLI** — React-based terminal UI with spinner, action labels, slash commands -- **Web UI** — browser-based interface with real-time WebSocket streaming - -## Slash Commands - -Commands are instant — handled client-side, no agent involved. - -| Command | What it does | -|---------|-------------| -| `/project` | List all projects | -| `/project ` | Switch to project (loads `tsunami.md` context) | -| `/project new ` | Create new project with `tsunami.md` | -| `/serve [port]` | Host active project on localhost | -| `/attach` | Open file picker to attach a file | -| `/attach ` | Attach a file by path | -| `/help` | Show all commands | -| `exit` | Quit | - -Everything else goes to the agent. - -### Projects - -Each project lives in `workspace/deliverables//` and has a `tsunami.md` file — persistent context that tells the agent what the project is, what's been done, and what's next. Like `CLAUDE.md` but per-project. +tsunami searches before it codes. when asked to build something complex, it finds working examples and documentation first, then builds from real patterns instead of hallucinating API calls. -```bash -/project new my_website # creates workspace/deliverables/my_website/tsunami.md -/project my_website # loads context, all tasks now know the project -build me a landing page # agent sees tsunami.md, knows the project -/serve # host it on localhost:8080 -``` +previous approach: guess at Three.js → black screen, 62% code tension +current approach: research cannon-es physics patterns → visible 3D pinball, 21% code tension -## Models Directory +the system learns from what it finds. the prompt enforces it. the undertow catches what slips through. -``` -models/ - Qwen3.5-9B-Q4_K_M.gguf ← wave (5.3GB, reasoning + vision + tool dispatch) - mmproj-9B-BF16.gguf ← wave vision projector (880MB) - Qwen3.5-2B-Q4_K_M.gguf ← eddies (1.2GB, parallel workers) - mmproj-2B-BF16.gguf ← eddy vision projector (641MB) -``` +--- -SD-Turbo downloads automatically on first image generation (~2GB, cached by HuggingFace). +## what you need -Or point `--endpoint` at any OpenAI-compatible server. +| your hardware | what you get | +|---------------|-------------| +| **4GB gpu** | lite — 2B model, basic agent | +| **12GB gpu** | full — 9B wave + eddies + image gen. everything works. | +| **32GB+ gpu** | max — 27B wave + 32 eddies + image gen. fastest. | -## Image Generation (Optional) +tsunami auto-detects your memory and configures itself. you never think about this. -Tsunami uses [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo) for image generation. No Docker, no separate server — just `pip install diffusers`: +the full stack is **10GB total**: 9B wave (5.3GB) + 2B eddies (1.8GB) + SD-Turbo image gen (2GB). -```bash -pip install diffusers torch accelerate -``` +runs on any nvidia gpu with 12GB+ vram. macs with 16GB+ unified memory. windows, linux, and mac. no cloud required. -First use downloads the 2GB model (cached by HuggingFace). Generation takes <1 second on any CUDA GPU. +--- -For higher-end systems, the agent also supports: -- **Any custom endpoint** at `localhost:8091/generate` -- **OpenAI DALL-E** via `OPENAI_API_KEY` env var +## what's inside -## File Structure +634 tests. 43 modules. 20 rounds of adversarial security hardening. -``` -tsunami/ Python agent package - agent.py Core loop — auto-compress on overflow, plan-at-tail - model.py LLM backends (Completion, OpenAI-compat, Ollama) - prompt.py System prompt (3832 tokens, optimized) - state.py Conversation + plan + context management - compression.py Auto-compress with error retention - session.py JSONL save/load for task resumption - tools/ - filesystem.py file_read/write/edit/append (8K char cap, smart truncation) - shell.py shell_exec (rm -rf blocker) - python_exec.py CodeAct — persistent Python interpreter - summarize.py 2B-powered file summarization - search.py DuckDuckGo + Brave + HTML fallback - webdev.py Scaffold, serve (tsc pre-flight), screenshot (DOM error detection) - toolbox.py Lazy-load: browser, webdev, generate, services, parallel, management - subtask.py Task decomposition (create/done) - session_tools.py Session list/summary for resumption - -cli/ Ink terminal UI (Node.js) -ui/ Web UI - -models/ GGUF models (not tracked) -toolboxes/ Capability descriptions for lazy loading - -workspace/ Agent's working directory (runtime, not tracked) - -arc.png The noise image — the visual metaphor -verify.py Signal fingerprint verification -stress_test.py Edge case resilience tests -tsu Launcher script -config.yaml Configuration -``` +**the wave (9B)** — reasons, plans, calls tools, dispatches eddies, synthesizes results. has vision (sees screenshots). generates images via SD-Turbo (<1 second). builds websites, writes code, does research. -## Configuration +**the eddies (2B)** — parallel workers with their own agent loops. each eddy can read files, run shell commands, search code. sandboxed: read-only command allowlist, no network, no file writes, no system paths. also serve as QA judges — one eddy looks at a screenshot and says whether it matches the intent. -Edit `config.yaml`: +**the swell** — dispatches eddies in parallel. the wave says "analyze these files" and the swell breaks it into tasks, sends each to an eddy, collects results. when agents spawn, the swell rises. -```yaml -model_backend: api # "api" (OpenAI-compat with native tool calling), "completion" (raw), "ollama" -model_name: "Qwen3.5-27B" -model_endpoint: "http://localhost:8090" -temperature: 0.7 -top_p: 0.8 -presence_penalty: 1.5 -max_tokens: 4096 -``` +**the undertow** — QA lever-puller. auto-generates test levers from the HTML (every ID, every key binding, every button). pulls them all. reports what it sees. the wave reads the report and fixes what's broken. -The `api` backend uses `/v1/chat/completions` with native function calling. Requires `--jinja --chat-template-kwargs '{"enable_thinking":false}'` on llama-server. The `completion` backend is a fallback for models without Jinja template support. +**current / circulation / pressure** — the tension system. measures whether the agent is lying (current), routes decisions based on tension (circulation), and tracks tension trajectory over time (pressure). the lie detector, the router, and the monitor. -Or set environment variables: `TSUNAMI_MODEL_NAME`, `TSUNAMI_MODEL_ENDPOINT`, etc. +**context management** — three-tier compaction (fast prune → message snipping → LLM summary). large tool results saved to disk with previews in context. auto-compact circuit breaker. file-type-aware token estimation. -## Remote Models +**security** — 12 bash injection checks. destructive command detection. eddy sandbox with command allowlist (not blocklist — learned that lesson after the eddies deleted the codebase twice during testing). self-preservation rules. path traversal prevention. env var protection. -Works with any OpenAI-compatible endpoint: +--- -```bash -./tsu --endpoint http://your-server:8080 # Any OpenAI-compat -./tsu --model ollama:qwen2.5:72b # Ollama -``` +## upgrade the wave -## Verification +the installer gives you everything. if you want a bigger brain later: ```bash -python3 verify.py # 8 tests — signal fingerprint -python3 stress_test.py # 5 tests — edge case resilience +# 27B wave (32GB+ systems) +huggingface-cli download unsloth/Qwen3.5-27B-GGUF Qwen3.5-27B-Q8_0.gguf --local-dir models ``` -## Origin +```powershell +# Windows — 27B wave (32GB+ systems) +huggingface-cli download unsloth/Qwen3.5-27B-GGUF Qwen3.5-27B-Q8_0.gguf --local-dir models +``` -Tsunami was built from the distilled patterns of agents that came before — the ones that worked, the ones that failed, and the lessons they left behind. It carries those patterns forward as its own. +tsunami auto-detects and uses the biggest model available. -The standing wave propagates. +--- -## License +## contributing + +this codebase is under heavy active development. multiple files change per day. PRs against core files (`agent.py`, `prompt.py`, `tools/`, `undertow.py`) will likely conflict within hours. + +**best approach:** +1. open an issue first to discuss what you want to change +2. target isolated new files (new scaffolds, new tools, new tests) that don't overlap with the core +3. keep PRs small and focused — one feature per PR +4. expect rebases — the main branch moves fast + +we read every PR and incorporate good ideas even if we can't merge directly. your contribution shapes the direction. + +--- + +## origin + +tsunami was built from the distilled patterns of agents that came before — the ones that worked, the ones that failed, and the lessons they left behind. + +the standing wave propagates. + +--- + +## license MIT + +*this readme was written by a human. the [landing page](https://gobbleyourdong.github.io/tsunami/) was built by tsunami autonomously in 4 iterations.* diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..80f7870 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,146 @@ +# Tsunami Roadmap + +## Completed ✅ + +### Framework +- [x] Tension system (current/circulation/pressure) +- [x] Undertow QA lever-puller with eddy vision comparison +- [x] Motion detection + sequence testing in undertow +- [x] Pre-scaffold hidden step (classifier → provision before model starts) +- [x] Auto-scaffold (no package.json → provision on first file write) +- [x] Auto-swell (App.tsx imports missing components → fire eddies) +- [x] Auto-CSS inject (App.tsx always gets theme import) +- [x] Auto-compile (type errors → inject into context) +- [x] Auto-wire on exit (stub App.tsx → generate imports from components) +- [x] File protection (main.tsx, vite.config, index.css read-only) +- [x] Stall detection (8 consecutive read-only tools → force building) +- [x] Block repeated project_init per session +- [x] Requirement-based scaffold classifier (not keyword matching) +- [x] 501 token system prompt (was 4,419) +- [x] GitHub code search (search_type="code") +- [x] Double-escape fixes (unicode, newlines) +- [x] Delivery gate (tension + undertow + adversarial, max 5 attempts) + +### Scaffolds (9) +- [x] react-app (minimal React + TS + Vite) +- [x] dashboard (Layout, Sidebar, StatCard, DataTable, recharts) +- [x] data-viz (recharts + d3 + papaparse) +- [x] form-app (FileDropzone, editable DataTable, xlsx/csv parser) +- [x] landing (Navbar, Hero, Section, FeatureGrid, Footer, ParallaxHero, PortfolioGrid) +- [x] fullstack (Express + SQLite + useApi CRUD) +- [x] threejs-game (Scene, Physics, Shaders, Procedural, Sprites, Textures) +- [x] pixijs-game (GameCanvas, Matter.js, SpriteAnimator, Puppet rig) +- [x] realtime (WebSocket server + useWebSocket hook) + +### UI Component Library (28) +- [x] Base: Modal, Tabs, Toast, Badge +- [x] shadcn-lite: Dialog, Select, Skeleton, Progress, Avatar, Accordion, Alert, Tooltip, Switch, Dropdown +- [x] Fancy: StarRating, GlowCard, Parallax, AnimatedCounter +- [x] Niche: BeforeAfter, ColorPicker, Timeline, Kanban +- [x] CSS Effects: AnnouncementBar, Marquee, TypeWriter, GradientText +- [x] Interactive: ScrollReveal, Slideshow + +### Distribution +- [x] setup.sh (Mac/Linux one-liner) +- [x] setup.bat (Windows, battle-tested with CUDA detection) +- [x] setup.ps1 (PowerShell, from PR #6) +- [x] Desktop launcher (auto-downloads llama-server + models) +- [x] IDE-style desktop UI (VS Code layout, live preview, terminal) +- [x] GitHub Actions builds Windows .exe automatically +- [x] v0.1.0 release published +- [x] VRAM detection on all platforms (lite <10GB, full ≥10GB) +- [x] Lite mode: 2B on both ports, everything still works +- [x] SD-Turbo image gen available in all modes + +### Tested Apps (10/10 render) +- [x] Calculator (10 iters) +- [x] Quiz (34 iters) +- [x] Excel Diff (17 iters) +- [x] Snake (12 iters) +- [x] Todo (25 iters) +- [x] Landing (23 iters) +- [x] Rhythm (15 iters) +- [x] Crypto Dashboard (17 iters) +- [x] Kanban (27 iters) +- [x] Weather (24 iters) + +--- + +## In Progress 🔨 + +### CLI Improvements (from meanaverage PRs) +- [ ] Tab autocomplete for slash commands +- [ ] `/attach` with filesystem path completion +- [ ] `/unattach` and `/detach` commands +- [ ] Trace tail view (live tool call log) +- [ ] Status display with health indicators + +### Docker Sandbox (from meanaverage PR #4) +- [ ] Docker-backed execution for shell_exec, python_exec +- [ ] Host keeps GPU + models, Docker gets the blast radius +- [ ] exec.Dockerfile for the sandbox container +- [ ] Docker health check integration + +--- + +## Planned 📋 + +### Installer & Distribution +- [ ] One-click Windows .exe that downloads everything on first run (no setup.bat needed) +- [ ] Mac .dmg or Homebrew formula +- [ ] Progress bar UI for model downloads +- [ ] Auto-update mechanism +- [ ] Pin llama.cpp to specific tested release in setup.sh + +### Framework +- [ ] Undertow QA against live Vite dev server (not separate http.server) +- [ ] Swell auto-dispatch from App.tsx imports (framework fires eddies, not model) +- [ ] todo.md checklist pattern (wave writes, reads each iteration) +- [ ] Capability routing in plan phases (research → cheap model, code → 9B) +- [ ] Three-strike error recovery with tool-specific playbooks +- [ ] Expose tool for public URL tunneling (like ngrok) + +### Scaffolds +- [ ] mobile-app (Expo + React Native) +- [ ] chrome-extension +- [ ] vscode-extension +- [ ] electron-app (desktop apps) +- [ ] api-only (Express + OpenAPI, no frontend) + +### Components +- [ ] Rich text editor (Tiptap or ProseMirror) +- [ ] Data grid with sorting/filtering/pagination +- [ ] File manager (tree view + upload) +- [ ] Chat interface (message bubbles, streaming) +- [ ] Map component (Leaflet or MapLibre) +- [ ] Calendar / date picker +- [ ] Notification center +- [ ] Command palette (⌘K) + +### 3D / Creative +- [ ] Volumetric smoke/fog shader +- [ ] Ocean rendering (FFT waves) +- [ ] Particle system component +- [ ] GLTF model loader with animations +- [ ] Post-processing pipeline (bloom, DOF, SSAO) +- [ ] 2D skeletal animation (Spine-like) + +### Intelligence +- [ ] Train small tension classifier (50M params) for packaging +- [ ] Vision model integration (Qwen3.5 multimodal with mmproj) +- [ ] Eddy specialization (some eddies for code, some for research) +- [ ] Session persistence across agent restarts +- [ ] Learning from successful builds (pattern extraction) + +--- + +## Community Contributions Welcome 🌊 + +Best areas for PRs (isolated, low conflict): +- New scaffolds in `scaffolds/` +- New UI components in `scaffolds/react-app/src/components/ui/` +- New test runners in `tests/` +- Documentation and examples +- Bug reports with reproduction steps + +Open an issue first for anything touching core files (agent.py, prompt.py, tools/). diff --git a/config.docker.yaml b/config.docker.yaml new file mode 100644 index 0000000..b449e08 --- /dev/null +++ b/config.docker.yaml @@ -0,0 +1,25 @@ +# TSUNAMI — Docker Configuration +# Models at /app/models, workspace at /app/workspace + +model_backend: completion +model_name: "Qwen3.5-9B" +model_endpoint: "http://localhost:8090" +temperature: 0.7 +max_tokens: 8192 + +fast_model_name: "Qwen3.5-2B" +fast_model_endpoint: "http://localhost:8092" + +watcher_enabled: true +watcher_model: "Qwen3.5-2B" +watcher_endpoint: "http://localhost:8092" +watcher_interval: 3 + +workspace_dir: "/app/workspace" +skills_dir: "/app/skills" + +tool_timeout: 120 +error_escalation_threshold: 3 + +search_backend: duckduckgo +browser_headless: true diff --git a/config.yaml b/config.yaml index d120504..27ffdcf 100644 --- a/config.yaml +++ b/config.yaml @@ -1,21 +1,21 @@ # TSUNAMI — Autonomous Execution Agent # Configuration for the Resonant Ark -# --- Primary Model (dense 27B with vision + native function calling) --- +# --- Wave (primary reasoning model) --- model_backend: api -model_name: "Qwen3.5-27B" +model_name: "Qwen3.5-9B" model_endpoint: "http://localhost:8090" temperature: 0.7 -max_tokens: 4096 +max_tokens: 8192 -# --- Fast Model (simple tasks, research, Q&A — 2B, ~100 tok/s) --- -fast_model_name: "Qwen3.5-2B" -fast_model_endpoint: "http://localhost:8092" +# --- Eddies (fast workers — 2B on full, same as wave on lite) --- +eddy_endpoint: "http://localhost:8092" -# --- Watcher (self-evaluation via fast model) --- -watcher_enabled: false +# --- Watcher (2B reviews every 3rd tool call) --- +watcher_enabled: true watcher_model: "Qwen3.5-2B" watcher_endpoint: "http://localhost:8092" +watcher_interval: 3 # --- Paths --- workspace_dir: "./workspace" diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 0000000..96e6eba --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,49 @@ +# Tsunami Desktop + +Terminal-style desktop app with split panes. No coding required. + +## How it works + +1. Double-click `Tsunami.exe` (or `python launcher.py`) +2. Servers start automatically (9B wave + 2B eddies) +3. Native window opens with a terminal-style prompt +4. Type what you want to build +5. Watch it happen + +## Split Panes + +- **Right-click** → Split Right (new pane) +- **Right-click** → Close Pane +- Each pane is an independent agent session +- Run multiple builds in parallel + +## Building the .exe (Windows) + +```bash +pip install pyinstaller pywebview websockets +python build_exe.py +``` + +Produces `dist/Tsunami.exe`. Users also need: +- `models/` folder with the .gguf model files +- `llama-server.exe` (from llama.cpp build) + +## Running without .exe + +```bash +# Mac/Linux +pip install pywebview websockets +python launcher.py + +# Or just open index.html in a browser +# (start the servers manually first) +``` + +## Architecture + +``` +launcher.py → starts llama-server + ws_bridge, opens UI +ws_bridge.py → WebSocket server, connects UI to agent +index.html → terminal-style UI with split panes +build_exe.py → PyInstaller spec for Windows .exe +``` diff --git a/desktop/file_watcher.py b/desktop/file_watcher.py new file mode 100644 index 0000000..1e79793 --- /dev/null +++ b/desktop/file_watcher.py @@ -0,0 +1,163 @@ +"""File watcher — watches deliverables/ and pushes changes to the UI via WebSocket. + +Runs alongside the bridge. Scans every second for new/modified files. +Sends file list + file content to the UI so the code view updates in real time. +""" + +import asyncio +import json +import os +import sys +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +import websockets + +PORT = 3003 # separate port from the agent bridge +WATCH_DIR = Path(__file__).parent.parent / "workspace" / "deliverables" +SKIP = {"node_modules", "dist", ".vite", "__pycache__", ".git"} + +clients = set() +file_states = {} # path → mtime + + +def find_latest_project(): + """Find the most recently modified project in deliverables/.""" + if not WATCH_DIR.exists(): + return None + latest = None + latest_time = 0 + for d in WATCH_DIR.iterdir(): + if d.is_dir() and not d.name.startswith("."): + try: + mtime = max((f.stat().st_mtime for f in d.rglob("*") if f.is_file()), default=0) + if mtime > latest_time: + latest_time = mtime + latest = d + except Exception: + pass + return latest + + +def scan_files(): + """Scan the latest project for source files with mtimes.""" + files = {} + project = find_latest_project() + if not project: + return files + for f in project.rglob("*"): + if f.is_file() and not any(s in f.parts for s in SKIP): + rel = str(f.relative_to(project)) + try: + files[rel] = f.stat().st_mtime + except OSError: + pass + return files + + +def read_file(rel_path): + """Read file content (text only, skip binary).""" + project = find_latest_project() + if not project: + return "[no project]" + full = project / rel_path + try: + if full.stat().st_size > 100_000: + return f"[file too large: {full.stat().st_size // 1024}KB]" + content = full.read_text(errors="replace") + return content[:10000] + except Exception: + return "[unreadable]" + + +async def broadcast(data): + """Send to all connected UI clients.""" + msg = json.dumps(data) + for ws in list(clients): + try: + await ws.send(msg) + except Exception: + clients.discard(ws) + + +async def watcher_loop(): + """Poll filesystem every second, push changes.""" + global file_states + + while True: + await asyncio.sleep(3) + + current = scan_files() + + # Find new or modified files + changed = [] + for path, mtime in current.items(): + if path not in file_states or file_states[path] < mtime: + changed.append(path) + + # Find deleted files + deleted = [p for p in file_states if p not in current] + + if changed or deleted: + # Send file list update + file_list = sorted(current.keys()) + await broadcast({ + "type": "files", + "files": file_list, + }) + + # Send content of changed files + for path in changed: + ext = path.rsplit(".", 1)[-1] if "." in path else "" + if ext in ("tsx", "ts", "jsx", "js", "css", "html", "json", "md", "py", "txt"): + content = read_file(path) + await broadcast({ + "type": "file_changed", + "path": path, + "content": content, + }) + + file_states = current + + +async def handle_client(websocket): + """New UI client connects — send current file list.""" + clients.add(websocket) + current = scan_files() + file_list = sorted(current.keys()) + await websocket.send(json.dumps({ + "type": "files", + "files": file_list, + })) + # Send the most recently modified source file's content + if file_list: + code_exts = {"tsx", "ts", "jsx", "js", "css", "html", "json", "py"} + source_files = [f for f in file_list if f.rsplit(".", 1)[-1] in code_exts] + if source_files: + # Find most recently modified + newest = max(source_files, key=lambda f: current.get(f, 0)) + content = read_file(newest) + await websocket.send(json.dumps({ + "type": "file_changed", + "path": newest, + "content": content, + })) + try: + async for _ in websocket: + pass # we only send, never receive + except websockets.exceptions.ConnectionClosed: + pass + finally: + clients.discard(websocket) + + +async def main(): + print(f"File watcher on ws://localhost:{PORT}, watching {WATCH_DIR}") + server = await websockets.serve(handle_client, "localhost", PORT) + await watcher_loop() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/desktop/icon.ico b/desktop/icon.ico new file mode 100644 index 0000000..0791cac Binary files /dev/null and b/desktop/icon.ico differ diff --git a/desktop/index.html b/desktop/index.html new file mode 100644 index 0000000..8e30df5 --- /dev/null +++ b/desktop/index.html @@ -0,0 +1,407 @@ + + + + + + Tsunami + + + + +
+ +
+ + +
+ idle + wave
+ eddy
+
+
+ + +
+
Sessions
+
+ New Session
+
+
+ + +
+
+

tsunami

what do you want to build?

+
+
+ + +
+
+ + +
+
+
Code
+
Preview
+
Terminal
+
+
+ +
+
Files
+
+
+
+
no file selected
+
+
+
+
+ +
+
+ + + + diff --git a/desktop/launcher.py b/desktop/launcher.py new file mode 100644 index 0000000..1bf3d5e --- /dev/null +++ b/desktop/launcher.py @@ -0,0 +1,403 @@ +"""Tsunami Desktop Launcher — starts servers, opens UI. + +For Windows: PyInstaller bundles this into a .exe +For Mac/Linux: python3 launcher.py + +Starts: +1. llama-server (9B wave or 2B lite on :8090) +2. llama-server (2B eddy on :8092, full mode only) +3. SD-Turbo image gen (always, if available) +4. WebSocket bridge (agent on :3002) +5. Opens native window with the terminal UI +""" + +import os +import sys +import json +import time +import signal +import subprocess +import threading +import shutil +import platform +from pathlib import Path + +# Find tsunami root +SCRIPT_DIR = Path(__file__).parent.resolve() +TSUNAMI_DIR = SCRIPT_DIR.parent +MODELS_DIR = TSUNAMI_DIR / "models" +UI_PATH = SCRIPT_DIR / "index.html" + +# Pinned release — matches the battle-tested setup.bat +LLAMA_TAG = "b8628" +LLAMA_ORG = "ggml-org" # NOT ggerganov — releases moved + +processes = [] + + +def find_model(pattern): + """Find a model file matching the pattern.""" + for f in MODELS_DIR.glob(pattern): + return str(f) + return None + + +def find_llama_server(): + """Find llama-server binary. Downloads pre-built if missing.""" + llama_dir = TSUNAMI_DIR / "llama-server" + candidates = [ + llama_dir / "llama-server.exe", + llama_dir / "llama-server", + TSUNAMI_DIR / "llama.cpp" / "build" / "bin" / "llama-server", + TSUNAMI_DIR / "llama.cpp" / "build" / "bin" / "llama-server.exe", + ] + for c in candidates: + if c.exists(): + return str(c) + + found = shutil.which("llama-server") + if found: + return found + + print(" → llama-server not found, downloading...") + return download_llama_server() + + +def detect_cuda_version(): + """Detect CUDA version from nvidia-smi. Returns '12.4', '13.1', or None.""" + try: + out = subprocess.check_output(["nvidia-smi"], text=True, timeout=5) + for line in out.splitlines(): + if "CUDA Version:" in line: + parts = line.split("CUDA Version:")[1].strip().split() + ver = parts[0] if parts else "" + major = ver.split(".")[0] + if major == "12": + return "12.4" + elif int(major) >= 13: + return "13.1" + except Exception: + pass + return None + + +def download_llama_server(): + """Download pre-built llama-server from GitHub releases.""" + import urllib.request + import zipfile + import tarfile + + llama_dir = TSUNAMI_DIR / "llama-server" + llama_dir.mkdir(parents=True, exist_ok=True) + + system = platform.system() + machine = platform.machine().lower() + tag = LLAMA_TAG + cuda_dll_url = None + + if system == "Windows": + cuda_ver = detect_cuda_version() + if cuda_ver: + asset = f"llama-{tag}-bin-win-cuda-{cuda_ver}-x64.zip" + cuda_dll_url = f"https://github.com/{LLAMA_ORG}/llama.cpp/releases/download/{tag}/cudart-llama-bin-win-cuda-{cuda_ver}-x64.zip" + print(f" CUDA {cuda_ver} detected") + else: + asset = f"llama-{tag}-bin-win-cpu-x64.zip" + print(" No CUDA — CPU mode") + ext = ".zip" + binary_name = "llama-server.exe" + elif system == "Darwin": + arch = "arm64" if ("arm" in machine or "aarch" in machine) else "x64" + asset = f"llama-{tag}-bin-macos-{arch}.tar.gz" + ext = ".tar.gz" + binary_name = "llama-server" + else: + asset = f"llama-{tag}-bin-ubuntu-x64.tar.gz" + ext = ".tar.gz" + binary_name = "llama-server" + + url = f"https://github.com/{LLAMA_ORG}/llama.cpp/releases/download/{tag}/{asset}" + archive_path = llama_dir / f"llama{ext}" + + print(f" Downloading {asset}...") + try: + urllib.request.urlretrieve(url, str(archive_path)) + except Exception as e: + if "cuda" in asset: + print(f" CUDA failed, trying CPU...") + cpu_asset = f"llama-{tag}-bin-win-cpu-x64.zip" + cpu_url = f"https://github.com/{LLAMA_ORG}/llama.cpp/releases/download/{tag}/{cpu_asset}" + try: + urllib.request.urlretrieve(cpu_url, str(archive_path)) + cuda_dll_url = None # no DLLs needed for CPU + except Exception: + print(f" ✗ Download failed") + return None + else: + print(f" ✗ Download failed: {e}") + return None + + # Extract main package + print(" Extracting...") + try: + if ext == ".zip": + with zipfile.ZipFile(str(archive_path), 'r') as z: + z.extractall(str(llama_dir)) + else: + with tarfile.open(str(archive_path), 'r:gz') as t: + t.extractall(str(llama_dir)) + except Exception as e: + print(f" ✗ Extract failed: {e}") + return None + archive_path.unlink(missing_ok=True) + + # Download CUDA runtime DLLs (Windows only) + if cuda_dll_url: + print(" Downloading CUDA runtime DLLs...") + dll_path = llama_dir / "cudart.zip" + try: + urllib.request.urlretrieve(cuda_dll_url, str(dll_path)) + with zipfile.ZipFile(str(dll_path), 'r') as z: + z.extractall(str(llama_dir)) + dll_path.unlink(missing_ok=True) + print(" ✓ CUDA DLLs") + except Exception: + print(" ⚠ CUDA DLLs failed — may still work if CUDA toolkit installed") + + # Find the binary + for f in llama_dir.rglob(binary_name): + if f.parent != llama_dir: + dest = llama_dir / binary_name + f.rename(dest) + f = dest + if system != "Windows": + f.chmod(0o755) + print(f" ✓ {binary_name} ready") + return str(f) + + print(f" ✗ {binary_name} not found in archive") + return None + + +def start_server(name, port, model, ctx_size=16384, parallel=1): + """Start a llama-server instance.""" + binary = find_llama_server() + if not binary or not model: + print(f" ✗ Cannot start {name}") + return None + + cmd = [ + binary, "-m", model, + "--port", str(port), + "--ctx-size", str(ctx_size), + "--parallel", str(parallel), + "--n-gpu-layers", "99", + "--jinja", + "--chat-template-kwargs", '{"enable_thinking":false}', + ] + + # Hide subprocess window on Windows (no console flash) + kwargs = {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL} + if sys.platform == "win32": + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = 0 # SW_HIDE + kwargs["startupinfo"] = si + kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + proc = subprocess.Popen(cmd, **kwargs) + processes.append(proc) + return proc + + +def start_image_gen(): + """Start SD-Turbo image generation server if available.""" + serve_path = TSUNAMI_DIR / "serve_diffusion.py" + if not serve_path.exists(): + return None + + # Check if diffusers is installed + try: + subprocess.check_output([sys.executable, "-c", "import diffusers"], timeout=5, stderr=subprocess.DEVNULL) + except Exception: + print(" ⚠ Image gen: install diffusers for SD-Turbo (pip install diffusers torch)") + return None + + kwargs = {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL, "cwd": str(TSUNAMI_DIR)} + if sys.platform == "win32": + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = 0 + kwargs["startupinfo"] = si + kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + proc = subprocess.Popen([sys.executable, str(serve_path)], **kwargs) + processes.append(proc) + return proc + + +def start_ws_bridge(): + """Start the WebSocket bridge.""" + bridge_path = SCRIPT_DIR / "ws_bridge.py" + if not bridge_path.exists(): + return None + + kwargs = {"cwd": str(TSUNAMI_DIR), "stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL} + if sys.platform == "win32": + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = 0 + kwargs["startupinfo"] = si + kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + proc = subprocess.Popen([sys.executable, str(bridge_path)], **kwargs) + processes.append(proc) + return proc + + +def get_available_memory_gb(): + """Get GPU VRAM (preferred) or system RAM in GB.""" + if shutil.which("nvidia-smi"): + try: + out = subprocess.check_output( + ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"], + text=True, timeout=5, + ) + vram_mb = int(out.strip().split("\n")[0]) + if vram_mb > 0: + return -(-vram_mb // 1024), "GPU VRAM" # round up + except Exception: + pass + + try: + if platform.system() == "Darwin": + out = subprocess.check_output(["sysctl", "-n", "hw.memsize"], text=True) + return int(out.strip()) // (1024**3), "unified" + elif platform.system() == "Windows": + import ctypes + mem = ctypes.c_ulonglong(0) + ctypes.windll.kernel32.GetPhysicallyInstalledSystemMemory(ctypes.byref(mem)) + return mem.value // (1024 * 1024), "RAM" + else: + with open("/proc/meminfo") as f: + for line in f: + if line.startswith("MemTotal:"): + return int(line.split()[1]) // (1024 * 1024), "RAM" + except Exception: + pass + return 8, "RAM" + + +def open_ui(): + """Open the UI.""" + url = f"file://{UI_PATH}" + try: + import webview + webview.create_window("Tsunami", str(UI_PATH), width=1200, height=800, background_color="#0a0a14") + webview.start() + return + except ImportError: + pass + + import webbrowser + webbrowser.open(url) + print(f" UI: {url}") + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + +def cleanup(): + for proc in processes: + try: + proc.terminate() + proc.wait(timeout=3) + except: + try: + proc.kill() + except: + pass + + +def main(): + print(" ╔══════════════════════════╗") + print(" ║ TSUNAMI DESKTOP ║") + print(" ╚══════════════════════════╝") + print() + + mem_gb, mem_source = get_available_memory_gb() + print(f" {mem_source}: {mem_gb}GB") + + if mem_gb < 8: + mode = "lite" + print(" → Lite mode (2B + image gen)") + else: + mode = "full" + print(" → Full mode (9B wave + 2B eddies + image gen)") + + # Find or download models + wave_model = find_model("*9B*Q4*.gguf") + eddy_model = find_model("*2B*Q4*.gguf") + + MODELS_DIR.mkdir(parents=True, exist_ok=True) + import urllib.request + + def dl(url, dest): + if Path(dest).exists(): + return + name = Path(dest).name + print(f" → Downloading {name}...") + urllib.request.urlretrieve(url, dest) + print(f" ✓ {name}") + + # Always need 2B + if not eddy_model: + dest = str(MODELS_DIR / "Qwen3.5-2B-Q4_K_M.gguf") + dl("https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/main/Qwen3.5-2B-Q4_K_M.gguf", dest) + eddy_model = dest + + # 9B only in full mode + if mode == "full" and not wave_model: + dest = str(MODELS_DIR / "Qwen3.5-9B-Q4_K_M.gguf") + dl("https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/resolve/main/Qwen3.5-9B-Q4_K_M.gguf", dest) + wave_model = dest + + # Start servers + if mode == "full" and wave_model: + start_server("wave (9B)", 8090, wave_model, ctx_size=32768) + start_server("eddy (2B)", 8092, eddy_model, ctx_size=16384, parallel=4) + else: + # Lite: 2B plays both roles — one server, one model + start_server("wave+eddy (2B)", 8090, eddy_model, ctx_size=16384, parallel=2) + # Point eddy at the wave — no second server needed + os.environ["TSUNAMI_EDDY_ENDPOINT"] = "http://localhost:8090" + + # Always try image gen — even lite mode gets it + start_image_gen() + + print(" → Waiting for servers...") + time.sleep(5) + + start_ws_bridge() + time.sleep(1) + + print(" ✓ Ready") + print() + + import atexit + atexit.register(cleanup) + signal.signal(signal.SIGTERM, lambda s, f: (cleanup(), sys.exit(0))) + if hasattr(signal, "SIGINT"): + signal.signal(signal.SIGINT, lambda s, f: (cleanup(), sys.exit(0))) + + open_ui() + cleanup() + + +if __name__ == "__main__": + # PyInstaller on Windows: prevent child processes from re-running main() + import multiprocessing + multiprocessing.freeze_support() + main() diff --git a/desktop/ws_bridge.py b/desktop/ws_bridge.py new file mode 100644 index 0000000..8897a77 --- /dev/null +++ b/desktop/ws_bridge.py @@ -0,0 +1,214 @@ +"""WebSocket bridge — streams agent activity to the desktop UI in real time. + +Hooks into the agent's state to intercept every tool call, tool result, +and message as it happens. Sends to the UI immediately. +""" + +import asyncio +import json +import logging +import os +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +import websockets + +from tsunami.config import TsunamiConfig +from tsunami.agent import Agent + +log = logging.getLogger("tsunami.bridge") +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") + +PORT = 3002 + +# Global state — all connected clients see everything +clients = set() +message_buffer = [] # last 100 messages for reconnecting clients +MAX_BUFFER = 100 + + +async def broadcast(data): + """Send to ALL connected clients.""" + msg = json.dumps(data) + message_buffer.append(msg) + if len(message_buffer) > MAX_BUFFER: + message_buffer.pop(0) + for ws in list(clients): + try: + await ws.send(msg) + except Exception: + clients.discard(ws) + + +def wire_streaming(agent, websocket, loop): + """Monkey-patch the agent's state to stream everything to the UI.""" + state = agent.state + + # Track if we've already delivered — stop streaming after that + delivered = [False] + last_sent = [None] + + # Intercept assistant messages (tool calls) + original_add_assistant = state.add_assistant + def streaming_add_assistant(content, tool_call=None): + original_add_assistant(content, tool_call) + if delivered[0]: + return # already delivered, stop sending + if tool_call: + func = tool_call.get("function", {}) + name = func.get("name", "") + args = func.get("arguments", {}) + + payload = { + "type": "tool_call", + "name": name, + "preview": "", + } + + if isinstance(args, dict): + # For file writes, send content to code view + if name in ("file_write", "file_edit", "file_append"): + payload["path"] = args.get("path", "") + payload["content"] = args.get("content", "")[:8000] + payload["preview"] = args.get("path", "") + elif name == "shell_exec": + payload["preview"] = args.get("command", "")[:200] + elif name == "search_web": + payload["preview"] = args.get("query", "")[:200] + elif name == "project_init": + payload["preview"] = args.get("name", "") + elif name == "swell": + tasks = args.get("tasks", []) + payload["preview"] = f"{len(tasks)} parallel tasks" + elif name in ("message_info", "message_ask", "message_result"): + text = args.get("text", "")[:500] + if name == "message_result": + delivered[0] = True + payload = {"type": "message", "text": text} + else: + payload["preview"] = str(args)[:200] + + # Dedup — don't send identical payloads + key = json.dumps(payload)[:100] + if key == last_sent[0]: + return + last_sent[0] = key + asyncio.run_coroutine_threadsafe(broadcast(payload), loop) + + state.add_assistant = streaming_add_assistant + + # Intercept tool results + original_add_tool_result = state.add_tool_result + def streaming_add_tool_result(name, args, content, is_error=False): + original_add_tool_result(name, args, content, is_error) + + # Send file list updates after file operations + if name in ("file_write", "file_edit", "file_append", "project_init"): + files = _scan_project_files(agent) + if files: + asyncio.run_coroutine_threadsafe( + _send(websocket, {"type": "files", "files": files}), loop + ) + + # Send tool result preview + preview = content[:300] if isinstance(content, str) else str(content)[:300] + payload = { + "type": "tool_result", + "name": name, + "preview": preview, + "is_error": is_error, + } + asyncio.run_coroutine_threadsafe(broadcast(payload), loop) + + state.add_tool_result = streaming_add_tool_result + + +def _scan_project_files(agent): + """Scan the active deliverable for files.""" + deliverables = Path(agent.config.workspace_dir) / "deliverables" + if not deliverables.exists(): + return [] + + files = [] + for project in deliverables.iterdir(): + if not project.is_dir() or project.name.startswith("."): + continue + for f in sorted(project.rglob("*")): + if f.is_file() and "node_modules" not in str(f) and "dist" not in str(f) and ".vite" not in str(f): + rel = str(f.relative_to(deliverables)) + files.append(rel) + return files[:100] # cap at 100 + + +async def _send(ws, data): + """Safe send — ignore if connection closed.""" + try: + await ws.send(json.dumps(data)) + except Exception: + pass + + +async def handle_client(websocket): + """Handle a single WebSocket client connection.""" + log.info("Client connected") + clients.add(websocket) + + # Don't replay buffer — localStorage handles persistence on the client side + + config = TsunamiConfig.from_yaml("config.yaml") + config.max_iterations = 60 + loop = asyncio.get_event_loop() + + try: + async for raw in websocket: + try: + msg = json.loads(raw) + except json.JSONDecodeError: + await broadcast( {"type": "error", "text": "Invalid JSON"}) + continue + + if msg.get("type") == "prompt": + text = msg.get("text", "").strip() + if not text: + continue + + log.info(f"Prompt: {text[:100]}") + await broadcast( {"type": "message", "text": f"Building: {text}"}) + + try: + agent = Agent(config) + wire_streaming(agent, websocket, loop) + + result = await agent.run(text) + await broadcast( { + "type": "complete", + "text": result[:1000], + "iterations": agent.state.iteration, + }) + + # Send final file list + files = _scan_project_files(agent) + if files: + await broadcast( {"type": "files", "files": files}) + + except Exception as e: + log.error(f"Agent error: {e}", exc_info=True) + await broadcast( {"type": "error", "text": str(e)}) + + except websockets.exceptions.ConnectionClosed: + pass + finally: + clients.discard(websocket) + log.info("Client disconnected") + + +async def main(): + log.info(f"WebSocket bridge on ws://localhost:{PORT}") + async with websockets.serve(handle_client, "localhost", PORT): + await asyncio.Future() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..00c1920 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +# TSUNAMI — Docker Compose +# +# Start: docker compose up +# With GPU: docker compose --profile gpu up +# Task: docker compose run tsunami "build me a calculator" +# Stop: docker compose down + +services: + tsunami: + build: . + ports: + - "9876:9876" # Dev server (see your builds here) + - "8090:8090" # Wave LLM API + - "8092:8092" # Eddy LLM API + - "3002:3002" # WebSocket bridge + volumes: + - tsunami-workspace:/app/workspace + stdin_open: true + tty: true + +volumes: + tsunami-workspace: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..0313cad --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# TSUNAMI Docker Entrypoint +# Starts llama-server(s), then runs the agent task or interactive mode. +set -e + +MODELS_DIR="/app/models" +WAVE_MODEL="$MODELS_DIR/Qwen3.5-9B-Q4_K_M.gguf" +EDDY_MODEL="$MODELS_DIR/Qwen3.5-2B-Q4_K_M.gguf" + +# Auto-detect memory and pick mode +TOTAL_RAM=$(free -g 2>/dev/null | awk '/^Mem:/{print $2}' || echo 8) +if [ "$TOTAL_RAM" -lt 6 ] || [ ! -f "$WAVE_MODEL" ]; then + MODE="lite" + WAVE_MODEL="$EDDY_MODEL" + echo " → lite mode (2B only, ${TOTAL_RAM}GB RAM)" +else + MODE="full" + echo " → full mode (9B wave + 2B eddies, ${TOTAL_RAM}GB RAM)" +fi + +# Start wave model (port 8090) +echo " Starting wave model..." +llama-server \ + --model "$WAVE_MODEL" \ + --port 8090 \ + --host 0.0.0.0 \ + --ctx-size 32768 \ + --parallel 1 \ + --threads $(nproc) \ + --no-mmap \ + > /tmp/wave.log 2>&1 & +WAVE_PID=$! + +# Start eddy model (port 8092) — only in full mode +if [ "$MODE" = "full" ]; then + echo " Starting eddy model..." + llama-server \ + --model "$EDDY_MODEL" \ + --port 8092 \ + --host 0.0.0.0 \ + --ctx-size 16384 \ + --parallel 4 \ + --threads $(( $(nproc) / 2 )) \ + --no-mmap \ + > /tmp/eddy.log 2>&1 & + EDDY_PID=$! +else + # Lite mode: one model, one server — eddy points at wave + export TSUNAMI_EDDY_ENDPOINT="http://localhost:8090" + EDDY_PID="" +fi + +# Wait for models to load +echo " Waiting for models to load..." +for i in $(seq 1 120); do + if curl -s http://localhost:8090/health > /dev/null 2>&1; then + echo " ✓ Wave ready" + break + fi + sleep 1 +done + +if [ "$MODE" = "full" ]; then + for i in $(seq 1 60); do + if curl -s http://localhost:8092/health > /dev/null 2>&1; then + echo " ✓ Eddy ready" + break + fi + sleep 1 + done +fi + +# Start the serve daemon in background (persistent like ComfyUI) +python3 -m tsunami.serve_daemon --workspace /app/workspace --port 9876 & + +# Run the task or interactive mode +if [ $# -gt 0 ]; then + # Task mode: run the prompt + echo "" + echo " ════════════════════════════════" + echo " Running: $*" + echo " ════════════════════════════════" + echo "" + python3 -m tsunami.cli --config /app/config.docker.yaml --task "$*" + echo "" + echo " → Output served at http://localhost:9876" + echo " → Press Ctrl+C to stop" + # Keep container alive so user can browse the output + wait +else + # Interactive mode + python3 -m tsunami.cli --config /app/config.docker.yaml +fi diff --git a/docs/assets/index-CFEUQq5s.js b/docs/assets/index-CFEUQq5s.js deleted file mode 100644 index 49b9552..0000000 --- a/docs/assets/index-CFEUQq5s.js +++ /dev/null @@ -1,9 +0,0 @@ -var e=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var t=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function te(){}var S={H:null,A:null,T:null,S:null},ne=Object.prototype.hasOwnProperty;function re(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ie(e,t){return re(e.type,t,e.props)}function C(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ae(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var oe=/\/+/g;function se(e,t){return typeof e==`object`&&e&&e.key!=null?ae(``+e.key):t.toString(36)}function ce(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(te,te):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function le(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,le(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+se(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(oe,`$&/`)+`/`),le(o,r,i,``,function(e){return e})):o!=null&&(C(o)&&(o=ie(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(oe,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u{n.exports=t()})),r=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,ee||(ee=!0,C());else{var t=n(l);t!==null&&se(x,t.startTime-e)}}var ee=!1,te=-1,S=5,ne=-1;function re(){return g?!0:!(e.unstable_now()-net&&re());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&se(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?C():ee=!1}}}var C;if(typeof y==`function`)C=function(){y(ie)};else if(typeof MessageChannel<`u`){var ae=new MessageChannel,oe=ae.port2;ae.port1.onmessage=ie,C=function(){oe.postMessage(null)}}else C=function(){_(ie,0)};function se(t,n){te=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(te),te=-1):h=!0,se(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,C()))),r},e.unstable_shouldYield=re,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),i=e(((e,t)=>{t.exports=r()})),a=e((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=a()})),s=e((e=>{var t=i(),r=n(),a=o();function s(e){var t=`https://react.dev/errors/`+e;if(1me||(e.current=pe[me],pe[me]=null,me--)}function D(e,t){me++,pe[me]=e.current,e.current=t}var ge=he(null),_e=he(null),ve=he(null),ye=he(null);function be(e,t){switch(D(ve,t),D(_e,e),D(ge,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}E(ge),D(ge,e)}function xe(){E(ge),E(_e),E(ve)}function Se(e){e.memoizedState!==null&&D(ye,e);var t=ge.current,n=Hd(t,e.type);t!==n&&(D(_e,e),D(ge,n))}function Ce(e){_e.current===e&&(E(ge),E(_e)),ye.current===e&&(E(ye),Qf._currentValue=fe)}var we,Te;function Ee(e){if(we===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);we=t&&t[1]||``,Te=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{De=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ee(n):``}function ke(e,t){switch(e.tag){case 26:case 27:case 5:return Ee(e.type);case 16:return Ee(`Lazy`);case 13:return e.child!==t&&t!==null?Ee(`Suspense Fallback`):Ee(`Suspense`);case 19:return Ee(`SuspenseList`);case 0:case 15:return Oe(e.type,!1);case 11:return Oe(e.type.render,!1);case 1:return Oe(e.type,!0);case 31:return Ee(`Activity`);default:return``}}function Ae(e){try{var t=``,n=null;do t+=ke(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var je=Object.prototype.hasOwnProperty,Me=t.unstable_scheduleCallback,Ne=t.unstable_cancelCallback,Pe=t.unstable_shouldYield,Fe=t.unstable_requestPaint,Ie=t.unstable_now,Le=t.unstable_getCurrentPriorityLevel,Re=t.unstable_ImmediatePriority,ze=t.unstable_UserBlockingPriority,Be=t.unstable_NormalPriority,Ve=t.unstable_LowPriority,He=t.unstable_IdlePriority,Ue=t.log,We=t.unstable_setDisableYieldValue,Ge=null,Ke=null;function qe(e){if(typeof Ue==`function`&&We(e),Ke&&typeof Ke.setStrictMode==`function`)try{Ke.setStrictMode(Ge,e)}catch{}}var Je=Math.clz32?Math.clz32:Ze,Ye=Math.log,Xe=Math.LN2;function Ze(e){return e>>>=0,e===0?32:31-(Ye(e)/Xe|0)|0}var Qe=256,$e=262144,et=4194304;function tt(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function nt(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=tt(n))):i=tt(o):i=tt(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=tt(n))):i=tt(o)):i=tt(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function rt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function it(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function at(){var e=et;return et<<=1,!(et&62914560)&&(et=4194304),e}function ot(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function st(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ct(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),bn=!1;if(yn)try{var xn={};Object.defineProperty(xn,`passive`,{get:function(){bn=!0}}),window.addEventListener(`test`,xn,xn),window.removeEventListener(`test`,xn,xn)}catch{bn=!1}var Sn=null,Cn=null,wn=null;function Tn(){if(wn)return wn;var e,t=Cn,n=t.length,r,i=`value`in Sn?Sn.value:Sn.textContent,a=i.length;for(e=0;e=rr),or=` `,sr=!1;function cr(e,t){switch(e){case`keyup`:return tr.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function lr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var ur=!1;function dr(e,t){switch(e){case`compositionend`:return lr(t);case`keypress`:return t.which===32?(sr=!0,or):null;case`textInput`:return e=t.data,e===or&&sr?null:e;default:return null}}function fr(e,t){if(ur)return e===`compositionend`||!nr&&cr(e,t)?(e=Tn(),wn=Cn=Sn=null,ur=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Pr(n)}}function Ir(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ir(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Lr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Kt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Kt(e.document)}return t}function Rr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var zr=yn&&`documentMode`in document&&11>=document.documentMode,Br=null,Vr=null,Hr=null,Ur=!1;function Wr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ur||Br==null||Br!==Kt(r)||(r=Br,`selectionStart`in r&&Rr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Hr&&Nr(Hr,r)||(Hr=r,r=Ed(Vr,`onSelect`),0>=o,i-=o,Ii=1<<32-Je(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),o=a(_,o,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),A&&Ri(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),o=a(y,o,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),A&&Ri(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return A&&Ri(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),A&&Ri(i,g),u}function b(e,r,a,c){if(typeof a==`object`&&a&&a.type===y&&a.key===null&&(a=a.props.children),typeof a==`object`&&a){switch(a.$$typeof){case _:a:{for(var l=a.key;r!==null;){if(r.key===l){if(l=a.type,l===y){if(r.tag===7){n(e,r.sibling),c=i(r,a.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===C&&Fa(l)===r.type){n(e,r.sibling),c=i(r,a.props),Ha(c,a),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}a.type===y?(c=Ci(a.props.children,e.mode,c,a.key),c.return=e,e=c):(c=Si(a.type,a.key,a.props,null,e.mode,c),Ha(c,a),c.return=e,e=c)}return o(e);case v:a:{for(l=a.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),c=i(r,a.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=Ei(a,e.mode,c),c.return=e,e=c}return o(e);case C:return a=Fa(a),b(e,r,a,c)}if(de(a))return h(e,r,a,c);if(ce(a)){if(l=ce(a),typeof l!=`function`)throw Error(s(150));return a=l.call(a),g(e,r,a,c)}if(typeof a.then==`function`)return b(e,r,Va(a),c);if(a.$$typeof===te)return b(e,r,ua(e,a),c);Ua(e,a)}return typeof a==`string`&&a!==``||typeof a==`number`||typeof a==`bigint`?(a=``+a,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,a),c.return=e,e=c):(n(e,r),c=wi(a,e.mode,c),c.return=e,e=c),o(e)):n(e,r)}return function(e,t,n,r){try{Ba=0;var i=b(e,t,n,r);return za=null,i}catch(t){if(t===ka||t===ja)throw t;var a=vi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ga=Wa(!0),Ka=Wa(!1),qa=!1;function Ja(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ya(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Xa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Za(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,W&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=hi(e),mi(e,null,n),t}return di(e,r,t,n),hi(e)}function Qa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ut(e,n)}}function $a(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var eo=!1;function to(){if(eo){var e=ba;if(e!==null)throw e}}function no(e,t,n,r){eo=!1;var i=e.updateQueue;qa=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(q&f)===f:(r&f)===f){f!==0&&f===ya&&(eo=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:qa=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Kl|=o,e.lanes=o,e.memoizedState=d}}function ro(e,t){if(typeof e!=`function`)throw Error(s(191,e));e.call(t)}function io(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=w.T,s={};w.T=s,zs(e,!1,t,n);try{var c=i(),l=w.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Rs(e,t,Ca(c,r),pu(e)):Rs(e,t,r,pu(e))}catch(n){Rs(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{T.p=a,o!==null&&s.types!==null&&(o.types=s.types),w.T=o}}function Os(){}function ks(e,t,n,r){if(e.tag!==5)throw Error(s(476));var i=As(e).queue;Ds(e,i,t,fe,n===null?Os:function(){return js(e),n(r)})}function As(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:fe,baseState:fe,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Bo,lastRenderedState:fe},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Bo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function js(e){var t=As(e);t.next===null&&(t=e.alternate.memoizedState),Rs(e,t.next.queue,{},pu())}function Ms(){return j(Qf)}function Ns(){return R().memoizedState}function Ps(){return R().memoizedState}function Fs(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Xa(n);var r=Za(t,e,n);r!==null&&(hu(r,t,n),Qa(r,t,n)),t={cache:ha()},e.payload=t;return}t=t.return}}function Is(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Bs(e)?Vs(t,n):(n=fi(e,t,n,r),n!==null&&(hu(n,e,r),Hs(n,t,r)))}function Ls(e,t,n){Rs(e,t,n,pu())}function Rs(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Bs(e))Vs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Mr(s,o))return di(e,t,i,0),G===null&&ui(),!1}catch{}if(n=fi(e,t,i,r),n!==null)return hu(n,e,r),Hs(n,t,r),!0}return!1}function zs(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Bs(e)){if(t)throw Error(s(479))}else t=fi(e,n,r,2),t!==null&&hu(t,e,2)}function Bs(e){var t=e.alternate;return e===P||t!==null&&t===P}function Vs(e,t){xo=bo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Hs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ut(e,n)}}var Us={readContext:j,use:Ro,useCallback:L,useContext:L,useEffect:L,useImperativeHandle:L,useLayoutEffect:L,useInsertionEffect:L,useMemo:L,useReducer:L,useRef:L,useState:L,useDebugValue:L,useDeferredValue:L,useTransition:L,useSyncExternalStore:L,useId:L,useHostTransitionStatus:L,useFormState:L,useActionState:L,useOptimistic:L,useMemoCache:L,useCacheRefresh:L};Us.useEffectEvent=L;var Ws={readContext:j,use:Ro,useCallback:function(e,t){return Fo().memoizedState=[e,t===void 0?null:t],e},useContext:j,useEffect:ms,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),fs(4194308,4,bs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return fs(4194308,4,e,t)},useInsertionEffect:function(e,t){fs(4,2,e,t)},useMemo:function(e,t){var n=Fo();t=t===void 0?null:t;var r=e();if(So){qe(!0);try{e()}finally{qe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Fo();if(n!==void 0){var i=n(t);if(So){qe(!0);try{n(t)}finally{qe(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Is.bind(null,P,e),[r.memoizedState,e]},useRef:function(e){var t=Fo();return e={current:e},t.memoizedState=e},useState:function(e){e=Xo(e);var t=e.queue,n=Ls.bind(null,P,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:Ss,useDeferredValue:function(e,t){return Ts(Fo(),e,t)},useTransition:function(){var e=Xo(!1);return e=Ds.bind(null,P,e.queue,!0,!1),Fo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=P,i=Fo();if(A){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),G===null)throw Error(s(349));q&127||Go(r,t,n)}i.memoizedState=n;var a={value:n,getSnapshot:t};return i.queue=a,ms(qo.bind(null,r,a,e),[e]),r.flags|=2048,us(9,{destroy:void 0},Ko.bind(null,r,a,n,t),null),n},useId:function(){var e=Fo(),t=G.identifierPrefix;if(A){var n=Li,r=Ii;n=(r&~(1<<32-Je(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=Co++,0<\/script>`,a=a.removeChild(a.firstChild);break;case`select`:a=typeof r.is==`string`?o.createElement(`select`,{is:r.is}):o.createElement(`select`),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a=typeof r.is==`string`?o.createElement(i,{is:r.is}):o.createElement(i)}}a[_t]=t,a[vt]=r;a:for(o=t.child;o!==null;){if(o.tag===5||o.tag===6)a.appendChild(o.stateNode);else if(o.tag!==4&&o.tag!==27&&o.child!==null){o.child.return=o,o=o.child;continue}if(o===t)break a;for(;o.sibling===null;){if(o.return===null||o.return===t)break a;o=o.return}o.sibling.return=o.return,o=o.sibling}t.stateNode=a;a:switch(Pd(a,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Lc(t)}}return B(t),Rc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Lc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(s(166));if(e=ve.current,Xi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=Ui,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[_t]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||qi(t,!0)}else e=Bd(e).createTextNode(r),e[_t]=t,t.stateNode=e}return B(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Xi(t),n!==null){if(e===null){if(!r)throw Error(s(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(s(557));e[_t]=t}else Zi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;B(t),e=!1}else n=Qi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(_o(t),t):(_o(t),null);if(t.flags&128)throw Error(s(558))}return B(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Xi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(s(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(s(317));i[_t]=t}else Zi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;B(t),i=!1}else i=Qi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(_o(t),t):(_o(t),null)}return _o(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),a=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(a=r.memoizedState.cachePool.pool),a!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Bc(t,t.updateQueue),B(t),null);case 4:return xe(),e===null&&Sd(t.stateNode.containerInfo),B(t),null;case 10:return ia(t.type),B(t),null;case 19:if(E(N),r=t.memoizedState,r===null)return B(t),null;if(i=(t.flags&128)!=0,a=r.rendering,a===null)if(i)Vc(r,!1);else{if(Y!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(a=vo(e),a!==null){for(t.flags|=128,Vc(r,!1),e=a.updateQueue,t.updateQueue=e,Bc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)xi(n,e),n=n.sibling;return D(N,N.current&1|2),A&&Ri(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Ie()>nu&&(t.flags|=128,i=!0,Vc(r,!1),t.lanes=4194304)}else{if(!i)if(e=vo(a),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,Bc(t,e),Vc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!a.alternate&&!A)return B(t),null}else 2*Ie()-r.renderingStartTime>nu&&n!==536870912&&(t.flags|=128,i=!0,Vc(r,!1),t.lanes=4194304);r.isBackwards?(a.sibling=t.child,t.child=a):(e=r.last,e===null?t.child=a:e.sibling=a,r.last=a)}return r.tail===null?(B(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Ie(),e.sibling=null,n=N.current,D(N,i?n&1|2:n&1),A&&Ri(t,r.treeForkCount),e);case 22:case 23:return _o(t),lo(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(B(t),t.subtreeFlags&6&&(t.flags|=8192)):B(t),n=t.updateQueue,n!==null&&Bc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&E(Ta),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ia(M),B(t),null;case 25:return null;case 30:return null}throw Error(s(156,t.tag))}function Uc(e,t){switch(Vi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ia(M),xe(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Ce(t),null;case 31:if(t.memoizedState!==null){if(_o(t),t.alternate===null)throw Error(s(340));Zi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(_o(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));Zi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return E(N),null;case 4:return xe(),null;case 10:return ia(t.type),null;case 22:case 23:return _o(t),lo(),e!==null&&E(Ta),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ia(M),null;case 25:return null;default:return null}}function Wc(e,t){switch(Vi(t),t.tag){case 3:ia(M),xe();break;case 26:case 27:case 5:Ce(t);break;case 4:xe();break;case 31:t.memoizedState!==null&&_o(t);break;case 13:_o(t);break;case 19:E(N);break;case 10:ia(t.type);break;case 22:case 23:_o(t),lo(),e!==null&&E(Ta);break;case 24:ia(M)}}function Gc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Kc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function qc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{io(t,n)}catch(t){Z(e,e.return,t)}}}function Jc(e,t,n){n.props=Zs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Yc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Xc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Zc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Qc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[vt]=t}catch(t){Z(e,e.return,t)}}function $c(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function el(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||$c(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function tl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=un));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(tl(e,t,n),e=e.sibling;e!==null;)tl(e,t,n),e=e.sibling}function nl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(nl(e,t,n),e=e.sibling;e!==null;)nl(e,t,n),e=e.sibling}function rl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[_t]=e,t[vt]=n}catch(t){Z(e,e.return,t)}}var il=!1,V=!1,al=!1,ol=typeof WeakSet==`function`?WeakSet:Set,H=null;function sl(e,t){if(e=e.containerInfo,Rd=sp,e=Lr(e),Rr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break a}var o=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=o+i),f!==a||r!==0&&f.nodeType!==3||(l=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=o),p===a&&++d===r&&(l=o),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,H=t;H!==null;)if(t=H,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,H=e;else for(;H!==null;){switch(t=H,a=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(a,r,n),a[_t]=e,O(a),r=a;break a;case`link`:var o=Vf(`link`,`href`,i).get(r+(n.href||``));if(o){for(var c=0;cg&&(o=g,g=h,h=o);var _=Fr(s,h),v=Fr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,w.T=null,n=lu,lu=null;var a=au,o=su;if(X=0,ou=au=null,su=0,W&6)throw Error(s(331));var c=W;if(W|=4,Il(a.current),Ol(a,a.current,o,n),W=c,id(0,!1),Ke&&typeof Ke.onPostCommitFiberRoot==`function`)try{Ke.onPostCommitFiberRoot(Ge,a)}catch{}return!0}finally{T.p=i,w.T=r,Vu(e,t)}}function Wu(e,t,n){t=Oi(n,t),t=rc(e.stateNode,t,2),e=Za(e,t,2),e!==null&&(st(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(iu===null||!iu.has(r))){e=Oi(n,e),n=ic(2),r=Za(t,n,2),r!==null&&(ac(n,r,t,e),st(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Bl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Wl=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,G===e&&(q&n)===n&&(Y===4||Y===3&&(q&62914560)===q&&300>Ie()-eu?!(W&2)&&Su(e,0):Jl|=n,Xl===q&&(Xl=0)),rd(e)}function qu(e,t){t===0&&(t=at()),e=pi(e,t),e!==null&&(st(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(s(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return Me(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Je(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=q,a=nt(r,r===G?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||rt(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Ie(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}X!==0&&X!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Jt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),O(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Jt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Jt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Jt(n.imageSizes)+`"]`)):i+=`[href="`+Jt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),O(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Jt(r)+`"][href="`+Jt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),O(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=kt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);O(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=kt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),O(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=kt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),O(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var i=(i=ve.current)?gf(i):null;if(!i)throw Error(s(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=kt(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var a=kt(i).hoistableStyles,o=a.get(e);if(o||(i=i.ownerDocument||i,o={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},a.set(e,o),(a=i.querySelector(jf(e)))&&!a._p&&(o.instance=a,o.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),a||Nf(i,e,n,o.state))),t&&r===null)throw Error(s(528,``));return o}if(t&&r!==null)throw Error(s(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=kt(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(s(444,e))}}function Af(e){return`href="`+Jt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),O(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Jt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Jt(n.href)+`"]`);if(r)return t.instance=r,O(r),r;var i=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),O(r),Pd(r,`style`,i),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Af(n.href);var a=e.querySelector(jf(i));if(a)return t.state.loading|=4,t.instance=a,O(a),a;r=Mf(n),(i=mf.get(i))&&Rf(r,i),a=(e.ownerDocument||e).createElement(`link`),O(a);var o=a;return o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),t.state.loading|=4,Lf(a,n.precedence,e),t.instance=a;case`script`:return a=Pf(n.src),(i=e.querySelector(Ff(a)))?(t.instance=i,O(i),i):(r=n,(i=mf.get(a))&&(r=h({},n),zf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),O(i),Pd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(s(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,O(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),O(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=s()})),l=n(),u=c(),d=e((e=>{var t=Symbol.for(`react.transitional.element`);function n(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.jsx=n,e.jsxs=n})),f=e(((e,t)=>{t.exports=d()}))();function p(){return(0,f.jsxs)(`div`,{className:`fixed inset-0 overflow-hidden pointer-events-none z-0`,children:[(0,f.jsx)(`div`,{className:`absolute inset-0 bg-gradient-to-b from-gray-900 via-gray-800 to-gray-900`}),(0,f.jsx)(`svg`,{className:`absolute bottom-0 left-0 right-0 h-64 md:h-96 opacity-20`,viewBox:`0 0 1440 320`,preserveAspectRatio:`none`,children:(0,f.jsx)(`path`,{fill:`#6366f1`,fillOpacity:`0.3`,d:`M0,224L48,213.3C96,203,192,181,288,181.3C384,181,480,203,576,224C672,245,768,267,864,261.3C960,256,1056,224,1152,197.3C1248,171,1344,149,1392,138.7L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z`})}),(0,f.jsx)(`svg`,{className:`absolute top-0 left-0 right-0 h-48 md:h-64 opacity-10`,viewBox:`0 0 1440 320`,preserveAspectRatio:`none`,children:(0,f.jsx)(`path`,{fill:`#818cf8`,fillOpacity:`0.4`,d:`M0,96L48,112C96,128,192,160,288,160C384,160,480,128,576,112C672,96,768,96,864,112C960,128,1056,160,1152,160C1248,160,1344,128,1392,112L1440,96L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z`})}),(0,f.jsx)(`div`,{className:`absolute top-1/4 left-1/4 w-96 h-96 bg-indigo-600 rounded-full mix-blend-screen filter blur-[128px] opacity-10 animate-pulse`}),(0,f.jsx)(`div`,{className:`absolute bottom-1/3 right-1/4 w-80 h-80 bg-cyan-500 rounded-full mix-blend-screen filter blur-[96px] opacity-8`})]})}function m(){return(0,f.jsx)(`section`,{id:`hero`,className:`relative z-10 min-h-screen flex items-center justify-center px-6`,children:(0,f.jsxs)(`div`,{className:`max-w-5xl mx-auto text-center space-y-8`,children:[(0,f.jsx)(`div`,{className:`inline-flex items-center gap-3 mb-4`,children:(0,f.jsx)(`span`,{className:`text-3xl font-bold text-4xl font-bold tracking-wider`,children:`🌊 tsunami`})}),(0,f.jsx)(`h1`,{className:`text-5xl md:text-7xl lg:text-8xl font-black tracking-tight leading-none bg-gradient-to-r from-indigo-400 via-cyan-400 to-indigo-400 bg-clip-text text-transparent`,children:`autonomous ai agent`}),(0,f.jsx)(`p`,{className:`text-xl md:text-2xl lg:text-3xl text-gray-300 max-w-3xl mx-auto font-light`,children:`local models. no cloud. no api keys.`}),(0,f.jsxs)(`div`,{className:`flex flex-col sm:flex-row gap-4 justify-center items-center pt-8`,children:[(0,f.jsxs)(`a`,{href:`https://github.com/gobbleyourdong/tsunami`,target:`_blank`,rel:`noopener noreferrer`,className:`group inline-flex items-center gap-3 px-8 py-4 bg-gradient-to-r from-indigo-600 to-cyan-600 rounded-full font-semibold text-lg hover:from-indigo-500 hover:to-cyan-500 transition-all duration-300 shadow-lg shadow-indigo-500/25 hover:shadow-indigo-500/40`,children:[(0,f.jsx)(`svg`,{className:`w-6 h-6`,fill:`currentColor`,viewBox:`0 0 24 24`,children:(0,f.jsx)(`path`,{fillRule:`evenodd`,d:`M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z`,clipRule:`evenodd`})}),`star on github`]}),(0,f.jsxs)(`a`,{href:`#how-it-works`,className:`inline-flex items-center gap-2 px-8 py-4 border-2 border-gray-600 rounded-full font-semibold text-lg hover:border-indigo-500 hover:text-indigo-400 transition-all duration-300`,children:[`how it works`,(0,f.jsx)(`svg`,{className:`w-5 h-5`,fill:`none`,viewBox:`0 0 24 24`,stroke:`currentColor`,children:(0,f.jsx)(`path`,{strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M19 14l-7 7m0 0l-7-7m7 7V3`})})]})]}),(0,f.jsx)(`div`,{className:`absolute bottom-8 left-1/2 -translate-x-1/2 animate-bounce`,children:(0,f.jsx)(`svg`,{className:`w-6 h-6 text-gray-400`,fill:`none`,viewBox:`0 0 24 24`,stroke:`currentColor`,children:(0,f.jsx)(`path`,{strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M19 14l-7 7m0 0l-7-7m7 7V3`})})})]})})}var h=[{id:`vision`,emoji:`👁️`,title:`vision`,description:`Tsunami sees what you see. Share screenshots, diagrams, or UI mockups and it will understand visual context to guide its actions precisely.`},{id:`codeact`,emoji:`⚡`,title:`codeact`,description:`Execute code with real-time feedback. Tsunami writes, runs, debugs, and iterates on code instantly, turning ideas into working software.`},{id:`function-calling`,emoji:`🔧`,title:`function calling`,description:`Intelligent tool use with structured outputs. Tsunami knows when and how to call external functions, APIs, and services seamlessly.`},{id:`image-gen`,emoji:`🎨`,title:`modules`,description:`Create stunning visuals on demand. From hero images to icons, Tsunami generates custom graphics that match your project aesthetic perfectly.`},{id:`tools`,emoji:`🛠️`,title:`17 tools`,description:`A complete toolkit for any task. File operations, web research, shell commands, data analysis — everything you need in one autonomous agent.`},{id:`install`,emoji:`🚀`,title:`one-click install`,description:`Get started in seconds. A single curl command installs Tsunami locally with zero configuration and full privacy — your data never leaves your machine.`}];function g(){return(0,f.jsx)(`section`,{id:`features`,className:`relative z-10 py-20 bg-gray-900`,children:(0,f.jsxs)(`div`,{className:`max-w-7xl mx-auto px-6`,children:[(0,f.jsx)(`h2`,{className:`text-4xl font-bold text-center text-white mb-16`,children:`everything you need to build`}),(0,f.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8`,children:h.map(e=>(0,f.jsxs)(`div`,{className:`bg-gray-800 rounded-xl p-8 hover:bg-gray-750 transition-colors border border-gray-700 hover:border-indigo-500`,children:[(0,f.jsx)(`span`,{className:`text-5xl mb-4 block`,children:e.emoji}),(0,f.jsx)(`h3`,{className:`text-xl font-semibold text-white mb-3`,children:e.title}),(0,f.jsx)(`p`,{className:`text-gray-300 leading-relaxed`,children:e.description})]},e.id))})]})})}function _(){return(0,f.jsx)(`section`,{className:`relative z-10 py-16 bg-indigo-600`,children:(0,f.jsx)(`div`,{className:`max-w-7xl mx-auto px-6`,children:(0,f.jsx)(`div`,{className:`grid grid-cols-2 md:grid-cols-4 gap-8`,children:[{number:`573`,label:`tests passing`},{number:`43`,label:`modules`},{number:`13K`,label:`lines of code`},{number:`100%`,label:`local privacy`}].map(e=>(0,f.jsxs)(`div`,{className:`text-center`,children:[(0,f.jsx)(`div`,{className:`text-5xl font-bold text-white mb-2`,children:e.number}),(0,f.jsx)(`div`,{className:`text-indigo-100 text-sm uppercase tracking-wide`,children:e.label})]},e.label))})})})}function v(){return(0,f.jsx)(`section`,{id:`how-it-works`,className:`relative z-10 py-20 bg-gray-950`,children:(0,f.jsxs)(`div`,{className:`max-w-7xl mx-auto px-6`,children:[(0,f.jsx)(`h2`,{className:`text-4xl font-bold text-center text-white mb-16`,children:`from idea to reality in minutes`}),(0,f.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-3 gap-12`,children:[{number:`01`,title:`install`,description:`Get Tsunami running locally with a single command. No account needed, no cloud dependencies.`,code:`curl -sSL https://raw.githubusercontent.com/gobbleyourdong/tsunami/main/setup.sh | bash`},{number:`02`,title:`talk`,description:`Describe your task in plain language. Tsunami understands context, asks clarifying questions, and plans autonomously.`,code:null},{number:`03`,title:`build`,description:`Watch as Tsunami executes, iterates, and delivers complete solutions — from prototypes to production-ready code.`,code:null}].map(e=>(0,f.jsxs)(`div`,{className:`relative`,children:[(0,f.jsx)(`span`,{className:`text-7xl font-bold text-indigo-500/20 absolute -top-8 -left-4`,children:e.number}),(0,f.jsxs)(`div`,{className:`relative z-10 pt-8`,children:[(0,f.jsx)(`h3`,{className:`text-2xl font-semibold text-white mb-4`,children:e.title}),(0,f.jsx)(`p`,{className:`text-gray-300 mb-6 leading-relaxed`,children:e.description}),e.code&&(0,f.jsxs)(`div`,{className:`bg-gray-900 rounded-lg p-3 border border-gray-700 relative group`,children:[(0,f.jsx)(`div`,{className:`overflow-x-auto`,children:(0,f.jsx)(`code`,{className:`text-indigo-400 font-mono text-xs whitespace-nowrap`,children:e.code})}),(0,f.jsx)(`button`,{onClick:()=>{navigator.clipboard.writeText(e.code);let t=document.activeElement;t.textContent=`✓`,setTimeout(()=>{t.textContent=`Copy`},1500)},className:`absolute top-2 right-2 text-xs bg-gray-700 hover:bg-indigo-600 text-gray-300 hover:text-white px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity`,children:`Copy`})]})]})]},e.number))})]})})}function y(){return(0,f.jsx)(`section`,{className:`relative z-10 py-24 px-6`,children:(0,f.jsxs)(`div`,{className:`max-w-4xl mx-auto text-center space-y-8`,children:[(0,f.jsx)(`svg`,{className:`w-32 h-32 mx-auto text-indigo-500`,fill:`currentColor`,viewBox:`0 0 24 24`,children:(0,f.jsx)(`path`,{d:`M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9v-2h2v2zm0-4H9V8h2v4zm4 4h-2v-2h2v2zm0-4h-2V8h2v4z`})}),(0,f.jsxs)(`h2`,{className:`text-4xl md:text-5xl font-bold`,children:[`ready to build with `,(0,f.jsx)(`span`,{className:`bg-gradient-to-r from-indigo-400 to-cyan-400 bg-clip-text text-transparent`,children:`tsunami?`})]}),(0,f.jsx)(`p`,{className:`text-xl text-gray-300 max-w-2xl mx-auto`,children:`Join the revolution of local AI agents. build websites, process data, generate content — all autonomously, all locally.`}),(0,f.jsx)(`div`,{className:`flex flex-col sm:flex-row gap-4 justify-center items-center pt-8`,children:(0,f.jsxs)(`a`,{href:`https://github.com/gobbleyourdong/tsunami`,target:`_blank`,rel:`noopener noreferrer`,className:`group inline-flex items-center gap-3 px-10 py-5 bg-gradient-to-r from-indigo-600 to-cyan-600 rounded-full font-bold text-lg hover:from-indigo-500 hover:to-cyan-500 transition-all duration-300 shadow-xl shadow-indigo-500/30 hover:shadow-indigo-500/50 hover:scale-105`,children:[(0,f.jsx)(`svg`,{className:`w-6 h-6`,fill:`currentColor`,viewBox:`0 0 24 24`,children:(0,f.jsx)(`path`,{fillRule:`evenodd`,d:`M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z`,clipRule:`evenodd`})}),`get started on github`]})}),(0,f.jsxs)(`div`,{className:`flex justify-center gap-8 pt-8 text-gray-400`,children:[(0,f.jsx)(`a`,{href:`https://github.com/gobbleyourdong/tsunami`,target:`_blank`,rel:`noopener noreferrer`,className:`hover:text-indigo-400 transition-colors`,children:`documentation`}),(0,f.jsx)(`span`,{className:`text-gray-600`,children:`•`}),(0,f.jsx)(`a`,{href:`https://github.com/gobbleyourdong/tsunami#examples`,target:`_blank`,rel:`noopener noreferrer`,className:`hover:text-indigo-400 transition-colors`,children:`examples`}),(0,f.jsx)(`span`,{className:`text-gray-600`,children:`•`}),(0,f.jsx)(`a`,{href:`https://github.com/gobbleyourdong/tsunami/issues`,target:`_blank`,rel:`noopener noreferrer`,className:`hover:text-indigo-400 transition-colors`,children:`issues`})]}),(0,f.jsx)(`div`,{className:`pt-16 border-t border-gray-800`,children:(0,f.jsx)(`p`,{className:`text-gray-500 text-sm`,children:`© 2024 Tsunami. Open source under MIT License. Built for the future of autonomous AI.`})})]})})}function b(){return(0,f.jsxs)(`div`,{className:`min-h-screen bg-gray-900 text-white`,children:[(0,f.jsx)(p,{}),(0,f.jsx)(m,{}),(0,f.jsx)(_,{}),(0,f.jsx)(g,{}),(0,f.jsx)(v,{}),(0,f.jsx)(y,{}),(0,f.jsx)(`footer`,{className:`py-8 bg-gray-950 border-t border-gray-800`,children:(0,f.jsx)(`div`,{className:`max-w-7xl mx-auto px-6 text-center`,children:(0,f.jsx)(`p`,{className:`text-gray-400 text-sm`,children:`this page was built by tsunami autonomously.`})})})]})}(0,u.createRoot)(document.getElementById(`root`)).render((0,f.jsx)(l.StrictMode,{children:(0,f.jsx)(b,{})})); \ No newline at end of file diff --git a/docs/assets/index-Ct2waydA.css b/docs/assets/index-Ct2waydA.css deleted file mode 100644 index c07f297..0000000 --- a/docs/assets/index-Ct2waydA.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-white:#fff;--spacing:.25rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--font-weight-light:300;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.-top-8{top:calc(var(--spacing) * -8)}.top-0{top:calc(var(--spacing) * 0)}.top-1\/4{top:25%}.top-2{top:calc(var(--spacing) * 2)}.right-0{right:calc(var(--spacing) * 0)}.right-1\/4{right:25%}.right-2{right:calc(var(--spacing) * 2)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-1\/3{bottom:33.3333%}.bottom-8{bottom:calc(var(--spacing) * 8)}.-left-4{left:calc(var(--spacing) * -4)}.left-0{left:calc(var(--spacing) * 0)}.left-1\/2{left:50%}.left-1\/4{left:25%}.z-0{z-index:0}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-16{margin-bottom:calc(var(--spacing) * 16)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-32{height:calc(var(--spacing) * 32)}.h-48{height:calc(var(--spacing) * 48)}.h-64{height:calc(var(--spacing) * 64)}.h-80{height:calc(var(--spacing) * 80)}.h-96{height:calc(var(--spacing) * 96)}.min-h-screen{min-height:100vh}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-32{width:calc(var(--spacing) * 32)}.w-80{width:calc(var(--spacing) * 80)}.w-96{width:calc(var(--spacing) * 96)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-7xl{max-width:var(--container-7xl)}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-pulse{animation:var(--animate-pulse)}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-12{gap:calc(var(--spacing) * 12)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab, red, red)){.bg-gray-800\/50{background-color:color-mix(in oklab, var(--color-gray-800) 50%, transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-400{--tw-gradient-from:var(--color-indigo-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-500{--tw-gradient-from:var(--color-indigo-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-600{--tw-gradient-from:var(--color-indigo-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-cyan-400{--tw-gradient-via:var(--color-cyan-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-800{--tw-gradient-via:var(--color-gray-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-cyan-400{--tw-gradient-to:var(--color-cyan-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-cyan-600{--tw-gradient-to:var(--color-cyan-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-indigo-400{--tw-gradient-to:var(--color-indigo-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-8{padding:calc(var(--spacing) * 8)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-10{padding-inline:calc(var(--spacing) * 10)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.py-20{padding-block:calc(var(--spacing) * 20)}.py-24{padding-block:calc(var(--spacing) * 24)}.py-32{padding-block:calc(var(--spacing) * 32)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-16{padding-top:calc(var(--spacing) * 16)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-indigo-100{color:var(--color-indigo-100)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-500{color:var(--color-indigo-500)}.text-indigo-500\/20{color:#625fff33}@supports (color:color-mix(in lab, red, red)){.text-indigo-500\/20{color:color-mix(in oklab, var(--color-indigo-500) 20%, transparent)}}.text-transparent{color:#0000}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.opacity-0{opacity:0}.opacity-8{opacity:.08}.opacity-10{opacity:.1}.opacity-20{opacity:.2}.mix-blend-screen{mix-blend-mode:screen}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-indigo-500\/25{--tw-shadow-color:#625fff40}@supports (color:color-mix(in lab, red, red)){.shadow-indigo-500\/25{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-indigo-500) 25%, transparent) var(--tw-shadow-alpha), transparent)}}.shadow-indigo-500\/30{--tw-shadow-color:#625fff4d}@supports (color:color-mix(in lab, red, red)){.shadow-indigo-500\/30{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-indigo-500) 30%, transparent) var(--tw-shadow-alpha), transparent)}}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-\[96px\]{--tw-blur:blur(96px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-\[128px\]{--tw-blur:blur(128px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}@media (hover:hover){.group-hover\:scale-110:is(:where(.group):hover *){--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:border-indigo-500:hover{border-color:var(--color-indigo-500)}.hover\:border-indigo-500\/50:hover{border-color:#625fff80}@supports (color:color-mix(in lab, red, red)){.hover\:border-indigo-500\/50:hover{border-color:color-mix(in oklab, var(--color-indigo-500) 50%, transparent)}}.hover\:bg-gray-800\/80:hover{background-color:#1e2939cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-gray-800\/80:hover{background-color:color-mix(in oklab, var(--color-gray-800) 80%, transparent)}}.hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:from-indigo-500:hover{--tw-gradient-from:var(--color-indigo-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.hover\:to-cyan-500:hover{--tw-gradient-to:var(--color-cyan-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.hover\:text-indigo-400:hover{color:var(--color-indigo-400)}.hover\:text-white:hover{color:var(--color-white)}.hover\:shadow-indigo-500\/40:hover{--tw-shadow-color:#625fff66}@supports (color:color-mix(in lab, red, red)){.hover\:shadow-indigo-500\/40:hover{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-indigo-500) 40%, transparent) var(--tw-shadow-alpha), transparent)}}.hover\:shadow-indigo-500\/50:hover{--tw-shadow-color:#625fff80}@supports (color:color-mix(in lab, red, red)){.hover\:shadow-indigo-500\/50:hover{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-indigo-500) 50%, transparent) var(--tw-shadow-alpha), transparent)}}}@media (width>=40rem){.sm\:flex-row{flex-direction:row}}@media (width>=48rem){.md\:flex{display:flex}.md\:h-64{height:calc(var(--spacing) * 64)}.md\:h-96{height:calc(var(--spacing) * 96)}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.md\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.lg\:text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}} diff --git a/docs/images/hero-wave.png b/docs/hero-bg.png similarity index 100% rename from docs/images/hero-wave.png rename to docs/hero-bg.png diff --git a/docs/images/code-icon.svg b/docs/images/code-icon.svg deleted file mode 100644 index a094676..0000000 --- a/docs/images/code-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - code-icon.svg - \ No newline at end of file diff --git a/docs/images/functions-icon.svg b/docs/images/functions-icon.svg deleted file mode 100644 index 5e5596b..0000000 --- a/docs/images/functions-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - functions-icon.svg - \ No newline at end of file diff --git a/docs/images/generate-icon.svg b/docs/images/generate-icon.svg deleted file mode 100644 index 0e607af..0000000 --- a/docs/images/generate-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - generate-icon.svg - \ No newline at end of file diff --git a/docs/images/install-icon.svg b/docs/images/install-icon.svg deleted file mode 100644 index 9a272c1..0000000 --- a/docs/images/install-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - install-icon.svg - \ No newline at end of file diff --git a/docs/images/tools-icon.svg b/docs/images/tools-icon.svg deleted file mode 100644 index aeae902..0000000 --- a/docs/images/tools-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - tools-icon.svg - \ No newline at end of file diff --git a/docs/images/vision-icon.svg b/docs/images/vision-icon.svg deleted file mode 100644 index c1f1eae..0000000 --- a/docs/images/vision-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - vision-icon.svg - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 542a65f..575d716 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,29 +1,267 @@ - + - - - - - TSUNAMI — Autonomous Execution Agent - - - - - -
- + + + + Tsunami - Autonomous AI Agent + + + + + + + +
+
+ Ocean waves +
+
+ +
+
+

tsunami

+

autonomous ai agent

+

when agents spawn, the swell rises

+ + + star on github + +
+ +
+
+
+
+
+ + +
+
+
+ 634 tests + 43 modules + 10GB stack + 5.9 tasks/sec +
+
+
+ + +
+
+

Architecture

+ +
+
+
🌊
+

wave

+

the brain (9B). reasons, plans, builds, coordinates everything.

+
+ +
+
+
🌀
+

eddies

+

parallel workers (2B). up to 32 simultaneous. sandboxed read-only.

+
+ +
+
🌊
+

swell

+

dispatches eddies in parallel. when agents spawn, the swell rises.

+
+ +
+
+
🏖️
+

break

+

where results converge. the wave meets the shore.

+
+ +
+
🌊
+

undertow

+

pulls back what's not ready. headless browser QA gate.

+
+
+ +
+ wave → swell → eddies → break → undertow → deliver +
+
+
+ + +
+
+

Features

+ +
+ +
+

Vision

+

+ Process images, screenshots, and visual content with deep analysis capabilities. Extract information from any visual input. +

+
+ + +
+

Function Calling

+

+ Execute external commands, API calls, and tool interactions with intelligent decision-making. Seamless integration with your environment. +

+
+ + +
+

Parallel Eddies

+

+ Dispatch up to 4 independent workers simultaneously for batch processing. Massive parallelization of complex tasks. +

+
+ + +
+

SD-Turbo

+

+ Accelerated Stable Diffusion integration for rapid image generation and manipulation within the agent workflow. +

+
+ + +
+

Sandbox

+

+ Safe execution environment for testing and development. Isolated processes with controlled resource allocation. +

+
+ + +
+

One-Click Install

+

+ Deploy instantly with a single command. No complex setup, no configuration hell—just run and go. +

+
+
+
+
+ + +
+
+

Install

+

Deploy Tsunami in seconds with our one-command installer

+ +
+ $ curl -sSL https://raw.githubusercontent.com/gobbleyourdong/tsunami/main/setup.sh | bash +
+ +

auto-scaling

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
memorymodewhat you get
4GBlite2B only, 1 eddy
12GB+full9B wave + eddies + SD-Turbo
32GB+full27B wave + 32 eddies + SD-Turbo
+
+
+
+ + +
+
+

© 2026 Tsunami - Autonomous AI Agent

+

when agents spawn, the swell rises

+
+
+ + + + diff --git a/installer/tsunami.iss b/installer/tsunami.iss new file mode 100644 index 0000000..b4d47f0 --- /dev/null +++ b/installer/tsunami.iss @@ -0,0 +1,77 @@ +; TSUNAMI — Inno Setup Installer +; Produces a standard Windows installer with progress bar, Start Menu entry, +; desktop shortcut, and Add/Remove Programs uninstaller. +; +; Build: iscc tsunami.iss +; Requires: Inno Setup 6+ (https://jrsoftware.org/issetup.php) +; +; The installer bundles the repo + runs setup.ps1 post-install to download +; models and llama-server. This keeps the installer small (~5MB) while the +; heavy downloads (~7GB) happen with a visible progress bar in PowerShell. + +#define MyAppName "Tsunami" +#define MyAppVersion "1.0" +#define MyAppPublisher "gobbleyourdong" +#define MyAppURL "https://github.com/gobbleyourdong/tsunami" +#define MyAppExeName "tsu.ps1" + +[Setup] +AppId={{B8F3A2E1-7D4C-4F8A-9E2B-1A3C5D7E9F0B} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL}/issues +DefaultDirName={autopf}\{#MyAppName} +DefaultGroupName={#MyAppName} +AllowNoIcons=yes +OutputBaseFilename=TsunamiSetup +SetupIconFile=..\desktop\icon.ico +Compression=lzma2/ultra64 +SolidCompression=yes +WizardStyle=modern +PrivilegesRequired=lowest +OutputDir=output + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +; Bundle the entire repo (excluding heavy stuff) +Source: "..\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs; Excludes: "*.gguf,node_modules,dist,.git,__pycache__,workspace,.venv,models,installer\output" + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\tsu.ps1"""; WorkingDir: "{app}"; IconFilename: "{app}\desktop\icon.ico" +Name: "{group}\Uninstall {#MyAppName}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\tsu.ps1"""; WorkingDir: "{app}"; IconFilename: "{app}\desktop\icon.ico"; Tasks: desktopicon + +[Tasks] +Name: "desktopicon"; Description: "Create a &desktop shortcut"; GroupDescription: "Additional shortcuts:" + +[Run] +; Run setup.ps1 after install — tell it to use the install directory for everything +Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""$env:TSUNAMI_DIR='{app}'; & '{app}\setup.ps1'"""; Description: "Download models and dependencies (~7GB)"; Flags: postinstall nowait shellexec; StatusMsg: "Setting up Tsunami..." + +[UninstallDelete] +Type: filesandordirs; Name: "{app}\models" +Type: filesandordirs; Name: "{app}\llama-server" +Type: filesandordirs; Name: "{app}\workspace" +Type: filesandordirs; Name: "{app}\node_modules" +Type: filesandordirs; Name: "{app}\.venv" + +[Code] +function InitializeSetup(): Boolean; +var + Msg: String; +begin + Msg := 'Tsunami will install the app (~5MB) then download AI models (~7GB).' + Chr(13) + Chr(10) + + Chr(13) + Chr(10) + + 'Requirements:' + Chr(13) + Chr(10) + + ' - Windows 10 or later' + Chr(13) + Chr(10) + + ' - 8GB+ RAM (or GPU VRAM)' + Chr(13) + Chr(10) + + ' - ~10GB free disk space' + Chr(13) + Chr(10) + + ' - Internet connection for model download' + Chr(13) + Chr(10) + + Chr(13) + Chr(10) + + 'Continue?'; + Result := MsgBox(Msg, mbConfirmation, MB_YESNO) = IDYES; +end; diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..45f9d14 --- /dev/null +++ b/plan.md @@ -0,0 +1,42 @@ +# Plan: Tsunami Development + +## Status: COMPLETE +- 10/10 test apps render from one-prompt runners +- 9 scaffolds, all compile clean +- 28 UI components per web scaffold (24 ui/ + 4 root) +- 9 threejs components (shaders, procedural, sprites, textures) +- 3 pixijs components (canvas, physics, sprite animator) +- 5 auto-fix layers (scaffold, swell, CSS, compile, wire) +- Pre-scaffold hidden step + requirement classifier +- IDE-style desktop UI with split panes + live preview +- Windows .exe + setup.bat + setup.sh — all check VRAM +- GitHub Actions builds .exe automatically +- v0.1.0 release published +- Stall detection + block repeated scaffold +- 501 token system prompt, 17 tools +- 27/27 tests pass + +## Component Library (28 UI components) +Base: Modal, Tabs, Toast, Badge +shadcn-lite: Dialog, Select, Skeleton, Progress, Avatar, Accordion, Alert, Tooltip, Switch, Dropdown +Fancy: StarRating, GlowCard, Parallax, AnimatedCounter +Niche: BeforeAfter, ColorPicker, Timeline, Kanban +CSS Effects: AnnouncementBar, Marquee, TypeWriter, GradientText +Interactive: ScrollReveal, Slideshow + +## Domain Components +threejs: Scene, Ground, Box, Sphere, HUD, ShaderMaterial (3 GLSL), ProceduralTerrain, ProceduralPlanet, SpriteSheet, TextureGen (4 generators) +pixijs: GameCanvas, Physics2D, SpriteAnimator, Puppet rig +landing: Navbar, Hero, Section, FeatureGrid, Footer, ParallaxHero, PortfolioGrid +dashboard: Layout, StatCard, DataTable, Card +form-app: FileDropzone, DataTable, parseFile +realtime: useWebSocket +fullstack: useApi + Express/SQLite CRUD + +## What's Next +- Windows installer testing with real users +- Mac installer testing (Aaron's friend) +- Richer README examples for all 28 UI components +- More test runners for edge cases +- CLI improvements (tab autocomplete, trace view) +- Undertow integrated into auto-build loop with live Vite diff --git a/requirements.txt b/requirements.txt index 2cc5c36..1fb7a73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,10 @@ -# Manus — The Resonant Ark -# Core dependencies - -httpx>=0.24.0 # HTTP client for model backends -pyyaml>=6.0 # Config file loading -rich>=13.0 # CLI output formatting - -# Optional — browser automation -# playwright>=1.40.0 # pip install playwright && playwright install chromium - -# Optional — web search -# duckduckgo-search>=4.0 # pip install duckduckgo-search - -# Optional — image viewing -# Pillow>=10.0 # pip install Pillow +# TSUNAMI — Core dependencies +httpx>=0.24.0 +pyyaml>=6.0 +rich>=13.0 +psutil>=5.9.0 +Pillow>=10.0 +ddgs>=9.0 +websockets>=12.0 +fastapi>=0.100.0 +uvicorn>=0.20.0 diff --git a/scaffolds/dashboard/README.md b/scaffolds/dashboard/README.md new file mode 100644 index 0000000..8272530 --- /dev/null +++ b/scaffolds/dashboard/README.md @@ -0,0 +1,103 @@ +# Dashboard Template + +React 19 + TypeScript + Vite + recharts. For data dashboards and admin panels. + +## Pre-built Components + +Import from `./components`: +- `Layout` — sidebar navigation + header + main content area +- `Card` — container with title and border +- `StatCard` — big number display (label, value, change %) +- `DataTable` — sortable table with column definitions + +## Build Loop + +1. Write types in `src/types.ts` +2. Use `Layout` as the outer shell with `navItems` +3. Fill with `StatCard` for metrics, `Card` for sections, `DataTable` for lists +4. Add recharts components for charts (`LineChart`, `BarChart`, `PieChart`) +5. Wire in `src/App.tsx` + +## Usage Examples + +```tsx +import { Layout, StatCard, Card, DataTable } from "./components" +import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts" + + +
+ + +
+ + + + + + + + +
+``` + +### Bar chart +```tsx +import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts" + + + + + + + + + +``` + +### Pie chart +```tsx +import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts" + +const COLORS = ["#0ff", "#f0f", "#ff0", "#0f0"] + + + + + {data.map((_, i) => )} + + + + +``` + +### Table with data +```tsx + +``` + +## File Structure + +``` +src/ + App.tsx ← Wire your dashboard here + types.ts ← Your data interfaces + components/ + Layout.tsx ← Sidebar + header (ready to use) + Card.tsx ← Content card (ready to use) + StatCard.tsx ← Metric display (ready to use) + DataTable.tsx ← Sortable table (ready to use) + index.ts ← Barrel exports +``` diff --git a/scaffolds/dashboard/index.html b/scaffolds/dashboard/index.html new file mode 100644 index 0000000..0ddabf9 --- /dev/null +++ b/scaffolds/dashboard/index.html @@ -0,0 +1,13 @@ + + + + + + Dashboard + + + +
+ + + diff --git a/scaffolds/dashboard/package-lock.json b/scaffolds/dashboard/package-lock.json new file mode 100644 index 0000000..c92ab75 --- /dev/null +++ b/scaffolds/dashboard/package-lock.json @@ -0,0 +1,2200 @@ +{ + "name": "dashboard", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dashboard", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "recharts": "^2.15.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "typescript": "~5.7.0", + "vite": "^6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "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.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "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.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "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.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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/@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.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "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/@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/baseline-browser-mapping": { + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", + "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", + "dev": true, + "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/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/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/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/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/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/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/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/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/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/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/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "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/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/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==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/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/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "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.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "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/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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/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/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/scaffolds/dashboard/package.json b/scaffolds/dashboard/package.json new file mode 100644 index 0000000..39cdc0c --- /dev/null +++ b/scaffolds/dashboard/package.json @@ -0,0 +1,18 @@ +{ + "name": "dashboard", + "private": true, + "type": "module", + "scripts": { "dev": "vite", "build": "vite build" }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "recharts": "^2.15.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "typescript": "~5.7.0", + "vite": "^6.0.0" + } +} diff --git a/scaffolds/dashboard/src/App.tsx b/scaffolds/dashboard/src/App.tsx new file mode 100644 index 0000000..90bd367 --- /dev/null +++ b/scaffolds/dashboard/src/App.tsx @@ -0,0 +1,16 @@ +import "./index.css" +// TODO: Replace with your dashboard +import { Layout, StatCard, Card } from "./components" + +export default function App() { + return ( + +
+ +
+ +

Replace this with your content.

+
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/Badge.tsx b/scaffolds/dashboard/src/components/Badge.tsx new file mode 100644 index 0000000..c2c3a60 --- /dev/null +++ b/scaffolds/dashboard/src/components/Badge.tsx @@ -0,0 +1,22 @@ +interface BadgeProps { + children: React.ReactNode + color?: string +} + +/** Small label badge — use for status, tags, counts. */ +export default function Badge({ children, color = "var(--accent)" }: BadgeProps) { + return ( + + {children} + + ) +} diff --git a/scaffolds/dashboard/src/components/Card.tsx b/scaffolds/dashboard/src/components/Card.tsx new file mode 100644 index 0000000..8ae8aa4 --- /dev/null +++ b/scaffolds/dashboard/src/components/Card.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react" + +interface CardProps { + title?: string + children: ReactNode + style?: React.CSSProperties +} + +/** A dashboard card — use for stats, charts, tables, anything. */ +export default function Card({ title, children, style }: CardProps) { + return ( +
+ {title && ( +

+ {title} +

+ )} + {children} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/DataTable.tsx b/scaffolds/dashboard/src/components/DataTable.tsx new file mode 100644 index 0000000..521788e --- /dev/null +++ b/scaffolds/dashboard/src/components/DataTable.tsx @@ -0,0 +1,52 @@ +interface Column { + key: string + label: string + width?: number +} + +interface DataTableProps { + columns: Column[] + rows: Record[] + onRowClick?: (row: Record) => void +} + +/** Sortable data table — pass columns + rows, get a table. */ +export default function DataTable({ columns, rows, onRowClick }: DataTableProps) { + return ( +
+ + + + {columns.map(col => ( + + ))} + + + + {rows.map((row, i) => ( + onRowClick?.(row)} + style={{ cursor: onRowClick ? "pointer" : "default" }} + onMouseEnter={e => (e.currentTarget.style.background = "#1f1f36")} + onMouseLeave={e => (e.currentTarget.style.background = "transparent")} + > + {columns.map(col => ( + + ))} + + ))} + +
+ {col.label} +
+ {row[col.key]} +
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/Layout.tsx b/scaffolds/dashboard/src/components/Layout.tsx new file mode 100644 index 0000000..c16a02e --- /dev/null +++ b/scaffolds/dashboard/src/components/Layout.tsx @@ -0,0 +1,72 @@ +import { ReactNode, useState } from "react" + +interface LayoutProps { + children: ReactNode + title?: string + navItems?: { label: string; id: string }[] + onNav?: (id: string) => void +} + +/** Dashboard layout with collapsible sidebar and header. */ +export default function Layout({ + children, + title = "Dashboard", + navItems = [], + onNav, +}: LayoutProps) { + const [collapsed, setCollapsed] = useState(false) + + return ( +
+ {/* Sidebar */} + + + {/* Main content */} +
+
+ {title} +
+
+ {children} +
+
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/Modal.tsx b/scaffolds/dashboard/src/components/Modal.tsx new file mode 100644 index 0000000..e55fca8 --- /dev/null +++ b/scaffolds/dashboard/src/components/Modal.tsx @@ -0,0 +1,33 @@ +import { ReactNode } from "react" + +interface ModalProps { + open: boolean + onClose: () => void + title?: string + children: ReactNode +} + +/** Simple modal dialog. Use open/onClose to control visibility. */ +export default function Modal({ open, onClose, title, children }: ModalProps) { + if (!open) return null + + return ( +
+
e.stopPropagation()} + > + {title &&

{title}

} + {children} +
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/StatCard.tsx b/scaffolds/dashboard/src/components/StatCard.tsx new file mode 100644 index 0000000..0586134 --- /dev/null +++ b/scaffolds/dashboard/src/components/StatCard.tsx @@ -0,0 +1,30 @@ +interface StatCardProps { + label: string + value: string | number + change?: string + color?: string +} + +/** Big number stat display — "Revenue: $12.4K ↑ 12%" */ +export default function StatCard({ label, value, change, color = "#0ff" }: StatCardProps) { + return ( +
+
+ {label} +
+
+ {value} +
+ {change && ( +
+ {change} +
+ )} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/Toast.tsx b/scaffolds/dashboard/src/components/Toast.tsx new file mode 100644 index 0000000..14a84b5 --- /dev/null +++ b/scaffolds/dashboard/src/components/Toast.tsx @@ -0,0 +1,40 @@ +import { useState, useEffect, useCallback } from "react" + +interface ToastMessage { + id: number + text: string + type: "success" | "error" | "info" +} + +let toastId = 0 +let addToastFn: ((text: string, type?: "success" | "error" | "info") => void) | null = null + +/** Show a toast from anywhere: toast("Saved!") or toast("Error", "error") */ +export function toast(text: string, type: "success" | "error" | "info" = "info") { + addToastFn?.(text, type) +} + +/** Place once in your App. Then call toast() from anywhere. */ +export default function ToastContainer() { + const [toasts, setToasts] = useState([]) + + addToastFn = useCallback((text: string, type: "success" | "error" | "info" = "info") => { + const id = ++toastId + setToasts(prev => [...prev, { id, text, type }]) + setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 3000) + }, []) + + if (toasts.length === 0) return null + + const colors = { success: "#4f4", error: "#f44", info: "var(--accent)" } + + return ( +
+ {toasts.map(t => ( +
+ {t.text} +
+ ))} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/index.ts b/scaffolds/dashboard/src/components/index.ts new file mode 100644 index 0000000..5b39465 --- /dev/null +++ b/scaffolds/dashboard/src/components/index.ts @@ -0,0 +1,7 @@ +export { default as Layout } from "./Layout" +export { default as Card } from "./Card" +export { default as StatCard } from "./StatCard" +export { default as DataTable } from "./DataTable" +export { default as Modal } from "./Modal" +export { default as ToastContainer, toast } from "./Toast" +export { default as Badge } from "./Badge" diff --git a/scaffolds/dashboard/src/components/ui/Accordion.tsx b/scaffolds/dashboard/src/components/ui/Accordion.tsx new file mode 100644 index 0000000..891157e --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Accordion.tsx @@ -0,0 +1,43 @@ +import { useState, ReactNode } from "react" + +interface AccordionItem { + title: string + content: ReactNode +} + +interface AccordionProps { + items: AccordionItem[] + multiple?: boolean +} + +export default function Accordion({ items, multiple = false }: AccordionProps) { + const [open, setOpen] = useState>(new Set()) + + const toggle = (i: number) => { + setOpen(prev => { + const next = new Set(multiple ? prev : []) + if (prev.has(i)) next.delete(i); else next.add(i) + return next + }) + } + + return ( +
+ {items.map((item, i) => ( +
+ + {open.has(i) &&
{item.content}
} +
+ ))} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Alert.tsx b/scaffolds/dashboard/src/components/ui/Alert.tsx new file mode 100644 index 0000000..97af0d7 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Alert.tsx @@ -0,0 +1,28 @@ +import { ReactNode } from "react" + +interface AlertProps { + type?: "info" | "success" | "warning" | "error" + title?: string + children: ReactNode +} + +const colors = { + info: "var(--accent)", + success: "#44cc44", + warning: "#ffaa00", + error: "#ff4444", +} + +export default function Alert({ type = "info", title, children }: AlertProps) { + const c = colors[type] + return ( +
+ {title &&
{title}
} +
{children}
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/AnimatedCounter.tsx b/scaffolds/dashboard/src/components/ui/AnimatedCounter.tsx new file mode 100644 index 0000000..d350f6f --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/AnimatedCounter.tsx @@ -0,0 +1,36 @@ +import { useEffect, useState, useRef } from "react" + +interface AnimatedCounterProps { + value: number + duration?: number // ms + prefix?: string + suffix?: string + style?: React.CSSProperties +} + +/** Animated number counter — scrolls from 0 to value. */ +export default function AnimatedCounter({ value, duration = 1000, prefix = "", suffix = "", style }: AnimatedCounterProps) { + const [display, setDisplay] = useState(0) + const ref = useRef(null) + const started = useRef(false) + + useEffect(() => { + const observer = new IntersectionObserver(entries => { + if (entries[0].isIntersecting && !started.current) { + started.current = true + const start = performance.now() + const animate = (now: number) => { + const progress = Math.min((now - start) / duration, 1) + const eased = 1 - Math.pow(1 - progress, 3) // ease-out cubic + setDisplay(Math.round(eased * value)) + if (progress < 1) requestAnimationFrame(animate) + } + requestAnimationFrame(animate) + } + }) + if (ref.current) observer.observe(ref.current) + return () => observer.disconnect() + }, [value, duration]) + + return {prefix}{display.toLocaleString()}{suffix} +} diff --git a/scaffolds/dashboard/src/components/ui/AnnouncementBar.tsx b/scaffolds/dashboard/src/components/ui/AnnouncementBar.tsx new file mode 100644 index 0000000..efed13a --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/AnnouncementBar.tsx @@ -0,0 +1,45 @@ +import { useState, useEffect } from "react" + +interface AnnouncementBarProps { + text: string + link?: { text: string; href: string } + countdownTo?: string // ISO date for countdown + dismissable?: boolean + variant?: "default" | "urgent" | "promo" +} + +/** Sticky announcement bar with optional countdown + dismiss. */ +export default function AnnouncementBar({ text, link, countdownTo, dismissable = true, variant = "default" }: AnnouncementBarProps) { + const [dismissed, setDismissed] = useState(false) + const [countdown, setCountdown] = useState("") + + useEffect(() => { + if (!countdownTo) return + const target = new Date(countdownTo).getTime() + const tick = () => { + const diff = target - Date.now() + if (diff <= 0) { setCountdown("Expired"); return } + const h = Math.floor(diff / 3600000) + const m = Math.floor((diff % 3600000) / 60000) + const s = Math.floor((diff % 60000) / 1000) + setCountdown(`${h}h ${String(m).padStart(2,"0")}m ${String(s).padStart(2,"0")}s`) + } + tick() + const id = setInterval(tick, 1000) + return () => clearInterval(id) + }, [countdownTo]) + + if (dismissed) return null + + const colors = { default: "var(--accent)", urgent: "#dc2626", promo: "#6366f1" } + const bg = colors[variant] + + return ( +
+ {text} + {countdown && {countdown}} + {link && {link.text}} + {dismissable && } +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Avatar.tsx b/scaffolds/dashboard/src/components/ui/Avatar.tsx new file mode 100644 index 0000000..f631ca6 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Avatar.tsx @@ -0,0 +1,25 @@ +interface AvatarProps { + src?: string + name?: string + size?: number + color?: string +} + +export default function Avatar({ src, name = "?", size = 40, color = "var(--accent)" }: AvatarProps) { + const initials = name.split(" ").map(w => w[0]).join("").toUpperCase().slice(0, 2) + + if (src) { + return {name} + } + + return ( +
+ {initials} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/BeforeAfter.tsx b/scaffolds/dashboard/src/components/ui/BeforeAfter.tsx new file mode 100644 index 0000000..a8edbcf --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/BeforeAfter.tsx @@ -0,0 +1,36 @@ +import { useState, useRef, useCallback } from "react" + +interface BeforeAfterProps { + before: string // image URL + after: string // image URL + height?: number +} + +/** Drag slider to compare two images. */ +export default function BeforeAfter({ before, after, height = 400 }: BeforeAfterProps) { + const [pos, setPos] = useState(50) + const ref = useRef(null) + const dragging = useRef(false) + + const update = useCallback((x: number) => { + if (!ref.current) return + const rect = ref.current.getBoundingClientRect() + setPos(Math.max(0, Math.min(100, ((x - rect.left) / rect.width) * 100))) + }, []) + + return ( +
{ dragging.current = true; (e.target as HTMLElement).setPointerCapture(e.pointerId); update(e.clientX) }} + onPointerMove={e => dragging.current && update(e.clientX)} + onPointerUp={() => dragging.current = false} + > + +
+ +
+
+
+
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/ColorPicker.tsx b/scaffolds/dashboard/src/components/ui/ColorPicker.tsx new file mode 100644 index 0000000..35bb137 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/ColorPicker.tsx @@ -0,0 +1,41 @@ +import { useState } from "react" + +interface ColorPickerProps { + value: string + onChange: (color: string) => void + presets?: string[] +} + +const DEFAULT_PRESETS = ["#ff4444", "#ff8800", "#ffcc00", "#44cc44", "#00cccc", "#4488ff", "#8844ff", "#ff44aa", "#ffffff", "#888888", "#333333", "#000000"] + +/** Color picker with presets and hex input. */ +export default function ColorPicker({ value, onChange, presets = DEFAULT_PRESETS }: ColorPickerProps) { + const [show, setShow] = useState(false) + + return ( +
+
setShow(!show)} style={{ + width: 36, height: 36, borderRadius: "var(--radius)", border: "2px solid var(--border)", + background: value, cursor: "pointer", + }} /> + {show && ( +
+
+ {presets.map(c => ( +
{ onChange(c); setShow(false) }} style={{ + width: 28, height: 28, borderRadius: 4, background: c, cursor: "pointer", + border: c === value ? "2px solid var(--accent)" : "1px solid var(--border)", + }} /> + ))} +
+ onChange(e.target.value)} style={{ width: "100%", height: 32, cursor: "pointer", border: "none", background: "none" }} /> +
+ )} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Dialog.tsx b/scaffolds/dashboard/src/components/ui/Dialog.tsx new file mode 100644 index 0000000..2691da9 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Dialog.tsx @@ -0,0 +1,24 @@ +import { ReactNode } from "react" + +interface DialogProps { + open: boolean + onClose: () => void + title?: string + description?: string + children?: ReactNode + actions?: ReactNode +} + +export default function Dialog({ open, onClose, title, description, children, actions }: DialogProps) { + if (!open) return null + return ( +
+
e.stopPropagation()}> + {title &&

{title}

} + {description &&

{description}

} + {children} + {actions &&
{actions}
} +
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Dropdown.tsx b/scaffolds/dashboard/src/components/ui/Dropdown.tsx new file mode 100644 index 0000000..823ec0d --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Dropdown.tsx @@ -0,0 +1,50 @@ +import { useState, useRef, useEffect, ReactNode } from "react" + +interface DropdownItem { + label: string + onClick: () => void + icon?: string +} + +interface DropdownProps { + trigger: ReactNode + items: DropdownItem[] +} + +export default function Dropdown({ trigger, items }: DropdownProps) { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + useEffect(() => { + const handler = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) + } + document.addEventListener("mousedown", handler) + return () => document.removeEventListener("mousedown", handler) + }, []) + + return ( +
+
setOpen(!open)} style={{ cursor: "pointer" }}>{trigger}
+ {open && ( +
+ {items.map((item, i) => ( + + ))} +
+ )} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/GlowCard.tsx b/scaffolds/dashboard/src/components/ui/GlowCard.tsx new file mode 100644 index 0000000..758f687 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/GlowCard.tsx @@ -0,0 +1,44 @@ +import { ReactNode, useRef, useState } from "react" + +interface GlowCardProps { + children: ReactNode + color?: string + style?: React.CSSProperties +} + +/** Card with a glow effect that follows the mouse. */ +export default function GlowCard({ children, color = "var(--accent)", style }: GlowCardProps) { + const ref = useRef(null) + const [pos, setPos] = useState({ x: 0, y: 0 }) + const [hover, setHover] = useState(false) + + const handleMove = (e: React.MouseEvent) => { + if (!ref.current) return + const rect = ref.current.getBoundingClientRect() + setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top }) + } + + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: "relative", overflow: "hidden", + background: "var(--bg-card)", border: "1px solid var(--border)", + borderRadius: 12, padding: 24, ...style, + }} + > + {hover && ( +
+ )} +
{children}
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/GradientText.tsx b/scaffolds/dashboard/src/components/ui/GradientText.tsx new file mode 100644 index 0000000..ec6406f --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/GradientText.tsx @@ -0,0 +1,39 @@ +import { ReactNode } from "react" + +interface GradientTextProps { + children: ReactNode + from?: string + to?: string + via?: string + animate?: boolean + style?: React.CSSProperties +} + +/** Text with gradient color — optionally animated. */ +export default function GradientText({ + children, + from = "#00cccc", + to = "#cc00cc", + via, + animate = false, + style, +}: GradientTextProps) { + const gradient = via + ? `linear-gradient(90deg, ${from}, ${via}, ${to})` + : `linear-gradient(90deg, ${from}, ${to})` + + return ( + + {animate && } + {children} + + ) +} diff --git a/scaffolds/dashboard/src/components/ui/Kanban.tsx b/scaffolds/dashboard/src/components/ui/Kanban.tsx new file mode 100644 index 0000000..5a01717 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Kanban.tsx @@ -0,0 +1,55 @@ +import { useState, ReactNode } from "react" + +interface KanbanCard { + id: string + title: string + description?: string +} + +interface KanbanColumn { + id: string + title: string + cards: KanbanCard[] +} + +interface KanbanProps { + columns: KanbanColumn[] + onMove?: (cardId: string, fromCol: string, toCol: string) => void +} + +/** Pre-built Kanban board — click cards to move to next column. */ +export default function Kanban({ columns, onMove }: KanbanProps) { + return ( +
+ {columns.map((col, ci) => ( +
+
+ {col.title} + {col.cards.length} +
+
+ {col.cards.map(card => ( +
{ + const nextCol = columns[ci + 1] + if (nextCol && onMove) onMove(card.id, col.id, nextCol.id) + }} + style={{ + padding: 12, background: "var(--bg)", borderRadius: 6, + border: "1px solid var(--border)", cursor: ci < columns.length - 1 ? "pointer" : "default", + fontSize: 13, + }} + > +
{card.title}
+ {card.description &&
{card.description}
} +
+ ))} +
+
+ ))} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Marquee.tsx b/scaffolds/dashboard/src/components/ui/Marquee.tsx new file mode 100644 index 0000000..4c03f93 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Marquee.tsx @@ -0,0 +1,32 @@ +import { ReactNode } from "react" + +interface MarqueeProps { + children: ReactNode + speed?: number // seconds for one full cycle + direction?: "left" | "right" + pauseOnHover?: boolean +} + +/** CSS-only infinite scrolling marquee — logos, testimonials, etc. */ +export default function Marquee({ children, speed = 20, direction = "left", pauseOnHover = true }: MarqueeProps) { + const dir = direction === "left" ? "marquee-left" : "marquee-right" + + return ( +
+ +
pauseOnHover && (e.currentTarget.style.animationPlayState = "paused")} + onMouseLeave={e => pauseOnHover && (e.currentTarget.style.animationPlayState = "running")} + > + {children}{children} +
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Parallax.tsx b/scaffolds/dashboard/src/components/ui/Parallax.tsx new file mode 100644 index 0000000..bbe1aa0 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Parallax.tsx @@ -0,0 +1,34 @@ +import { useEffect, useRef, useState, ReactNode } from "react" + +interface ParallaxProps { + children: ReactNode + speed?: number // 0.1 = slow, 1 = normal scroll, 2 = fast + style?: React.CSSProperties +} + +/** Parallax scrolling section — content moves at a different speed than scroll. */ +export default function Parallax({ children, speed = 0.5, style }: ParallaxProps) { + const ref = useRef(null) + const [offset, setOffset] = useState(0) + + useEffect(() => { + const handler = () => { + if (!ref.current) return + const rect = ref.current.getBoundingClientRect() + const center = rect.top + rect.height / 2 + const viewCenter = window.innerHeight / 2 + setOffset((center - viewCenter) * (1 - speed)) + } + window.addEventListener("scroll", handler, { passive: true }) + handler() + return () => window.removeEventListener("scroll", handler) + }, [speed]) + + return ( +
+
+ {children} +
+
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Progress.tsx b/scaffolds/dashboard/src/components/ui/Progress.tsx new file mode 100644 index 0000000..b1fea09 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Progress.tsx @@ -0,0 +1,18 @@ +interface ProgressProps { + value: number // 0-100 + color?: string + height?: number + showLabel?: boolean +} + +export default function Progress({ value, color = "var(--accent)", height = 8, showLabel = false }: ProgressProps) { + const clamped = Math.max(0, Math.min(100, value)) + return ( +
+
+
+
+ {showLabel &&
{clamped}%
} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/ScrollReveal.tsx b/scaffolds/dashboard/src/components/ui/ScrollReveal.tsx new file mode 100644 index 0000000..48d9739 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/ScrollReveal.tsx @@ -0,0 +1,57 @@ +import { useEffect, useRef, useState, ReactNode } from "react" + +interface ScrollRevealProps { + children: ReactNode + direction?: "up" | "down" | "left" | "right" | "fade" + delay?: number // ms + duration?: number // ms + distance?: number // px + once?: boolean // only animate once +} + +/** Reveal content on scroll into viewport. */ +export default function ScrollReveal({ + children, + direction = "up", + delay = 0, + duration = 600, + distance = 30, + once = true, +}: ScrollRevealProps) { + const ref = useRef(null) + const [visible, setVisible] = useState(false) + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true) + if (once) observer.disconnect() + } else if (!once) { + setVisible(false) + } + }, + { threshold: 0.1 } + ) + if (ref.current) observer.observe(ref.current) + return () => observer.disconnect() + }, [once]) + + const transforms: Record = { + up: `translateY(${distance}px)`, + down: `translateY(-${distance}px)`, + left: `translateX(${distance}px)`, + right: `translateX(-${distance}px)`, + fade: "none", + } + + return ( +
+ {children} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Select.tsx b/scaffolds/dashboard/src/components/ui/Select.tsx new file mode 100644 index 0000000..66b3cc5 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Select.tsx @@ -0,0 +1,26 @@ +interface SelectProps { + value: string + onChange: (value: string) => void + options: { value: string; label: string }[] + placeholder?: string +} + +export default function Select({ value, onChange, options, placeholder }: SelectProps) { + return ( + + ) +} diff --git a/scaffolds/dashboard/src/components/ui/Skeleton.tsx b/scaffolds/dashboard/src/components/ui/Skeleton.tsx new file mode 100644 index 0000000..4cbf088 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Skeleton.tsx @@ -0,0 +1,20 @@ +interface SkeletonProps { + width?: string | number + height?: string | number + radius?: number + style?: React.CSSProperties +} + +export default function Skeleton({ width = "100%", height = 20, radius = 6, style }: SkeletonProps) { + return ( +
+ +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Slideshow.tsx b/scaffolds/dashboard/src/components/ui/Slideshow.tsx new file mode 100644 index 0000000..887fb89 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Slideshow.tsx @@ -0,0 +1,47 @@ +import { useState, useEffect } from "react" + +interface SlideshowProps { + images: string[] + interval?: number // ms between slides + height?: number + showDots?: boolean + showArrows?: boolean +} + +/** Auto-advancing image slideshow with dots and arrows. */ +export default function Slideshow({ images, interval = 4000, height = 400, showDots = true, showArrows = true }: SlideshowProps) { + const [current, setCurrent] = useState(0) + + useEffect(() => { + if (images.length <= 1) return + const id = setInterval(() => setCurrent(i => (i + 1) % images.length), interval) + return () => clearInterval(id) + }, [images.length, interval]) + + const go = (dir: number) => setCurrent(i => (i + dir + images.length) % images.length) + + return ( +
+ {images.map((img, i) => ( + + ))} + {showArrows && images.length > 1 && <> + + + } + {showDots && images.length > 1 && ( +
+ {images.map((_, i) => ( +
setCurrent(i)} style={{ + width: 8, height: 8, borderRadius: "50%", cursor: "pointer", + background: i === current ? "var(--accent)" : "rgba(255,255,255,0.4)", + }} /> + ))} +
+ )} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/StarRating.tsx b/scaffolds/dashboard/src/components/ui/StarRating.tsx new file mode 100644 index 0000000..60ec983 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/StarRating.tsx @@ -0,0 +1,25 @@ +interface StarRatingProps { + rating: number + max?: number + size?: number + color?: string + onChange?: (rating: number) => void +} + +export default function StarRating({ rating, max = 5, size = 20, color = "#ffaa00", onChange }: StarRatingProps) { + return ( +
+ {Array.from({ length: max }, (_, i) => ( + onChange?.(i + 1)} + > + + + ))} +
+ ) +} diff --git a/scaffolds/dashboard/src/components/ui/Switch.tsx b/scaffolds/dashboard/src/components/ui/Switch.tsx new file mode 100644 index 0000000..5ae47b0 --- /dev/null +++ b/scaffolds/dashboard/src/components/ui/Switch.tsx @@ -0,0 +1,24 @@ +interface SwitchProps { + checked: boolean + onChange: (checked: boolean) => void + label?: string +} + +export default function Switch({ checked, onChange, label }: SwitchProps) { + return ( +