commit 61e145a442dd23d7e8c8895eedd467f30365d553 Author: Kansaram Date: Sun Jun 7 18:19:23 2026 +0530 feat: launch NEXUS TERMINAL — Bloomberg-style FX options analytics platform Full-stack forex options analytics terminal with Bloomberg-inspired UI. Backend (FastAPI + Python): - Garman-Kohlhagen options pricing engine with full Greeks - Goldman Sachs gs-quant AI signals (RSI, MACD, Bollinger, Hurst, OU) - Monte Carlo GBM simulation and volatility surface generation - CFTC COT institutional positioning + Forex Factory economic calendar - Live data proxy: OpenSky aircraft + USGS earthquakes (CORS-safe) - Multi-leg strategy library (straddle, iron condor, butterfly, spreads) Frontend (React 18 + Vite): - NEXUS animated orbital logo (3-ring SVG) + canvas favicon animation - Bloomberg terminal design: JetBrains Mono, color-mix() tokens - 11 dashboard tabs: Greeks, Chart, AI Signals, 3D Surfaces, Breakeven, Scenarios, Monte Carlo, Institutional, Calendar, Live Map, Live Feeds - Live World Map (react-leaflet): aircraft, earthquakes, weather radar - Live Feeds: CoinGecko crypto top-12 + Windy.com global webcams - Economic calendar with filters + institutional flow (CFTC COT) - Animated landing page + session-based routing - Fully responsive dark-only terminal design system Co-Authored-By: Claude Sonnet 4.6 diff --git a/.github/workflows/python-check.yml b/.github/workflows/python-check.yml new file mode 100644 index 0000000..41fec41 --- /dev/null +++ b/.github/workflows/python-check.yml @@ -0,0 +1,49 @@ +name: Python Check + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Verify script executes without error + # Use a non-interactive matplotlib backend so plots don't open a window + env: + MPLBACKEND: Agg + run: | + cd src + python -c " + import sys, os + sys.path.insert(0, os.path.abspath('.')) + from config import * + from greeks import portfolio_greeks + from surface import risk_surface, pnl_surface + import numpy as np + + S_range = np.linspace(SPOT_LOW, SPOT_HIGH, 10) + sigma_range = np.linspace(VOL_LOW, VOL_HIGH, 10) + D, G, V, Th = risk_surface(EXAMPLE_PORTFOLIO, S_range, sigma_range, DEFAULT_TIME_TO_EXPIRY, DEFAULT_RISK_FREE_RATE) + P = pnl_surface(EXAMPLE_PORTFOLIO, S_range, sigma_range, DEFAULT_TIME_TO_EXPIRY, DEFAULT_RISK_FREE_RATE, DEFAULT_SPOT, DEFAULT_VOLATILITY) + assert D.shape == (10, 10), 'Delta surface shape mismatch' + assert G.shape == (10, 10), 'Gamma surface shape mismatch' + assert V.shape == (10, 10), 'Vega surface shape mismatch' + assert Th.shape == (10, 10), 'Theta surface shape mismatch' + assert P.shape == (10, 10), 'PnL surface shape mismatch' + print('All checks passed.') + " diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..744d0de --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# ── Python ─────────────────────────────────────────── +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.Python +*.egg-info/ +dist/ +build/ +.eggs/ +*.egg +*.whl + +# Virtual environments +.venv/ +venv/ +env/ +ENV/ +.env +.env.* +!.env.example + +# ── Node / Frontend ─────────────────────────────────── +node_modules/ +frontend/node_modules/ +frontend/dist/ +frontend/.vite/ +*.local + +# ── IDE / Editor ────────────────────────────────────── +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store +Thumbs.db +desktop.ini + +# ── Logs ───────────────────────────────────────────── +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +vite.log + +# ── Testing / Coverage ──────────────────────────────── +.coverage +htmlcov/ +.pytest_cache/ +.playwright-mcp/ + +# ── Generated / Temp ────────────────────────────────── +*.tmp +*.temp +src/plots/*.png + +# ── Secrets ─────────────────────────────────────────── +*.pem +*.key +secrets.json +credentials.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..afe85f5 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +
+ +# ⚛ NEXUS TERMINAL + +### Institutional-Grade FX Options Analytics Platform + +[![Python](https://img.shields.io/badge/Python-3.11+-3776AB?style=flat-square&logo=python&logoColor=white)](https://python.org) +[![FastAPI](https://img.shields.io/badge/FastAPI-0.110+-009688?style=flat-square&logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com) +[![React](https://img.shields.io/badge/React-18+-61DAFB?style=flat-square&logo=react&logoColor=black)](https://react.dev) +[![Vite](https://img.shields.io/badge/Vite-5+-646CFF?style=flat-square&logo=vite&logoColor=white)](https://vitejs.dev) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE) + +*Three orbital rings. One terminal. Every market.* + +![Landing Page](screenshots/01-landing.png) + +
+ +--- + +## What is NEXUS? + +NEXUS TERMINAL is a Bloomberg-style web application for professional forex options analysis. It combines **Garman-Kohlhagen options pricing**, **Goldman Sachs gs-quant signals**, **live global data feeds**, and a **real-time world map** — all in one free, no-account-required platform. + +![Dashboard](screenshots/02-dashboard.png) + +--- + +## Features + +### Options Analytics +| Module | Description | +|--------|-------------| +| **Greeks Dashboard** | Δ Delta · Γ Gamma · ν Vega · Θ Theta · ρ Rho · φ Phi — per leg and portfolio total via Garman-Kohlhagen (1983) | +| **Volatility Surfaces** | Interactive 3D surface plots (Delta, Gamma, Vega, Theta, P&L) across full spot × vol grid | +| **Breakeven Profile** | Payoff curve at expiry with breakeven strikes, max profit/loss, net premium | +| **Scenario Analysis** | Stress tests across spot ±5%/±10% and vol ±1pp/±2pp shocks | +| **Monte Carlo** | GBM path simulation — up to 5,000 paths, terminal P&L distribution, probability of profit | +| **Strategy Library** | Pre-built multi-leg strategies: straddle, strangle, bull/bear spreads, iron condor, butterfly | + +### Live Intelligence + +![AI Signals](screenshots/03-ai-signals.png) + +| Module | Description | +|--------|-------------| +| **AI Quant Signals** | 9 signals: RSI, MACD, Bollinger Bands, Hurst exponent, mean reversion (OU), momentum, carry, volatility regime — powered by gs-quant | +| **Institutional Flow** | CFTC COT positioning — Asset Managers, Hedge Funds, Dealers — with Volume Profile, POC, VAH, VAL | +| **Economic Calendar** | Forex Factory events (120+ per week) with impact levels, forecasts, countdowns, and currency filters | + +### Live World Map + +![Live Map](screenshots/04-live-map.png) + +| Layer | Source | Refresh | +|-------|--------|---------| +| ✈ Aircraft positions | OpenSky Network (free, anonymous) | 30s | +| 🔴 Earthquake markers | USGS GeoJSON feed (M4.5+, 7 days) | 5min | +| 🌦 Weather radar | RainViewer animated tiles | on-demand | +| 📍 Forex events | Forex Factory via backend cache | with calendar | + +### Live Feeds + +![Live Feeds](screenshots/05-live-feeds.png) + +- **Crypto prices** — Top 12 by market cap via CoinGecko free API, 60s refresh +- **Global webcams** — Windy.com live camera feeds for 6 financial centers: New York · London · Tokyo · Frankfurt · Singapore · Dubai + +### Economic Calendar + +![Calendar](screenshots/06-calendar.png) + +--- + +## Tech Stack + +``` +NEXUS TERMINAL +├── backend/ FastAPI (Python 3.11+) +│ └── app/ +│ ├── core/ Garman-Kohlhagen · Greeks · Monte Carlo · Quant Analysis +│ └── routers/ REST API endpoints +│ ├── greeks.py Options pricing & Greeks +│ ├── surface.py Volatility surface grid +│ ├── montecarlo.py GBM simulation +│ ├── scenarios.py Stress testing +│ ├── forex.py Live FX rates +│ ├── institutional.py CFTC COT data +│ ├── news.py Forex Factory calendar +│ └── livedata.py Aircraft & earthquake proxy +│ +└── frontend/ React 18 + Vite 5 + └── src/ + ├── components/ + │ ├── GreeksDashboard.jsx + │ ├── QuantSignals.jsx (gs-quant AI signals) + │ ├── WorldMap.jsx (Leaflet + live layers) + │ ├── LiveFeeds.jsx (Crypto + Webcams) + │ ├── EconomicCalendar.jsx + │ ├── InstitutionalFlow.jsx + │ └── NexusLogo.jsx (animated SVG + canvas favicon) + └── pages/ + ├── LandingPage.jsx + └── Dashboard.jsx +``` + +### Key Libraries + +**Backend** +- `fastapi` — REST API framework +- `gs-quant` — Goldman Sachs quant library (RSI, MACD, Bollinger, volatility) +- `numpy` / `scipy` — Monte Carlo, OU process, Hurst exponent +- `httpx` — Async HTTP client for proxying live data + +**Frontend** +- `react-leaflet` + `leaflet` — Interactive world map with live layers +- `plotly.js` — 3D volatility surface plots +- `zustand` — Global portfolio state management +- `JetBrains Mono` — Bloomberg-terminal monospace font + +--- + +## Getting Started + +### Prerequisites +- Python 3.11+ +- Node.js 18+ + +### Backend + +```bash +cd backend +python -m venv .venv + +# Windows +.venv\Scripts\activate +# macOS/Linux +source .venv/bin/activate + +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8000 +``` + +API available at `http://localhost:8000` · Docs at `http://localhost:8000/docs` + +### Frontend + +```bash +cd frontend +npm install +npm run dev +``` + +App available at `http://localhost:5173` + +--- + +## API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/greeks` | Compute portfolio Greeks (Garman-Kohlhagen) | +| `POST` | `/api/surface` | Generate volatility surface grid | +| `POST` | `/api/montecarlo` | Run GBM Monte Carlo simulation | +| `POST` | `/api/scenarios` | Stress-test scenario matrix | +| `GET` | `/api/forex/rates` | Live FX spot rates | +| `GET` | `/api/news/calendar` | Forex Factory economic calendar | +| `GET` | `/api/institutional/{pair}` | CFTC COT positioning data | +| `GET` | `/api/signals/{pair}` | AI quant signals (gs-quant) | +| `GET` | `/api/live/aircraft` | Live aircraft positions (OpenSky proxy) | +| `GET` | `/api/live/earthquakes` | USGS earthquake feed proxy | + +--- + +## Design System + +NEXUS uses a Bloomberg terminal-inspired design: +- **Colors** — Navy dark (`#080e1a`) base, cyan (`#00c8f0`) accent, semantic red/green/amber/purple +- **Typography** — JetBrains Mono for all data panels, `tabular-nums` for price columns +- **Panels** — 3px left-border accent system (worldmonitor-style) +- **Tokens** — `color-mix(in srgb, ...)` for semi-transparent semantic backgrounds + +--- + +## Data Sources + +| Source | Data | Cost | +|--------|------|------| +| Goldman Sachs gs-quant | RSI, MACD, Bollinger, volatility | Free (open-source) | +| OpenSky Network | Live aircraft positions | Free, anonymous | +| USGS Earthquake API | M4.5+ seismic events | Free | +| RainViewer | Weather radar tiles | Free | +| Forex Factory | Economic calendar (120+ events/week) | Free | +| CoinGecko | Crypto prices (top 12) | Free | +| Windy.com | Global webcam feeds | Free embed | +| Yahoo Finance (via yfinance) | FX historical rates | Free | + +--- + +## License + +MIT © 2026 — Built with ⚛ NEXUS TERMINAL diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/black_scholes.py b/backend/app/core/black_scholes.py new file mode 100644 index 0000000..a712e6c --- /dev/null +++ b/backend/app/core/black_scholes.py @@ -0,0 +1,49 @@ +import numpy as np +from scipy.stats import norm + + +def _d1_d2(S, K, T, r, sigma): + S = np.asarray(S, dtype=float) + sqrt_T = np.sqrt(T) + d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sqrt_T) + d2 = d1 - sigma * sqrt_T + return d1, d2 + + +def bs_price(S, K, T, r, sigma, option_type="call"): + d1, d2 = _d1_d2(S, K, T, r, sigma) + if option_type == "call": + return float(np.asarray(S) * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)) + return float(K * np.exp(-r * T) * norm.cdf(-d2) - np.asarray(S) * norm.cdf(-d1)) + + +def bs_delta(S, K, T, r, sigma, option_type="call"): + d1, _ = _d1_d2(S, K, T, r, sigma) + return float(norm.cdf(d1) if option_type == "call" else norm.cdf(d1) - 1.0) + + +def bs_gamma(S, K, T, r, sigma): + d1, _ = _d1_d2(S, K, T, r, sigma) + return float(norm.pdf(d1) / (np.asarray(S) * sigma * np.sqrt(T))) + + +def bs_vega(S, K, T, r, sigma): + d1, _ = _d1_d2(S, K, T, r, sigma) + return float(np.asarray(S) * norm.pdf(d1) * np.sqrt(T)) + + +def bs_theta(S, K, T, r, sigma, option_type="call"): + d1, d2 = _d1_d2(S, K, T, r, sigma) + S = np.asarray(S) + decay = -(S * norm.pdf(d1) * sigma) / (2 * np.sqrt(T)) + if option_type == "call": + return float(decay - r * K * np.exp(-r * T) * norm.cdf(d2)) + return float(decay + r * K * np.exp(-r * T) * norm.cdf(-d2)) + + +def bs_rho(S, K, T, r, sigma, option_type="call"): + """Rho — sensitivity to interest rate changes.""" + _, d2 = _d1_d2(S, K, T, r, sigma) + if option_type == "call": + return float(K * T * np.exp(-r * T) * norm.cdf(d2)) + return float(-K * T * np.exp(-r * T) * norm.cdf(-d2)) diff --git a/backend/app/core/garman_kohlhagen.py b/backend/app/core/garman_kohlhagen.py new file mode 100644 index 0000000..c7cd1c0 --- /dev/null +++ b/backend/app/core/garman_kohlhagen.py @@ -0,0 +1,105 @@ +""" +garman_kohlhagen.py — Garman-Kohlhagen (1983) model for European forex options. + +Extension of Black-Scholes that accounts for BOTH the domestic and foreign +risk-free interest rates — essential for currency options pricing. + +Reference: Garman, M.B. & Kohlhagen, S.W. (1983). "Foreign currency option values." + Journal of International Money and Finance, 2(3), 231–237. + +Model: + d1 = [ln(S/K) + (r_d − r_f + σ²/2)·T] / (σ·√T) + d2 = d1 − σ·√T + C = S·e^(−r_f·T)·N(d1) − K·e^(−r_d·T)·N(d2) + P = K·e^(−r_d·T)·N(−d2) − S·e^(−r_f·T)·N(−d1) +""" + +import numpy as np +from scipy.stats import norm + + +def _d1_d2(S: float, K: float, T: float, r_d: float, r_f: float, sigma: float): + """Compute GK d1 and d2 intermediate values.""" + S = np.asarray(S, dtype=float) + sqrt_T = np.sqrt(T) + d1 = (np.log(S / K) + (r_d - r_f + 0.5 * sigma ** 2) * T) / (sigma * sqrt_T) + d2 = d1 - sigma * sqrt_T + return d1, d2 + + +def gk_price(S, K, T, r_d, r_f, sigma, option_type="call"): + d1, d2 = _d1_d2(S, K, T, r_d, r_f, sigma) + S = np.asarray(S, dtype=float) + if option_type == "call": + return float(S * np.exp(-r_f * T) * norm.cdf(d1) - K * np.exp(-r_d * T) * norm.cdf(d2)) + return float(K * np.exp(-r_d * T) * norm.cdf(-d2) - S * np.exp(-r_f * T) * norm.cdf(-d1)) + + +def gk_delta(S, K, T, r_d, r_f, sigma, option_type="call"): + """ + Delta — sensitivity of option price to spot rate change. + Call: e^(−r_f·T)·N(d1) Put: −e^(−r_f·T)·N(−d1) + """ + d1, _ = _d1_d2(S, K, T, r_d, r_f, sigma) + factor = np.exp(-r_f * T) + if option_type == "call": + return float(factor * norm.cdf(d1)) + return float(-factor * norm.cdf(-d1)) + + +def gk_gamma(S, K, T, r_d, r_f, sigma): + """ + Gamma — rate of change of delta. + Γ = e^(−r_f·T)·N'(d1) / (S·σ·√T) (same sign for calls and puts) + """ + d1, _ = _d1_d2(S, K, T, r_d, r_f, sigma) + return float(np.exp(-r_f * T) * norm.pdf(d1) / (np.asarray(S) * sigma * np.sqrt(T))) + + +def gk_vega(S, K, T, r_d, r_f, sigma): + """ + Vega — sensitivity to implied volatility. + ν = S·e^(−r_f·T)·N'(d1)·√T (same for calls and puts) + """ + d1, _ = _d1_d2(S, K, T, r_d, r_f, sigma) + return float(np.asarray(S) * np.exp(-r_f * T) * norm.pdf(d1) * np.sqrt(T)) + + +def gk_theta(S, K, T, r_d, r_f, sigma, option_type="call"): + """ + Theta — time decay (per year). + Call: −S·σ·e^(−r_f·T)·N'(d1)/(2√T) − r_d·K·e^(−r_d·T)·N(d2) + r_f·S·e^(−r_f·T)·N(d1) + Put: −S·σ·e^(−r_f·T)·N'(d1)/(2√T) + r_d·K·e^(−r_d·T)·N(−d2) − r_f·S·e^(−r_f·T)·N(−d1) + """ + d1, d2 = _d1_d2(S, K, T, r_d, r_f, sigma) + S = np.asarray(S, dtype=float) + decay = -(S * sigma * np.exp(-r_f * T) * norm.pdf(d1)) / (2 * np.sqrt(T)) + if option_type == "call": + return float(decay - r_d * K * np.exp(-r_d * T) * norm.cdf(d2) + + r_f * S * np.exp(-r_f * T) * norm.cdf(d1)) + return float(decay + r_d * K * np.exp(-r_d * T) * norm.cdf(-d2) + - r_f * S * np.exp(-r_f * T) * norm.cdf(-d1)) + + +def gk_rho_d(S, K, T, r_d, r_f, sigma, option_type="call"): + """ + Rho_d — sensitivity to DOMESTIC interest rate. + Call: K·T·e^(−r_d·T)·N(d2) Put: −K·T·e^(−r_d·T)·N(−d2) + """ + _, d2 = _d1_d2(S, K, T, r_d, r_f, sigma) + factor = K * T * np.exp(-r_d * T) + if option_type == "call": + return float(factor * norm.cdf(d2)) + return float(-factor * norm.cdf(-d2)) + + +def gk_phi(S, K, T, r_d, r_f, sigma, option_type="call"): + """ + Phi (ρ_f) — sensitivity to FOREIGN interest rate. + Call: −S·T·e^(−r_f·T)·N(d1) Put: S·T·e^(−r_f·T)·N(−d1) + """ + d1, _ = _d1_d2(S, K, T, r_d, r_f, sigma) + factor = np.asarray(S) * T * np.exp(-r_f * T) + if option_type == "call": + return float(-factor * norm.cdf(d1)) + return float(factor * norm.cdf(-d1)) diff --git a/backend/app/core/greeks.py b/backend/app/core/greeks.py new file mode 100644 index 0000000..2aedd82 --- /dev/null +++ b/backend/app/core/greeks.py @@ -0,0 +1,41 @@ +"""greeks.py — Portfolio-level Greek aggregation using Garman-Kohlhagen model.""" + +from .garman_kohlhagen import ( + gk_delta, gk_gamma, gk_vega, gk_theta, gk_rho_d, gk_phi +) + + +def portfolio_greeks( + options: list[dict], S: float, sigma: float, + T: float, r_d: float, r_f: float +) -> dict: + """Aggregate GK Greeks across all legs of a forex options portfolio.""" + total = {"delta": 0.0, "gamma": 0.0, "vega": 0.0, + "theta": 0.0, "rho_d": 0.0, "phi": 0.0} + legs = [] + + for opt in options: + otype = opt["type"] + K, qty = float(opt["K"]), float(opt["qty"]) + leg_T = float(opt.get("T", T)) + + d = gk_delta(S, K, leg_T, r_d, r_f, sigma, otype) * qty + g = gk_gamma(S, K, leg_T, r_d, r_f, sigma) * qty + v = gk_vega(S, K, leg_T, r_d, r_f, sigma) * qty + th = gk_theta(S, K, leg_T, r_d, r_f, sigma, otype) * qty + rho = gk_rho_d(S, K, leg_T, r_d, r_f, sigma, otype) * qty + phi = gk_phi(S, K, leg_T, r_d, r_f, sigma, otype) * qty + + total["delta"] += d; total["gamma"] += g; total["vega"] += v + total["theta"] += th; total["rho_d"] += rho; total["phi"] += phi + + legs.append({ + "label": f"{'+'if qty>0 else ''}{int(qty)} {otype.upper()} K={K}", + "delta": round(d, 5), "gamma": round(g, 7), + "vega": round(v, 4), "theta": round(th, 4), + }) + + return { + "total": {k: round(v, 6) for k, v in total.items()}, + "legs": legs, + } diff --git a/backend/app/core/montecarlo.py b/backend/app/core/montecarlo.py new file mode 100644 index 0000000..f12f033 --- /dev/null +++ b/backend/app/core/montecarlo.py @@ -0,0 +1,44 @@ +"""montecarlo.py — GBM Monte Carlo for forex options (uses GK cost basis).""" + +import numpy as np +from .garman_kohlhagen import gk_price + + +def run_montecarlo(options, S0, sigma, r_d, r_f, T, n_paths=1000, n_steps=100): + """Simulate GBM price paths and compute P&L distribution at expiry.""" + dt = T / n_steps + Z = np.random.standard_normal((n_paths, n_steps)) + paths = np.zeros((n_paths, n_steps + 1)); paths[:,0] = S0 + + # GBM: dS = (r_d - r_f)·S·dt + σ·S·dW (Garman-Kohlhagen drift) + for t in range(1, n_steps + 1): + paths[:,t] = paths[:,t-1] * np.exp( + (r_d - r_f - 0.5*sigma**2)*dt + sigma*np.sqrt(dt)*Z[:,t-1] + ) + + terminal = paths[:,-1] + pnl = np.zeros(n_paths) + for opt in options: + K, qty = float(opt["K"]), float(opt["qty"]) + payoff = (np.maximum(terminal - K, 0) if opt["type"]=="call" + else np.maximum(K - terminal, 0)) + pnl += payoff * qty + + # Subtract initial GK cost + cost = sum( + gk_price(S0, float(o["K"]), float(o.get("T",T)), r_d, r_f, sigma, o["type"]) + * float(o["qty"]) for o in options + ) + pnl -= cost + + idx = np.random.choice(n_paths, size=min(60, n_paths), replace=False) + return { + "time_axis": [round(i*dt, 4) for i in range(n_steps+1)], + "sample_paths": paths[idx].tolist(), + "pnl": pnl.tolist(), + "pnl_mean": round(float(pnl.mean()), 5), + "pnl_std": round(float(pnl.std()), 5), + "pnl_5pct": round(float(np.percentile(pnl, 5)), 5), + "pnl_95pct": round(float(np.percentile(pnl, 95)), 5), + "prob_profit": round(float((pnl > 0).mean()), 4), + } diff --git a/backend/app/core/quant_analysis.py b/backend/app/core/quant_analysis.py new file mode 100644 index 0000000..b19f1d4 --- /dev/null +++ b/backend/app/core/quant_analysis.py @@ -0,0 +1,274 @@ +""" +quant_analysis.py — Quantitative signals for forex pairs. + +Core analytics powered by: + • gs-quant (Goldman Sachs, 2024) — volatility, RSI, MACD, Bollinger Bands, + max drawdown, z-scores, rolling statistics + • Custom implementations for models not in gs-quant: + 1. EWMA Vol Forecast — RiskMetrics λ=0.94 (JP Morgan, 1994) + 2. VaR / CVaR — Parametric Normal + Historical ES (Basel III) + 3. Hurst Exponent — Variance-scaling (Hurst 1951) + 4. Ornstein-Uhlenbeck — OLS AR(1) (Uhlenbeck & Ornstein 1930) + 5. Carry Signal — Uncovered Interest Parity (Fama 1984) + 6. Momentum — Price momentum (Jegadeesh & Titman 1993) +""" + +import numpy as np +import pandas as pd +from scipy import stats + +# GS-Quant timeseries — all work offline, no credentials required +from gs_quant.timeseries import econometrics as gseco +from gs_quant.timeseries import statistics as gsstat +from gs_quant.timeseries import technicals as gstech + + +# ─── helpers ────────────────────────────────────────────────────────────────── + +def _to_series(prices) -> pd.Series: + arr = np.array(prices, dtype=float) + idx = pd.date_range(end=pd.Timestamp.today().normalize(), periods=len(arr), freq='D') + return pd.Series(arr, index=idx) + + +def _safe_last(series: pd.Series, default=0.0) -> float: + try: + v = series.dropna() + return float(v.iloc[-1]) if len(v) else default + except Exception: + return default + + +def _hurst_exponent(prices: np.ndarray) -> float: + """Variance-scaling H estimator: Var[ΔX_τ] ~ τ^(2H). (Hurst 1951)""" + prices = np.array(prices, dtype=float) + max_lag = min(len(prices) // 3, 30) + if max_lag < 3: + return 0.5 + lags = range(2, max_lag) + variances = [np.var(np.diff(prices, n=lag)) for lag in lags] + try: + slope, *_ = stats.linregress(np.log(list(lags)), np.log(variances)) + return float(np.clip(slope / 2, 0.01, 0.99)) + except Exception: + return 0.5 + + +def _estimate_ou_params(prices: np.ndarray) -> dict: + """OLS AR(1) → OU parameters. (Uhlenbeck & Ornstein 1930)""" + X = np.array(prices, dtype=float) + Xl, Xc = X[:-1], X[1:] + n = len(Xl) + b_num = n * np.dot(Xl, Xc) - Xl.sum() * Xc.sum() + b_den = n * np.dot(Xl, Xl) - Xl.sum() ** 2 + b = float(np.clip(b_num / b_den if b_den else 0.999, 0.001, 0.9999)) + a = float(Xc.mean() - b * Xl.mean()) + resid_sigma = float(np.std(Xc - (a + b * Xl)) * np.sqrt(252)) + kappa = float(-np.log(b) * 252) + theta = float(a / (1 - b)) + half_life = float(np.log(2) / max(kappa, 1e-6) / 252 * 365) + return {"kappa": round(kappa, 4), "theta": round(theta, 6), + "sigma": round(resid_sigma, 6), "half_life_days": round(half_life, 1)} + + +# ─── main signal engine ─────────────────────────────────────────────────────── + +def compute_signals(prices: list, r_d: float = 0.05, r_f: float = 0.04) -> dict: + """ + Full quant signal suite — core analytics via gs-quant (Goldman Sachs), + extended with OU, Hurst, VaR and carry trade signals. + """ + px_raw = np.array(prices, dtype=float) + n = len(px_raw) + if n < 10: + return {"error": "Need ≥10 observations."} + + px = _to_series(px_raw) + w20 = min(20, n) + w60 = min(60, n) + rets_raw = np.diff(np.log(px_raw)) + + # ── 1. Volatility (gs-quant econometrics.volatility) ──────────────────── + # GS implementation: annualized realized vol over rolling window + hv20_s = gseco.volatility(px, w20) + hv60_s = gseco.volatility(px, w60) + hv20 = _safe_last(hv20_s, np.std(rets_raw[-w20:]) * np.sqrt(252)) + hv60 = _safe_last(hv60_s, np.std(rets_raw[-w60:]) * np.sqrt(252)) + vol_regime = ("HIGH" if hv20 > hv60 * 1.25 else + "LOW" if hv20 < hv60 * 0.80 else "NORMAL") + + # EWMA forecast (RiskMetrics λ=0.94, JP Morgan 1994) + lam, ewma_var = 0.94, float(np.var(rets_raw[-w20:])) + for r in rets_raw[-w20:]: + ewma_var = lam * ewma_var + (1 - lam) * r ** 2 + ewma_vol = float(np.sqrt(ewma_var * 252)) + + # GS max-drawdown (institutional risk metric) + dd_s = gseco.max_drawdown(px, w60) + max_dd = _safe_last(dd_s, -0.01) + + # ── 2. VaR / CVaR (custom — Parametric Normal + Basel III) ───────────── + mu_d, sig_d = float(np.mean(rets_raw[-w20:])), float(np.std(rets_raw[-w20:])) + var95 = float(-(mu_d + sig_d * stats.norm.ppf(0.05))) + var99 = float(-(mu_d + sig_d * stats.norm.ppf(0.01))) + tail = np.sort(rets_raw[-w60:])[:max(1, int(0.05 * w60))] + cvar95 = float(-np.mean(tail)) + + # ── 3. Hurst Exponent (custom — Hurst 1951) ───────────────────────────── + hurst = _hurst_exponent(px_raw[-w60:]) + hurst_regime = ("MEAN-REVERTING" if hurst < 0.45 else + "TRENDING" if hurst > 0.55 else "RANDOM WALK") + hurst_action = {"MEAN-REVERTING": "FADE extreme moves — OU strategies apply", + "TRENDING": "FOLLOW momentum — trend-following applies", + "RANDOM WALK": "No structural edge — vol strategies apply"}[hurst_regime] + + # ── 4. Ornstein-Uhlenbeck (custom — Uhlenbeck & Ornstein 1930) ────────── + ou = _estimate_ou_params(px_raw[-w60:]) + ou_std = ou["sigma"] / np.sqrt(max(ou["kappa"], 0.01) * 252) + + # Z-score via gs-quant statistics (institutional grade) + z_s = gsstat.zscores(px, w20) + gs_z = _safe_last(z_s, 0.0) + ou_z = float((px_raw[-1] - ou["theta"]) / max(ou_std, 1e-9)) + ou_signal = "SELL" if ou_z > 2 else ("BUY" if ou_z < -2 else "NEUTRAL") + ou_conf = min(92, 50 + int(abs(ou_z) * 15)) if ou_signal != "NEUTRAL" else 40 + + # ── 5. RSI (gs-quant technicals.relative_strength_index) ──────────────── + rsi_s = gstech.relative_strength_index(px, 14) + rsi = _safe_last(rsi_s, 50.0) + rsi_signal = ("OVERBOUGHT" if rsi > 70 else + "OVERSOLD" if rsi < 30 else "NEUTRAL") + rsi_bias = ("BEARISH" if rsi > 70 else + "BULLISH" if rsi < 30 else "NEUTRAL") + + # ── 6. MACD (gs-quant technicals.macd) ────────────────────────────────── + macd_s = gstech.macd(px) + macd = _safe_last(macd_s, 0.0) + macd_signal = "BULLISH" if macd > 0 else "BEARISH" + + # ── 7. Bollinger Bands (gs-quant technicals.bollinger_bands) ──────────── + bb_s = gstech.bollinger_bands(px, w20) + current_px = float(px_raw[-1]) + sma_s = gstech.moving_average(px, w20) + sma = _safe_last(sma_s, current_px) + std_s = gsstat.std(px, w20) + gsstd = _safe_last(std_s, float(np.std(px_raw[-w20:]))) + bb_upper = sma + 2.0 * gsstd + bb_lower = sma - 2.0 * gsstd + bb_pct = float((current_px - bb_lower) / max(bb_upper - bb_lower, 1e-9)) # 0=lower,1=upper + bb_signal = ("NEAR UPPER BAND" if bb_pct > 0.85 else + "NEAR LOWER BAND" if bb_pct < 0.15 else "MID BAND") + bb_bias = ("BEARISH" if bb_pct > 0.85 else + "BULLISH" if bb_pct < 0.15 else "NEUTRAL") + + # ── 8. Momentum (Jegadeesh & Titman 1993) ─────────────────────────────── + ret5 = float(px_raw[-1] / px_raw[max(-5, -n)] - 1) if n >= 5 else 0.0 + ret20 = float(px_raw[-1] / px_raw[max(-20, -n)] - 1) if n >= 20 else 0.0 + mom_signal = ("BULLISH" if ret5 > 0 and ret20 > 0 else + "BEARISH" if ret5 < 0 and ret20 < 0 else "MIXED") + + # ── 9. Carry (Uncovered Interest Parity, Fama 1984) ───────────────────── + carry_diff = r_d - r_f + carry_signal = ("BUY BASE" if carry_diff > 0.005 else + "SELL BASE" if carry_diff < -0.005 else "NEUTRAL") + + # ── Composite signal (8 inputs, gs-quant enhanced) ──────────────────────── + bull = sum([ + ret5 > 0, ret20 > 0, + ou_signal == "BUY", + carry_signal == "BUY BASE", + macd_signal == "BULLISH", + rsi_bias == "BULLISH", + bb_bias == "BULLISH", + hurst_regime == "TRENDING" and ret5 > 0, + ]) + bear = sum([ + ret5 < 0, ret20 < 0, + ou_signal == "SELL", + carry_signal == "SELL BASE", + macd_signal == "BEARISH", + rsi_bias == "BEARISH", + bb_bias == "BEARISH", + hurst_regime == "TRENDING" and ret5 < 0, + ]) + if bull >= bear + 3: + composite, conf = "BULLISH", min(95, 50 + bull * 6) + elif bear >= bull + 3: + composite, conf = "BEARISH", min(95, 50 + bear * 6) + elif bull > bear: + composite, conf = "BULLISH", min(70, 50 + bull * 4) + elif bear > bull: + composite, conf = "BEARISH", min(70, 50 + bear * 4) + else: + composite, conf = "NEUTRAL", 45 + + return { + "composite": composite, + "confidence": conf, + "n_observations": n, + "powered_by": "gs-quant (Goldman Sachs) + custom quant models", + + "volatility": { + "hv_20_pct": round(hv20, 2), + "hv_60_pct": round(hv60, 2), + "regime": vol_regime, + "ewma_forecast_pct": round(ewma_vol * 100, 2), + "max_drawdown_pct": round(max_dd * 100, 2), + "method": "gs-quant econometrics.volatility() + EWMA λ=0.94 (RiskMetrics 1994)", + }, + "risk": { + "var_95_pct": round(var95 * 100, 3), + "var_99_pct": round(var99 * 100, 3), + "cvar_95_pct": round(cvar95 * 100, 3), + "method": "Parametric Normal VaR / Historical CVaR (Basel III)", + }, + "hurst": { + "exponent": round(hurst, 3), + "regime": hurst_regime, + "action": hurst_action, + "method": "Variance-scaling estimator (Hurst 1951)", + }, + "mean_reversion": { + "kappa": ou["kappa"], + "theta": ou["theta"], + "half_life_days": ou["half_life_days"], + "zscore": round(ou_z, 2), + "gs_zscore": round(gs_z, 2), + "signal": ou_signal, + "confidence": ou_conf, + "method": "OLS AR(1) → OU SDE (Uhlenbeck & Ornstein 1930) + gs-quant zscores", + }, + "rsi": { + "value": round(rsi, 2), + "signal": rsi_signal, + "bias": rsi_bias, + "method": "gs-quant technicals.relative_strength_index(14) (Wilder 1978)", + }, + "macd": { + "value": round(macd, 6), + "signal": macd_signal, + "method": "gs-quant technicals.macd() — 12/26/9 EMA crossover (Appel 1979)", + }, + "bollinger": { + "upper": round(bb_upper, 5), + "lower": round(bb_lower, 5), + "sma": round(sma, 5), + "pct_b": round(bb_pct, 3), + "signal": bb_signal, + "bias": bb_bias, + "method": "gs-quant technicals.bollinger_bands(20,2σ) (Bollinger 1983)", + }, + "momentum": { + "return_5d_pct": round(ret5 * 100, 3), + "return_20d_pct": round(ret20 * 100, 3), + "signal": mom_signal, + "method": "Price momentum (Jegadeesh & Titman 1993)", + }, + "carry": { + "r_d": round(r_d * 100, 2), + "r_f": round(r_f * 100, 2), + "differential_pct": round(carry_diff * 100, 2), + "signal": carry_signal, + "method": "Uncovered Interest Parity (Fama 1984)", + }, + } diff --git a/backend/app/core/surface.py b/backend/app/core/surface.py new file mode 100644 index 0000000..12847a7 --- /dev/null +++ b/backend/app/core/surface.py @@ -0,0 +1,33 @@ +"""surface.py — 2-D risk surface computation over spot × vol grid.""" + +import numpy as np +from .greeks import portfolio_greeks +from .garman_kohlhagen import gk_price + + +def compute_surfaces(options, S_range, sigma_range, T, r_d, r_f): + """Compute Delta, Gamma, Vega, Theta, and P&L surfaces.""" + n_s, n_v = len(S_range), len(sigma_range) + D = np.zeros((n_s, n_v)); G = np.zeros((n_s, n_v)) + V = np.zeros((n_s, n_v)); Th = np.zeros((n_s, n_v)) + + for i, S in enumerate(S_range): + for j, sigma in enumerate(sigma_range): + g = portfolio_greeks(options, S, sigma, T, r_d, r_f)["total"] + D[i,j]=g["delta"]; G[i,j]=g["gamma"] + V[i,j]=g["vega"]; Th[i,j]=g["theta"] + + # P&L via Delta-Gamma approx around grid midpoint + S0 = S_range[len(S_range)//2] + sig0 = sigma_range[len(sigma_range)//2] + base = portfolio_greeks(options, S0, sig0, T, r_d, r_f)["total"] + PnL = np.zeros((n_s, n_v)) + for i, S in enumerate(S_range): + dS = S - S0 + PnL[i,:] = base["delta"]*dS + 0.5*base["gamma"]*dS**2 + + return { + "S_range": S_range.tolist(), "sigma_range": sigma_range.tolist(), + "delta": D.tolist(), "gamma": G.tolist(), + "vega": V.tolist(), "theta": Th.tolist(), "pnl": PnL.tolist(), + } diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..4fc405b --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from .routers import greeks, surface, market, montecarlo, scenarios, strategies, export, forex, institutional, news, livedata + +app = FastAPI(title="QuantRisk FX Terminal API", + description="Forex options risk analytics — Garman-Kohlhagen model.", + version="3.0.0") + +app.add_middleware(CORSMiddleware, allow_origins=["*"], + allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) + +for router in [greeks, surface, market, montecarlo, scenarios, strategies, export, forex, institutional, news, livedata]: + app.include_router(router.router, prefix="/api") + +@app.get("/") +def root(): + return {"status": "ok", "version": "3.0.0", "model": "Garman-Kohlhagen (1983)", "docs": "/docs"} diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/routers/export.py b/backend/app/routers/export.py new file mode 100644 index 0000000..2014cf6 --- /dev/null +++ b/backend/app/routers/export.py @@ -0,0 +1,43 @@ +import io, csv +from fastapi import APIRouter +from fastapi.responses import StreamingResponse +from ..schemas import GreeksRequest +from ..core.greeks import portfolio_greeks +from ..core.garman_kohlhagen import gk_price + +router = APIRouter(prefix="/export", tags=["export"]) + +@router.post("/csv") +def export_csv(req: GreeksRequest): + options = [o.model_dump() for o in req.options] + result = portfolio_greeks(options, req.S, req.sigma, req.T, req.r_d, req.r_f) + out = io.StringIO() + w = csv.writer(out) + w.writerow(["QUANTRISK FX — GARMAN-KOHLHAGEN GREEKS REPORT"]) + w.writerow(["Spot", req.S, "Sigma", req.sigma, "T", req.T, + "r_d", req.r_d, "r_f", req.r_f]) + w.writerow([]) + w.writerow(["PORTFOLIO TOTALS"]) + w.writerow(["Greek", "Value", "Description"]) + desc = {"delta":"Price sensitivity","gamma":"Delta curvature","vega":"Vol sensitivity", + "theta":"Time decay/yr","rho_d":"Dom rate sensitivity","phi":"For rate sensitivity"} + for k, v in result["total"].items(): + w.writerow([k.upper(), v, desc.get(k,"")]) + w.writerow([]) + w.writerow(["LEG BREAKDOWN"]) + w.writerow(["Leg","Delta","Gamma","Vega","Theta"]) + for leg in result["legs"]: + w.writerow([leg["label"],leg["delta"],leg["gamma"],leg["vega"],leg["theta"]]) + w.writerow([]) + w.writerow(["OPTION PRICES (Garman-Kohlhagen)"]) + w.writerow(["Leg","GK Price"]) + for opt in options: + price = gk_price(req.S, opt["K"], opt.get("T",req.T), req.r_d, req.r_f, + req.sigma, opt["type"]) + w.writerow([f"{opt['type'].upper()} K={opt['K']} qty={opt['qty']}", round(price,5)]) + out.seek(0) + return StreamingResponse( + io.BytesIO(out.getvalue().encode()), + media_type="text/csv", + headers={"Content-Disposition": "attachment; filename=gk_greeks_report.csv"}, + ) diff --git a/backend/app/routers/forex.py b/backend/app/routers/forex.py new file mode 100644 index 0000000..42b2a4b --- /dev/null +++ b/backend/app/routers/forex.py @@ -0,0 +1,147 @@ +"""forex.py — Live forex data and quant signal endpoints via yfinance.""" + +import numpy as np +import yfinance as yf +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +from ..core.quant_analysis import compute_signals + +router = APIRouter(prefix="/forex", tags=["forex"]) + +# Supported pairs: yfinance symbol → display label + default rates +PAIRS = { + "EURUSD": {"sym": "EURUSD=X", "r_d": 0.0525, "r_f": 0.0400, "pip": 0.0001}, + "GBPUSD": {"sym": "GBPUSD=X", "r_d": 0.0525, "r_f": 0.0525, "pip": 0.0001}, + "USDJPY": {"sym": "USDJPY=X", "r_d": 0.0010, "r_f": 0.0525, "pip": 0.01}, + "USDCHF": {"sym": "USDCHF=X", "r_d": 0.0175, "r_f": 0.0525, "pip": 0.0001}, + "AUDUSD": {"sym": "AUDUSD=X", "r_d": 0.0525, "r_f": 0.0435, "pip": 0.0001}, + "USDCAD": {"sym": "USDCAD=X", "r_d": 0.0500, "r_f": 0.0525, "pip": 0.0001}, + "NZDUSD": {"sym": "NZDUSD=X", "r_d": 0.0525, "r_f": 0.0550, "pip": 0.0001}, + "EURJPY": {"sym": "EURJPY=X", "r_d": 0.0010, "r_f": 0.0400, "pip": 0.01}, + "GBPJPY": {"sym": "GBPJPY=X", "r_d": 0.0010, "r_f": 0.0525, "pip": 0.01}, + "EURGBP": {"sym": "EURGBP=X", "r_d": 0.0525, "r_f": 0.0400, "pip": 0.0001}, + "XAUUSD": {"sym": "GC=F", "r_d": 0.0525, "r_f": 0.0000, "pip": 0.01}, +} + + +def _fetch_rate(sym: str) -> dict: + t = yf.Ticker(sym) + fi = t.fast_info + spot = fi.last_price + prev = fi.previous_close + if not spot: + return None + change = round((spot - prev) / prev * 100, 3) if prev else 0.0 + return {"spot": round(float(spot), 5), "prev": round(float(prev), 5) if prev else None, + "change_pct": change} + + +@router.get("/pairs") +def list_pairs(): + """Return metadata for all supported forex pairs.""" + return [{"pair": k, **{f: v for f, v in meta.items() if f != "sym"}} + for k, meta in PAIRS.items()] + + +@router.get("/rates") +def all_rates(): + """Fetch current rates for all major pairs (bulk call).""" + results = [] + for pair, meta in PAIRS.items(): + try: + data = _fetch_rate(meta["sym"]) + if data: + results.append({"pair": pair, **data, + "r_d": meta["r_d"], "r_f": meta["r_f"]}) + except Exception: + pass + return results + + +@router.get("/rate/{pair}") +def get_rate(pair: str): + """Current spot rate + 24h change for a single pair.""" + pair = pair.upper() + if pair not in PAIRS: + raise HTTPException(404, f"Unknown pair '{pair}'. Supported: {list(PAIRS)}") + meta = PAIRS[pair] + data = _fetch_rate(meta["sym"]) + if not data: + raise HTTPException(503, "Rate unavailable from data provider.") + return {"pair": pair, **data, "r_d": meta["r_d"], "r_f": meta["r_f"], + "pip": meta["pip"]} + + +@router.get("/ohlc/{pair}") +def get_ohlc(pair: str, interval: str = "5m", period: str = "2d"): + """ + OHLC candlestick data for a pair. + interval: 1m 5m 15m 30m 1h 4h 1d + period: 1d 2d 5d 1mo + """ + pair = pair.upper() + if pair not in PAIRS: + raise HTTPException(404, f"Unknown pair '{pair}'.") + sym = PAIRS[pair]["sym"] + valid_intervals = {"1m", "5m", "15m", "30m", "1h", "4h", "1d"} + if interval not in valid_intervals: + interval = "5m" + try: + hist = yf.download(sym, period=period, interval=interval, + progress=False, auto_adjust=True) + if hist.empty: + raise HTTPException(503, "No OHLC data returned.") + hist = hist.dropna() + # Flatten MultiIndex columns if present + if isinstance(hist.columns, type(hist.columns)) and hasattr(hist.columns, 'droplevel'): + try: + hist.columns = hist.columns.droplevel(1) + except Exception: + pass + return { + "pair": pair, + "interval": interval, + "dates": [str(d) for d in hist.index], + "open": [round(float(v), 5) for v in hist["Open"]], + "high": [round(float(v), 5) for v in hist["High"]], + "low": [round(float(v), 5) for v in hist["Low"]], + "close": [round(float(v), 5) for v in hist["Close"]], + "volume": [int(v) for v in hist.get("Volume", [0]*len(hist))], + } + except HTTPException: + raise + except Exception as e: + raise HTTPException(503, f"Data fetch failed: {e}") + + +@router.get("/signals/{pair}") +def get_signals(pair: str): + """ + Compute full quant signal suite using 60 days of daily closes. + Signals: EWMA vol, VaR/CVaR, Hurst exponent, OU mean-reversion, + momentum, carry — all with academic citations. + """ + pair = pair.upper() + if pair not in PAIRS: + raise HTTPException(404, f"Unknown pair '{pair}'.") + meta = PAIRS[pair] + try: + hist = yf.download(meta["sym"], period="90d", interval="1d", + progress=False, auto_adjust=True) + if hist.empty or len(hist) < 10: + raise HTTPException(503, "Insufficient history for signal computation.") + if isinstance(hist.columns, type(hist.columns)) and hasattr(hist.columns, 'droplevel'): + try: + hist.columns = hist.columns.droplevel(1) + except Exception: + pass + closes = [float(v) for v in hist["Close"].dropna()] + signals = compute_signals(closes, r_d=meta["r_d"], r_f=meta["r_f"]) + signals["pair"] = pair + signals["current_price"] = round(closes[-1], 5) + return signals + except HTTPException: + raise + except Exception as e: + raise HTTPException(503, f"Signal computation failed: {e}") diff --git a/backend/app/routers/greeks.py b/backend/app/routers/greeks.py new file mode 100644 index 0000000..5afb590 --- /dev/null +++ b/backend/app/routers/greeks.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter +from ..schemas import GreeksRequest +from ..core.greeks import portfolio_greeks + +router = APIRouter(prefix="/greeks", tags=["greeks"]) + +@router.post("") +def compute_greeks(req: GreeksRequest): + options = [o.model_dump() for o in req.options] + return portfolio_greeks(options, req.S, req.sigma, req.T, req.r_d, req.r_f) diff --git a/backend/app/routers/institutional.py b/backend/app/routers/institutional.py new file mode 100644 index 0000000..94d3820 --- /dev/null +++ b/backend/app/routers/institutional.py @@ -0,0 +1,270 @@ +""" +institutional.py — Free institutional flow data. + +Sources: + 1. CFTC TFF (Traders in Financial Futures) — Socrata API on publicreporting.cftc.gov + Dataset: gpe5-46if (no API key needed, weekly, official CFTC data) + Categories: Dealers, Asset Managers, Leveraged Money (hedge funds), Other, Non-reportable + 2. Volume Profile — approximated from yfinance OHLCV daily bars + (distributes bar volume proportionally across the High-Low range) +""" + +import numpy as np +import httpx +import urllib.parse +import yfinance as yf +from fastapi import APIRouter, HTTPException + +router = APIRouter(prefix="/institutional", tags=["institutional"]) + +CFTC_BASE = "https://publicreporting.cftc.gov" +CFTC_DATASET = "gpe5-46if" + +# Map forex pair → CFTC market name + yfinance symbol +COT_MAP = { + "EURUSD": {"cot": "EURO FX - CHICAGO MERCANTILE EXCHANGE", "sym": "EURUSD=X"}, + "GBPUSD": {"cot": "BRITISH POUND STERLING - CHICAGO MERCANTILE EXCHANGE", "sym": "GBPUSD=X"}, + "USDJPY": {"cot": "JAPANESE YEN - CHICAGO MERCANTILE EXCHANGE", "sym": "USDJPY=X"}, + "AUDUSD": {"cot": "AUSTRALIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE", "sym": "AUDUSD=X"}, + "USDCAD": {"cot": "CANADIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE", "sym": "USDCAD=X"}, + "NZDUSD": {"cot": "NEW ZEALAND DOLLAR - CHICAGO MERCANTILE EXCHANGE", "sym": "NZDUSD=X"}, + "USDCHF": {"cot": "SWISS FRANC - CHICAGO MERCANTILE EXCHANGE", "sym": "USDCHF=X"}, + "EURJPY": {"cot": "EURO FX - CHICAGO MERCANTILE EXCHANGE", "sym": "EURJPY=X"}, + "GBPJPY": {"cot": "BRITISH POUND STERLING - CHICAGO MERCANTILE EXCHANGE", "sym": "GBPJPY=X"}, + "EURGBP": {"cot": "EURO FX - CHICAGO MERCANTILE EXCHANGE", "sym": "EURGBP=X"}, + "XAUUSD": {"cot": "GOLD - COMMODITY EXCHANGE INC.", "sym": "GC=F"}, +} + + +def _safe_int(v) -> int: + try: return int(v or 0) + except: return 0 + +def _safe_float(v) -> float: + try: return float(v or 0) + except: return 0.0 + + +def _fetch_cot(market_name: str, weeks: int = 52) -> list[dict]: + """Fetch TFF COT data from CFTC Socrata API (publicreporting.cftc.gov).""" + where = urllib.parse.quote(f"market_and_exchange_names='{market_name}'") + url = ( + f"{CFTC_BASE}/resource/{CFTC_DATASET}.json" + f"?$where={where}" + f"&$order=report_date_as_yyyy_mm_dd+DESC" + f"&$limit={weeks}" + ) + try: + resp = httpx.get(url, timeout=20, follow_redirects=True) + resp.raise_for_status() + return resp.json() + except Exception: + return [] + + +def _parse_tff(records: list[dict]) -> dict: + """Parse TFF records into structured signal data (newest-first records → oldest-first output).""" + if not records: + return {} + + rows = list(reversed(records)) # oldest first for charting + + dates = [] + oi, dealer_net, assetmgr_net, levmoney_net, other_net = [], [], [], [], [] + chg_oi, chg_am_long, chg_am_short, chg_lm_long, chg_lm_short = [], [], [], [], [] + pct_am_long, pct_am_short, pct_lm_long, pct_lm_short = [], [], [], [] + am_long_list, am_short_list, lm_long_list, lm_short_list = [], [], [], [] + + for r in rows: + d = (r.get("report_date_as_yyyy_mm_dd") or "")[:10] + if not d: + continue + dates.append(d) + + aml = _safe_int(r.get("asset_mgr_positions_long")) + ams = _safe_int(r.get("asset_mgr_positions_short")) + lml = _safe_int(r.get("lev_money_positions_long")) + lms = _safe_int(r.get("lev_money_positions_short")) + dl = _safe_int(r.get("dealer_positions_long_all")) + ds = _safe_int(r.get("dealer_positions_short_all")) + ol = _safe_int(r.get("other_rept_positions_long")) + os_ = _safe_int(r.get("other_rept_positions_short")) + total_oi = _safe_int(r.get("open_interest_all")) + + oi.append(total_oi) + dealer_net.append(dl - ds) + assetmgr_net.append(aml - ams) + levmoney_net.append(lml - lms) + other_net.append(ol - os_) + am_long_list.append(aml) + am_short_list.append(ams) + lm_long_list.append(lml) + lm_short_list.append(lms) + + chg_oi.append(_safe_int(r.get("change_in_open_interest_all"))) + chg_am_long.append(_safe_int(r.get("change_in_asset_mgr_long"))) + chg_am_short.append(_safe_int(r.get("change_in_asset_mgr_short"))) + chg_lm_long.append(_safe_int(r.get("change_in_lev_money_long"))) + chg_lm_short.append(_safe_int(r.get("change_in_lev_money_short"))) + + pct_am_long.append(_safe_float(r.get("pct_of_oi_asset_mgr_long"))) + pct_am_short.append(_safe_float(r.get("pct_of_oi_asset_mgr_short"))) + pct_lm_long.append(_safe_float(r.get("pct_of_oi_lev_money_long"))) + pct_lm_short.append(_safe_float(r.get("pct_of_oi_lev_money_short"))) + + if not dates: + return {} + + def _cot_index(series): + lo, hi = min(series), max(series) + return round((series[-1] - lo) / max(hi - lo, 1) * 100, 1) if hi > lo else 50.0 + + am_idx = _cot_index(assetmgr_net) + lm_idx = _cot_index(levmoney_net) + + def _bias(idx): + return "BULLISH" if idx >= 65 else ("BEARISH" if idx <= 35 else "NEUTRAL") + + am_bias = _bias(am_idx) + lm_bias = _bias(lm_idx) + + # Composite: weight asset managers 60%, leveraged money 40% + composite_idx = round(am_idx * 0.6 + lm_idx * 0.4, 1) + composite_bias = _bias(composite_idx) + + wk_chg_am = (assetmgr_net[-1] - assetmgr_net[-2]) if len(assetmgr_net) >= 2 else 0 + wk_chg_lm = (levmoney_net[-1] - levmoney_net[-2]) if len(levmoney_net) >= 2 else 0 + + return { + "dates": dates, + "open_interest": oi, + "change_oi": chg_oi, + # Asset Managers (institutional — real money) + "am_net": assetmgr_net, + "am_long": am_long_list, + "am_short": am_short_list, + "am_pct_long": pct_am_long, + "am_pct_short": pct_am_short, + "am_index": am_idx, + "am_bias": am_bias, + "am_current_net": assetmgr_net[-1], + "am_wk_change": wk_chg_am, + # Leveraged Money (hedge funds, CTAs) + "lm_net": levmoney_net, + "lm_long": lm_long_list, + "lm_short": lm_short_list, + "lm_pct_long": pct_lm_long, + "lm_pct_short": pct_lm_short, + "lm_index": lm_idx, + "lm_bias": lm_bias, + "lm_current_net": levmoney_net[-1], + "lm_wk_change": wk_chg_lm, + # Dealers + "dealer_net": dealer_net, + # Other + "other_net": other_net, + # Composite + "composite_index": composite_idx, + "composite_bias": composite_bias, + "weeks": len(dates), + "latest_date": dates[-1], + "source": "CFTC TFF — Traders in Financial Futures (Socrata API)", + } + + +def _volume_profile(sym: str, period: str = "3mo", buckets: int = 40) -> dict: + """ + Approximate volume profile from daily OHLCV. + Distributes each bar's volume uniformly across its High-Low range. + """ + try: + hist = yf.download(sym, period=period, interval="1d", + progress=False, auto_adjust=True) + if hist.empty: + return {} + if hasattr(hist.columns, "droplevel"): + try: hist.columns = hist.columns.droplevel(1) + except Exception: pass + + highs = hist["High"].dropna().values.astype(float) + lows = hist["Low"].dropna().values.astype(float) + volumes = hist["Volume"].dropna().values.astype(float) + closes = hist["Close"].dropna().values.astype(float) + + global_lo = float(np.min(lows)) + global_hi = float(np.max(highs)) + if global_hi <= global_lo: + return {} + + bucket_size = (global_hi - global_lo) / buckets + vol_profile = np.zeros(buckets) + + for i in range(len(highs)): + lo_b = int((lows[i] - global_lo) / bucket_size) + hi_b = int((highs[i] - global_lo) / bucket_size) + lo_b = max(0, min(lo_b, buckets - 1)) + hi_b = max(0, min(hi_b, buckets - 1)) + span = max(hi_b - lo_b + 1, 1) + vol_per_bucket = volumes[i] / span if volumes[i] > 0 else 0 + vol_profile[lo_b:hi_b + 1] += vol_per_bucket + + prices = [round(global_lo + (j + 0.5) * bucket_size, 5) for j in range(buckets)] + poc_idx = int(np.argmax(vol_profile)) + poc = prices[poc_idx] + + total_vol = float(np.sum(vol_profile)) + va_target = total_vol * 0.70 + lo_idx, hi_idx = poc_idx, poc_idx + va_vol = float(vol_profile[poc_idx]) + while va_vol < va_target and (lo_idx > 0 or hi_idx < buckets - 1): + expand_lo = vol_profile[lo_idx - 1] if lo_idx > 0 else 0 + expand_hi = vol_profile[hi_idx + 1] if hi_idx < buckets - 1 else 0 + if expand_hi >= expand_lo: + hi_idx = min(hi_idx + 1, buckets - 1); va_vol += expand_hi + else: + lo_idx = max(lo_idx - 1, 0); va_vol += expand_lo + + return { + "prices": prices, + "volumes": [round(float(v), 0) for v in vol_profile], + "poc": poc, + "vah": prices[hi_idx], + "val": prices[lo_idx], + "current_price": round(float(closes[-1]), 5), + "global_hi": round(global_hi, 5), + "global_lo": round(global_lo, 5), + "total_volume": round(total_vol, 0), + } + except Exception: + return {} + + +@router.get("/{pair}") +def get_institutional(pair: str, weeks: int = 26): + """ + Institutional flow data: CFTC TFF positioning + Volume Profile. + Data sources are 100% free — no API keys required. + """ + pair = pair.upper() + if pair not in COT_MAP: + raise HTTPException(404, f"No institutional data for '{pair}'.") + + meta = COT_MAP[pair] + weeks = min(max(weeks, 4), 52) + + raw = _fetch_cot(meta["cot"], weeks) + cot = _parse_tff(raw) + vp = _volume_profile(meta["sym"]) + + if not cot and not vp: + raise HTTPException(503, "CFTC API and volume profile both unavailable.") + + return { + "pair": pair, + "cot": cot, + "volume_profile": vp, + "data_sources": [ + "CFTC TFF (Traders in Financial Futures) — publicreporting.cftc.gov, weekly, free", + "Volume Profile — yfinance OHLCV daily, 3 months", + ], + } diff --git a/backend/app/routers/livedata.py b/backend/app/routers/livedata.py new file mode 100644 index 0000000..e2e8488 --- /dev/null +++ b/backend/app/routers/livedata.py @@ -0,0 +1,59 @@ +import time +import httpx +from fastapi import APIRouter + +router = APIRouter(prefix="/live", tags=["live"]) + +# Simple in-memory cache to avoid hammering free APIs +_cache: dict = {} +CACHE_TTL = 30 # seconds + + +def _cached(key: str, ttl: int = CACHE_TTL): + entry = _cache.get(key) + if entry and time.time() - entry["ts"] < ttl: + return entry["data"] + return None + + +def _store(key: str, data): + _cache[key] = {"ts": time.time(), "data": data} + return data + + +@router.get("/aircraft") +def get_aircraft(): + cached = _cached("aircraft", ttl=30) + if cached is not None: + return cached + try: + with httpx.Client(timeout=10) as client: + r = client.get("https://opensky-network.org/api/states/all") + r.raise_for_status() + data = r.json() + states = data.get("states") or [] + # Filter: has position, not on ground, limit 600 + filtered = [ + s for s in states + if s[5] is not None and s[6] is not None and not s[8] + ][:600] + result = {"time": data.get("time"), "states": filtered} + return _store("aircraft", result) + except Exception as e: + return {"time": None, "states": [], "error": str(e)} + + +@router.get("/earthquakes") +def get_earthquakes(): + cached = _cached("earthquakes", ttl=300) + if cached is not None: + return cached + try: + with httpx.Client(timeout=10) as client: + r = client.get( + "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_week.geojson" + ) + r.raise_for_status() + return _store("earthquakes", r.json()) + except Exception as e: + return {"features": [], "error": str(e)} diff --git a/backend/app/routers/market.py b/backend/app/routers/market.py new file mode 100644 index 0000000..a5649b5 --- /dev/null +++ b/backend/app/routers/market.py @@ -0,0 +1,66 @@ +from fastapi import APIRouter, HTTPException +import yfinance as yf + +router = APIRouter(prefix="/market", tags=["market"]) + + +@router.get("/{ticker}") +def get_market_data(ticker: str): + """Return current spot price, company name, and daily change for a ticker.""" + try: + t = yf.Ticker(ticker.upper()) + info = t.fast_info + spot = info.last_price + prev = info.previous_close + if not spot: + raise HTTPException(status_code=404, detail=f"Ticker '{ticker}' not found.") + change_pct = round((spot - prev) / prev * 100, 2) if prev else 0.0 + name = getattr(info, "exchange", ticker.upper()) + return { + "ticker": ticker.upper(), + "spot": round(float(spot), 2), + "prev_close": round(float(prev), 2) if prev else None, + "change_pct": change_pct, + } + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@router.get("/iv-surface/{ticker}") +def get_iv_surface(ticker: str): + """ + Fetch the real implied volatility surface from market option chains. + Returns strikes, expiries, and IV values for a heatmap. + """ + try: + t = yf.Ticker(ticker.upper()) + expiries = t.options[:6] # Limit to 6 nearest expiries + if not expiries: + raise HTTPException(status_code=404, detail="No options data found.") + + rows = [] + for exp in expiries: + chain = t.option_chain(exp) + for _, row in chain.calls.iterrows(): + if row.get("impliedVolatility") and row["impliedVolatility"] > 0: + rows.append({ + "expiry": exp, + "strike": float(row["strike"]), + "iv": round(float(row["impliedVolatility"]), 4), + "type": "call", + }) + for _, row in chain.puts.iterrows(): + if row.get("impliedVolatility") and row["impliedVolatility"] > 0: + rows.append({ + "expiry": exp, + "strike": float(row["strike"]), + "iv": round(float(row["impliedVolatility"]), 4), + "type": "put", + }) + + spot = float(t.fast_info.last_price) + return {"ticker": ticker.upper(), "spot": spot, "data": rows} + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/backend/app/routers/montecarlo.py b/backend/app/routers/montecarlo.py new file mode 100644 index 0000000..4bf496f --- /dev/null +++ b/backend/app/routers/montecarlo.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter +from ..schemas import MonteCarloRequest +from ..core.montecarlo import run_montecarlo + +router = APIRouter(prefix="/montecarlo", tags=["montecarlo"]) + +@router.post("") +def monte_carlo(req: MonteCarloRequest): + options = [o.model_dump() for o in req.options] + return run_montecarlo(options, req.S0, req.sigma, req.r_d, req.r_f, + req.T, req.n_paths, req.n_steps) diff --git a/backend/app/routers/news.py b/backend/app/routers/news.py new file mode 100644 index 0000000..b95cedd --- /dev/null +++ b/backend/app/routers/news.py @@ -0,0 +1,110 @@ +""" +news.py — Free economic calendar from Forex Factory. + +Source: nfs.faireconomy.media (official FF data mirror, JSON format) + ff_calendar_thisweek.json — current week + (nextweek.json appears Fri/Sat only, gracefully skipped if 404) + No API key. No auth. +""" + +import httpx +from datetime import datetime, timezone +from fastapi import APIRouter + +router = APIRouter(prefix="/news", tags=["news"]) + +FF_JSON_URLS = [ + "https://nfs.faireconomy.media/ff_calendar_thisweek.json", + "https://nfs.faireconomy.media/ff_calendar_nextweek.json", +] + +# In-memory cache — refresh every hour +_cache: dict = {"data": None, "ts": 0.0} +_CACHE_TTL = 3600 # seconds + + +def _fetch_ff_json(url: str) -> list[dict]: + try: + resp = httpx.get( + url, timeout=15, follow_redirects=True, + headers={"User-Agent": "Mozilla/5.0 (compatible; QuantRiskFX/3.0)"} + ) + if resp.status_code != 200: + return [] + raw = resp.json() + events = [] + for ev in (raw if isinstance(raw, list) else []): + # `date` is already ISO-8601 with TZ offset, e.g. "2026-06-07T08:30:00-04:00" + date_str = ev.get("date", "") + dt_utc = None + if date_str: + try: + dt_et = datetime.fromisoformat(date_str) + dt_utc = dt_et.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%MZ") + except ValueError: + pass + + events.append({ + "title": ev.get("title", "").strip(), + "country": ev.get("country", "").strip(), + "date_raw": date_str, + "datetime_utc": dt_utc, + "impact": ev.get("impact", "").strip(), + "forecast": ev.get("forecast", "").strip(), + "previous": ev.get("previous", "").strip(), + "actual": ev.get("actual", "").strip(), + "url": "", + }) + return events + except Exception: + return [] + + +def _load_calendar() -> dict: + import time + now = time.time() + if _cache["data"] is not None and (now - _cache["ts"]) < _CACHE_TTL: + return _cache["data"] + + seen: set[tuple] = set() + all_events: list = [] + for url in FF_JSON_URLS: + for ev in _fetch_ff_json(url): + key = (ev["title"], ev["country"], ev["datetime_utc"]) + if key not in seen: + seen.add(key) + all_events.append(ev) + + all_events.sort(key=lambda e: e["datetime_utc"] or "") + + now_utc = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%MZ") + next_high = next( + (e for e in all_events + if e["impact"] == "High" + and (e["datetime_utc"] or "") >= now_utc + and not e["actual"]), + None, + ) + + result = { + "events": all_events, + "count": len(all_events), + "now_utc": now_utc, + "next_high": next_high, + "source": "Forex Factory Economic Calendar — nfs.faireconomy.media (free, no API key)", + "note": "Times in UTC (source is US Eastern Time).", + "cached": False, + } + _cache["data"] = result + _cache["ts"] = now + return result + + +@router.get("/calendar") +def get_calendar(): + """ + Economic calendar from Forex Factory (current + next week). + Times returned as UTC ISO-8601 strings. Cached for 1 hour. + """ + result = _load_calendar() + return {**result, "cached": _cache["ts"] > 0} diff --git a/backend/app/routers/scenarios.py b/backend/app/routers/scenarios.py new file mode 100644 index 0000000..303c207 --- /dev/null +++ b/backend/app/routers/scenarios.py @@ -0,0 +1,30 @@ +from fastapi import APIRouter +from ..schemas import ScenarioRequest +from ..core.garman_kohlhagen import gk_price + +router = APIRouter(prefix="/scenarios", tags=["scenarios"]) + +@router.post("") +def compute_scenarios(req: ScenarioRequest): + options = [o.model_dump() for o in req.options] + + def portfolio_value(S, sigma): + return sum( + gk_price(S, opt["K"], opt.get("T", req.T), req.r_d, req.r_f, sigma, opt["type"]) + * opt["qty"] for opt in options + ) + + base = portfolio_value(req.S0, req.sigma0) + results = [] + for shock in req.shocks: + S_s = req.S0 * (1 + shock.dS_pct) + vol_s = max(0.005, req.sigma0 + shock.dVol) + pnl = portfolio_value(S_s, vol_s) - base + results.append({ + "label": shock.label, + "dS_pct": shock.dS_pct, "dVol": shock.dVol, + "S_shocked": round(S_s, 5), "vol_shocked": round(vol_s, 4), + "pnl": round(pnl, 5), + "pnl_pct": round(pnl / abs(base) * 100, 2) if base else 0, + }) + return {"base_value": round(base, 5), "scenarios": results} diff --git a/backend/app/routers/strategies.py b/backend/app/routers/strategies.py new file mode 100644 index 0000000..7f7e2e4 --- /dev/null +++ b/backend/app/routers/strategies.py @@ -0,0 +1,93 @@ +from fastapi import APIRouter + +router = APIRouter(prefix="/strategies", tags=["strategies"]) + +# Pre-built strategy templates — all expressed relative to ATM spot (K=100 placeholder) +STRATEGIES = [ + { + "name": "Long Call", + "description": "Bullish. Unlimited upside, limited downside to premium paid.", + "legs": [{"type": "call", "K_offset": 0, "qty": 1}], + }, + { + "name": "Long Put", + "description": "Bearish. Profit if spot falls below strike.", + "legs": [{"type": "put", "K_offset": 0, "qty": 1}], + }, + { + "name": "Covered Call", + "description": "Long stock + short OTM call. Income strategy.", + "legs": [{"type": "call", "K_offset": 5, "qty": -1}], + }, + { + "name": "Protective Put", + "description": "Long stock + long put. Portfolio insurance.", + "legs": [{"type": "put", "K_offset": -5, "qty": 1}], + }, + { + "name": "Straddle", + "description": "Long call + put at same strike. Profits from large moves either way.", + "legs": [ + {"type": "call", "K_offset": 0, "qty": 1}, + {"type": "put", "K_offset": 0, "qty": 1}, + ], + }, + { + "name": "Strangle", + "description": "OTM call + OTM put. Cheaper than straddle, needs bigger move.", + "legs": [ + {"type": "call", "K_offset": 5, "qty": 1}, + {"type": "put", "K_offset": -5, "qty": 1}, + ], + }, + { + "name": "Bull Call Spread", + "description": "Long ATM call + short OTM call. Capped upside, lower cost.", + "legs": [ + {"type": "call", "K_offset": 0, "qty": 1}, + {"type": "call", "K_offset": 10, "qty": -1}, + ], + }, + { + "name": "Bear Put Spread", + "description": "Long ATM put + short OTM put. Profits from moderate decline.", + "legs": [ + {"type": "put", "K_offset": 0, "qty": 1}, + {"type": "put", "K_offset": -10, "qty": -1}, + ], + }, + { + "name": "Iron Condor", + "description": "4-leg strategy. Profit from low volatility, defined risk.", + "legs": [ + {"type": "put", "K_offset": -15, "qty": 1}, + {"type": "put", "K_offset": -5, "qty": -1}, + {"type": "call", "K_offset": 5, "qty": -1}, + {"type": "call", "K_offset": 15, "qty": 1}, + ], + }, + { + "name": "Butterfly", + "description": "3-strike spread. Max profit when spot pins at middle strike.", + "legs": [ + {"type": "call", "K_offset": -10, "qty": 1}, + {"type": "call", "K_offset": 0, "qty": -2}, + {"type": "call", "K_offset": 10, "qty": 1}, + ], + }, +] + + +@router.get("") +def list_strategies(): + """Return all available strategy templates.""" + return STRATEGIES + + +@router.get("/{name}") +def get_strategy(name: str): + """Return a specific strategy by name (case-insensitive).""" + for s in STRATEGIES: + if s["name"].lower() == name.lower(): + return s + return {"error": f"Strategy '{name}' not found."} diff --git a/backend/app/routers/surface.py b/backend/app/routers/surface.py new file mode 100644 index 0000000..acdd13e --- /dev/null +++ b/backend/app/routers/surface.py @@ -0,0 +1,14 @@ +import numpy as np +from fastapi import APIRouter +from ..schemas import SurfaceRequest +from ..core.surface import compute_surfaces + +router = APIRouter(prefix="/surface", tags=["surface"]) + +@router.post("") +def compute_surface(req: SurfaceRequest): + options = [o.model_dump() for o in req.options] + # If caller didn't set S range, default to ±20% around midpoint — handled frontend-side + S_range = np.linspace(req.S_low, req.S_high, req.S_steps) + sigma_range = np.linspace(req.vol_low, req.vol_high, req.vol_steps) + return compute_surfaces(options, S_range, sigma_range, req.T, req.r_d, req.r_f) diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..6c00fdd --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,66 @@ +from pydantic import BaseModel, Field +from typing import Literal + + +class OptionLeg(BaseModel): + type: Literal["call", "put"] + K: float = Field(..., gt=0, description="Strike price (exchange rate)") + T: float = Field(..., gt=0, description="Time to expiry in years") + qty: float = Field(..., description="Signed quantity (positive=long)") + + +class GreeksRequest(BaseModel): + options: list[OptionLeg] + S: float = Field(..., gt=0, description="Spot exchange rate") + sigma: float = Field(..., gt=0, lt=5) + T: float = Field(..., gt=0) + r_d: float = Field(default=0.0525, description="Domestic risk-free rate") + r_f: float = Field(default=0.0400, description="Foreign risk-free rate") + + +class SurfaceRequest(BaseModel): + options: list[OptionLeg] + S_low: float = Field(default=0.0) + S_high: float = Field(default=0.0) + S_steps: int = Field(default=40) + vol_low: float = Field(default=0.05) + vol_high: float = Field(default=0.30) + vol_steps: int = Field(default=40) + T: float = Field(default=0.5) + r_d: float = Field(default=0.0525) + r_f: float = Field(default=0.0400) + + +class MonteCarloRequest(BaseModel): + options: list[OptionLeg] + S0: float = Field(..., gt=0) + sigma: float = Field(..., gt=0) + r_d: float = Field(default=0.0525) + r_f: float = Field(default=0.0400) + T: float = Field(..., gt=0) + n_paths: int = Field(default=1000, ge=100, le=10000) + n_steps: int = Field(default=100, ge=10, le=500) + + +class ScenarioShock(BaseModel): + label: str + dS_pct: float + dVol: float + + +class ScenarioRequest(BaseModel): + options: list[OptionLeg] + S0: float + sigma0: float + T: float + r_d: float = 0.0525 + r_f: float = 0.0400 + shocks: list[ScenarioShock] = Field(default_factory=lambda: [ + ScenarioShock(label="Flash Crash", dS_pct=-0.03, dVol=0.08), + ScenarioShock(label="Sharp Sell-off", dS_pct=-0.015,dVol=0.04), + ScenarioShock(label="Mild Weakness", dS_pct=-0.005,dVol=0.01), + ScenarioShock(label="Base Case", dS_pct=0.00, dVol=0.00), + ScenarioShock(label="Mild Strength", dS_pct=0.005, dVol=-0.01), + ScenarioShock(label="Sharp Rally", dS_pct=0.015, dVol=-0.03), + ScenarioShock(label="Breakout", dS_pct=0.03, dVol=-0.05), + ]) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..5bf9a7b --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,8 @@ +fastapi>=0.110,<1.0 +uvicorn[standard]>=0.29,<1.0 +numpy>=1.24,<3.0 +scipy>=1.10,<2.0 +pandas>=2.0,<4.0 +yfinance>=0.2.38 +pydantic>=2.0,<3.0 +reportlab>=4.0,<5.0 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..a36934d --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..ea36dd3 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..dd545f5 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + + NEXUS TERMINAL — FX Options Analytics + + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..3e564f4 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,5228 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "axios": "^1.17.0", + "leaflet": "^1.9.4", + "plotly.js": "^3.6.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-leaflet": "^5.0.0", + "react-plotly.js": "^2.6.0", + "react-router-dom": "^7.17.0", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "autoprefixer": "^10.5.0", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "postcss": "^8.5.15", + "tailwindcss": "^4.3.0", + "vite": "^8.0.12" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@choojs/findup": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", + "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", + "dependencies": { + "commander": "^2.15.1" + }, + "bin": { + "findup": "bin/findup.js" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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, + "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, + "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, + "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 + }, + "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, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/geojson-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz", + "integrity": "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==" + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", + "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==", + "peerDependencies": { + "mapbox-gl": ">=0.32.1 <2.0.0" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", + "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", + "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.4.0.tgz", + "integrity": "sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@plotly/d3": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.2.tgz", + "integrity": "sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA==" + }, + "node_modules/@plotly/d3-sankey": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@plotly/d3-sankey/-/d3-sankey-0.7.2.tgz", + "integrity": "sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw==", + "dependencies": { + "d3-array": "1", + "d3-collection": "1", + "d3-shape": "^1.2.0" + } + }, + "node_modules/@plotly/d3-sankey-circular": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@plotly/d3-sankey-circular/-/d3-sankey-circular-0.33.1.tgz", + "integrity": "sha512-FgBV1HEvCr3DV7RHhDsPXyryknucxtfnLwPtCKKxdolKyTFYoLX/ibEfX39iFYIL7DYbVeRtP43dbFcrHNE+KQ==", + "dependencies": { + "d3-array": "^1.2.1", + "d3-collection": "^1.0.4", + "d3-shape": "^1.2.0", + "elementary-circuits-directed-graph": "^1.0.4" + } + }, + "node_modules/@plotly/mapbox-gl": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@plotly/mapbox-gl/-/mapbox-gl-1.13.4.tgz", + "integrity": "sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/@plotly/point-cluster": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@plotly/point-cluster/-/point-cluster-3.1.9.tgz", + "integrity": "sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw==", + "dependencies": { + "array-bounds": "^1.0.1", + "binary-search-bounds": "^2.0.4", + "clamp": "^1.0.1", + "defined": "^1.0.0", + "dtype": "^2.0.0", + "flatten-vertex-data": "^1.0.2", + "is-obj": "^1.0.1", + "math-log2": "^1.0.1", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0" + } + }, + "node_modules/@plotly/regl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@plotly/regl/-/regl-2.1.2.tgz", + "integrity": "sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw==" + }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@turf/area": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.5.tgz", + "integrity": "sha512-sSn80wPT7XfBIDN3vurCPxhk9W4U8ozS/XImSqeLN8qveTICOxzZkhsGDMp0CuncaN+plWut4a2TdNM7mzZB6Q==", + "dependencies": { + "@turf/helpers": "7.3.5", + "@turf/meta": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.3.5.tgz", + "integrity": "sha512-oG1ya/HtBjAIg4TimbWx+nOYPbY0bCvt82Bq8tm6sBw3qqtbOyRSfDz79Sq90TnH7DXJprJ1qnVGKNtZ6jemfw==", + "dependencies": { + "@turf/helpers": "7.3.5", + "@turf/meta": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-7.3.5.tgz", + "integrity": "sha512-hkWaqwGFdOn6Tf0EWfn2yn1XZ1FWE1h2C5ZWstDMu/FxYO5DB+YjlmOFPl4K6SmSOEgdV07eK2vDCyPeTHqKGA==", + "dependencies": { + "@turf/helpers": "7.3.5", + "@turf/meta": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.3.5.tgz", + "integrity": "sha512-E/NMGV5MwbjjP7AJXBtsanC3yY8N2MQ87IGdIgkB2ji5AtBpwnH4L3gEqpYN4RlCJJWbLbzO91BbKv2waUd0eg==", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.3.5.tgz", + "integrity": "sha512-r+ohqxoyqeigFB0oFrQx/YEHIkOKqcKpCjvZkvZs7Tkv+IFco5MezAd2zd4rzK+0DfFgDP3KpJc7HqrYjvEjhg==", + "dependencies": { + "@turf/helpers": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "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, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-bounds": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-bounds/-/array-bounds-1.0.1.tgz", + "integrity": "sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ==" + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-normalize": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array-normalize/-/array-normalize-1.1.4.tgz", + "integrity": "sha512-fCp0wKFLjvSPmCn4F5Tiw4M3lpMZoHlCjfcs7nNzuj3vqQQ1/a8cgB9DXcpDSn18c+coLnaW7rqfcYCvKbyJXg==", + "dependencies": { + "array-bounds": "^1.0.0" + } + }, + "node_modules/array-range": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-range/-/array-range-1.0.1.tgz", + "integrity": "sha512-shdaI1zT3CVNL2hnx9c0JMc0ZogGaxDs5e85akgHWKYa0yVbIyp06Ind3dVkTj/uuFrzaHBOyqFzo+VV6aXgtA==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.34", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz", + "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-search-bounds": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==" + }, + "node_modules/bit-twiddle": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", + "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==" + }, + "node_modules/bitmap-sdf": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bitmap-sdf/-/bitmap-sdf-1.0.4.tgz", + "integrity": "sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==" + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "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" + } + ], + "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/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", + "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" + } + ] + }, + "node_modules/clamp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", + "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==" + }, + "node_modules/color-alpha": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.1.3.tgz", + "integrity": "sha512-krPYBO1RSO5LH4AGb/b6z70O1Ip2o0F0+0cVFN5FN99jfQtZFT08rQyg+9oOBNJYAn3SRwJIFC8jUEOKz7PisA==", + "dependencies": { + "color-parse": "^1.4.1" + } + }, + "node_modules/color-alpha/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-alpha/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/color-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/color-id/-/color-id-1.1.0.tgz", + "integrity": "sha512-2iRtAn6dC/6/G7bBIo0uupVrIne1NsQJvJxZOBCzQOfk7jRq97feaDZ3RdzuHakRXXnHGNwglto3pqtRx1sX0g==", + "dependencies": { + "clamp": "^1.0.1" + } + }, + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-normalize": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/color-normalize/-/color-normalize-1.5.0.tgz", + "integrity": "sha512-rUT/HDXMr6RFffrR53oX3HGWkDOP9goSAQGBkUaAYKjOE2JxozccdGyufageWDlInRAjm/jYPrf/Y38oa+7obw==", + "dependencies": { + "clamp": "^1.0.1", + "color-rgba": "^2.1.1", + "dtype": "^2.0.0" + } + }, + "node_modules/color-normalize/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-normalize/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/color-normalize/node_modules/color-rgba": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-2.4.0.tgz", + "integrity": "sha512-Nti4qbzr/z2LbUWySr7H9dk3Rl7gZt7ihHAxlgT4Ho90EXWkjtkL1avTleu9yeGuqrt/chxTB6GKK8nZZ6V0+Q==", + "dependencies": { + "color-parse": "^1.4.2", + "color-space": "^2.0.0" + } + }, + "node_modules/color-parse": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-2.0.2.tgz", + "integrity": "sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw==", + "dependencies": { + "color-name": "^2.0.0" + } + }, + "node_modules/color-rgba": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-3.0.0.tgz", + "integrity": "sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==", + "dependencies": { + "color-parse": "^2.0.0", + "color-space": "^2.0.0" + } + }, + "node_modules/color-space": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/color-space/-/color-space-2.3.2.tgz", + "integrity": "sha512-BcKnbOEsOarCwyoLstcoEztwT0IJxqqQkNwDuA3a65sICvvHL2yoeV13psoDFh5IuiOMnIOKdQDwB4Mk3BypiA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.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 + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/country-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/country-regex/-/country-regex-1.1.0.tgz", + "integrity": "sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-font": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-font/-/css-font-1.2.0.tgz", + "integrity": "sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA==", + "dependencies": { + "css-font-size-keywords": "^1.0.0", + "css-font-stretch-keywords": "^1.0.1", + "css-font-style-keywords": "^1.0.1", + "css-font-weight-keywords": "^1.0.0", + "css-global-keywords": "^1.0.1", + "css-system-font-keywords": "^1.0.0", + "pick-by-alias": "^1.2.0", + "string-split-by": "^1.0.0", + "unquote": "^1.1.0" + } + }, + "node_modules/css-font-size-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz", + "integrity": "sha512-Q+svMDbMlelgCfH/RVDKtTDaf5021O486ZThQPIpahnIjUkMUslC+WuOQSWTgGSrNCH08Y7tYNEmmy0hkfMI8Q==" + }, + "node_modules/css-font-stretch-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz", + "integrity": "sha512-KmugPO2BNqoyp9zmBIUGwt58UQSfyk1X5DbOlkb2pckDXFSAfjsD5wenb88fNrD6fvS+vu90a/tsPpb9vb0SLg==" + }, + "node_modules/css-font-style-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz", + "integrity": "sha512-0Fn0aTpcDktnR1RzaBYorIxQily85M2KXRpzmxQPgh8pxUN9Fcn00I8u9I3grNr1QXVgCl9T5Imx0ZwKU973Vg==" + }, + "node_modules/css-font-weight-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz", + "integrity": "sha512-5So8/NH+oDD+EzsnF4iaG4ZFHQ3vaViePkL1ZbZ5iC/KrsCY+WHq/lvOgrtmuOQ9pBBZ1ADGpaf+A4lj1Z9eYA==" + }, + "node_modules/css-global-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz", + "integrity": "sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ==" + }, + "node_modules/css-system-font-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz", + "integrity": "sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==" + }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, + "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==", + "devOptional": true + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "node_modules/d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "dependencies": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "node_modules/d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "node_modules/d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-geo-projection": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-2.9.0.tgz", + "integrity": "sha512-ZULvK/zBn87of5rWAfFMc9mJOipeSo57O+BBitsKIXmU4rTVAnX1kSsJkE0R+TxY8pGNoM1nbyRRE7GYHhdOEQ==", + "dependencies": { + "commander": "2", + "d3-array": "1", + "d3-geo": "^1.12.0", + "resolve": "^1.1.10" + }, + "bin": { + "geo2svg": "bin/geo2svg", + "geograticule": "bin/geograticule", + "geoproject": "bin/geoproject", + "geoquantize": "bin/geoquantize", + "geostitch": "bin/geostitch" + } + }, + "node_modules/d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "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==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "node_modules/d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "dependencies": { + "d3-time": "1" + } + }, + "node_modules/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-kerning": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz", + "integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/draw-svg-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz", + "integrity": "sha512-P8j3IHxcgRMcY6sDzr0QvJDLzBnJJqpTG33UZ2Pvp8rw0apCHhJCWqYprqrXjrgHnJ6tuhP1iTJSAodPDHxwkg==", + "dependencies": { + "abs-svg-path": "~0.1.1", + "normalize-svg-path": "~0.1.0" + } + }, + "node_modules/dtype": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", + "integrity": "sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dup/-/dup-1.0.0.tgz", + "integrity": "sha512-Bz5jxMMC0wgp23Zm15ip1x8IhYRqJvF3nFC0UInJUDkN1z4uNPk9jTnfCUJXbOGiQ1JbXLQsiV41Fb+HXcj5BA==" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.368", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", + "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", + "dev": true + }, + "node_modules/elementary-circuits-directed-graph": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/elementary-circuits-directed-graph/-/elementary-circuits-directed-graph-1.3.1.tgz", + "integrity": "sha512-ZEiB5qkn2adYmpXGnJKkxT8uJHlW/mxmBpmeqawEHzPxh9HkLD4/1mFYX5l0On+f6rcPIt8/EWlRU2Vo3fX6dQ==", + "dependencies": { + "strongly-connected-components": "^1.0.1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz", + "integrity": "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", + "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/falafel": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.5.tgz", + "integrity": "sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==", + "dependencies": { + "acorn": "^7.1.1", + "isarray": "^2.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/falafel/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-isnumeric": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-isnumeric/-/fast-isnumeric-1.1.4.tgz", + "integrity": "sha512-1mM8qOr2LYz8zGaUdmiqRDiuue00Dxjgcb1NQR7TnhLVh6sQyngP9xvLo7Sl7LZpP/sk5eb+bcyWXw530NTBZw==", + "dependencies": { + "is-string-blank": "^1.0.1" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "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, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/flatten-vertex-data": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", + "integrity": "sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw==", + "dependencies": { + "dtype": "^2.0.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/font-atlas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/font-atlas/-/font-atlas-2.1.0.tgz", + "integrity": "sha512-kP3AmvX+HJpW4w3d+PiPR2X6E1yvsBXt2yhuCw+yReO9F1WYhvZwx3c95DGZGwg9xYzDGrgJYa885xmVA+28Cg==", + "dependencies": { + "css-font": "^1.0.0" + } + }, + "node_modules/font-measure": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/font-measure/-/font-measure-1.2.2.tgz", + "integrity": "sha512-mRLEpdrWzKe9hbfaF3Qpr06TAjquuBVP5cHy4b3hyeNdjc9i0PO6HniGsX5vjL5OWv7+Bd++NiooNpT/s8BvIA==", + "dependencies": { + "css-font": "^1.2.0" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, + "node_modules/get-canvas-context": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-canvas-context/-/get-canvas-context-1.0.2.tgz", + "integrity": "sha512-LnpfLf/TNzr9zVOGiIY6aKCz8EKuXmlYNV7CM2pUjBa/B+c2I15tS7KLySep75+FuerJdmArvJLcsAXWEy2H0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gl-mat4": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz", + "integrity": "sha512-sT5C0pwB1/e9G9AvAoLsoaJtbMGjfd/jfxo8jMCKqYYEnjZuFvqV5rehqar0538EmssjdDeiEWnKyBSTw7quoA==" + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==" + }, + "node_modules/gl-text": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gl-text/-/gl-text-1.4.0.tgz", + "integrity": "sha512-o47+XBqLCj1efmuNyCHt7/UEJmB9l66ql7pnobD6p+sgmBUdzfMZXIF0zD2+KRfpd99DJN+QXdvTFAGCKCVSmQ==", + "dependencies": { + "bit-twiddle": "^1.0.2", + "color-normalize": "^1.5.0", + "css-font": "^1.2.0", + "detect-kerning": "^2.1.2", + "es6-weak-map": "^2.0.3", + "flatten-vertex-data": "^1.0.2", + "font-atlas": "^2.1.0", + "font-measure": "^1.2.2", + "gl-util": "^3.1.2", + "is-plain-obj": "^1.1.0", + "object-assign": "^4.1.1", + "parse-rect": "^1.2.0", + "parse-unit": "^1.0.1", + "pick-by-alias": "^1.2.0", + "regl": "^2.0.0", + "to-px": "^1.0.1", + "typedarray-pool": "^1.1.0" + } + }, + "node_modules/gl-util": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/gl-util/-/gl-util-3.1.3.tgz", + "integrity": "sha512-dvRTggw5MSkJnCbh74jZzSoTOGnVYK+Bt+Ckqm39CVcl6+zSsxqWk4lr5NKhkqXHL6qvZAU9h17ZF8mIskY9mA==", + "dependencies": { + "is-browser": "^2.0.1", + "is-firefox": "^1.0.3", + "is-plain-obj": "^1.1.0", + "number-is-integer": "^1.0.1", + "object-assign": "^4.1.0", + "pick-by-alias": "^1.2.0", + "weak-map": "^1.0.5" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glsl-inject-defines": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz", + "integrity": "sha512-W49jIhuDtF6w+7wCMcClk27a2hq8znvHtlGnrYkSWEr8tHe9eA2dcnohlcAmxLYBSpSSdzOkRdyPTrx9fw49+A==", + "dependencies": { + "glsl-token-inject-block": "^1.0.0", + "glsl-token-string": "^1.0.1", + "glsl-tokenizer": "^2.0.2" + } + }, + "node_modules/glsl-resolve": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz", + "integrity": "sha512-xxFNsfnhZTK9NBhzJjSBGX6IOqYpvBHxxmo+4vapiljyGNCY0Bekzn0firQkQrazK59c1hYxMDxYS8MDlhw4gA==", + "dependencies": { + "resolve": "^0.6.1", + "xtend": "^2.1.2" + } + }, + "node_modules/glsl-resolve/node_modules/resolve": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", + "integrity": "sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg==" + }, + "node_modules/glsl-resolve/node_modules/xtend": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", + "integrity": "sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/glsl-token-assignments": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz", + "integrity": "sha512-OwXrxixCyHzzA0U2g4btSNAyB2Dx8XrztY5aVUCjRSh4/D0WoJn8Qdps7Xub3sz6zE73W3szLrmWtQ7QMpeHEQ==" + }, + "node_modules/glsl-token-defines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz", + "integrity": "sha512-Vb5QMVeLjmOwvvOJuPNg3vnRlffscq2/qvIuTpMzuO/7s5kT+63iL6Dfo2FYLWbzuiycWpbC0/KV0biqFwHxaQ==", + "dependencies": { + "glsl-tokenizer": "^2.0.0" + } + }, + "node_modules/glsl-token-depth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz", + "integrity": "sha512-eQnIBLc7vFf8axF9aoi/xW37LSWd2hCQr/3sZui8aBJnksq9C7zMeUYHVJWMhFzXrBU7fgIqni4EhXVW4/krpg==" + }, + "node_modules/glsl-token-descope": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz", + "integrity": "sha512-kS2PTWkvi/YOeicVjXGgX5j7+8N7e56srNDEHDTVZ1dcESmbmpmgrnpjPcjxJjMxh56mSXYoFdZqb90gXkGjQw==", + "dependencies": { + "glsl-token-assignments": "^2.0.0", + "glsl-token-depth": "^1.1.0", + "glsl-token-properties": "^1.0.0", + "glsl-token-scope": "^1.1.0" + } + }, + "node_modules/glsl-token-inject-block": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz", + "integrity": "sha512-q/m+ukdUBuHCOtLhSr0uFb/qYQr4/oKrPSdIK2C4TD+qLaJvqM9wfXIF/OOBjuSA3pUoYHurVRNao6LTVVUPWA==" + }, + "node_modules/glsl-token-properties": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz", + "integrity": "sha512-dSeW1cOIzbuUoYH0y+nxzwK9S9O3wsjttkq5ij9ZGw0OS41BirKJzzH48VLm8qLg+au6b0sINxGC0IrGwtQUcA==" + }, + "node_modules/glsl-token-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz", + "integrity": "sha512-YKyOMk1B/tz9BwYUdfDoHvMIYTGtVv2vbDSLh94PT4+f87z21FVdou1KNKgF+nECBTo0fJ20dpm0B1vZB1Q03A==" + }, + "node_modules/glsl-token-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz", + "integrity": "sha512-1mtQ47Uxd47wrovl+T6RshKGkRRCYWhnELmkEcUAPALWGTFe2XZpH3r45XAwL2B6v+l0KNsCnoaZCSnhzKEksg==" + }, + "node_modules/glsl-token-whitespace-trim": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz", + "integrity": "sha512-ZJtsPut/aDaUdLUNtmBYhaCmhIjpKNg7IgZSfX5wFReMc2vnj8zok+gB/3Quqs0TsBSX/fGnqUUYZDqyuc2xLQ==" + }, + "node_modules/glsl-tokenizer": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz", + "integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==", + "dependencies": { + "through2": "^0.6.3" + } + }, + "node_modules/glsl-tokenizer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/glsl-tokenizer/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/glsl-tokenizer/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/glsl-tokenizer/node_modules/through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==", + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/glslify": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glslify/-/glslify-7.1.1.tgz", + "integrity": "sha512-bud98CJ6kGZcP9Yxcsi7Iz647wuDz3oN+IZsjCRi5X1PI7t/xPKeL0mOwXJjo+CRZMqvq0CkSJiywCcY7kVYog==", + "dependencies": { + "bl": "^2.2.1", + "concat-stream": "^1.5.2", + "duplexify": "^3.4.5", + "falafel": "^2.1.0", + "from2": "^2.3.0", + "glsl-resolve": "0.0.1", + "glsl-token-whitespace-trim": "^1.0.0", + "glslify-bundle": "^5.0.0", + "glslify-deps": "^1.2.5", + "minimist": "^1.2.5", + "resolve": "^1.1.5", + "stack-trace": "0.0.9", + "static-eval": "^2.0.5", + "through2": "^2.0.1", + "xtend": "^4.0.0" + }, + "bin": { + "glslify": "bin.js" + } + }, + "node_modules/glslify-bundle": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.1.1.tgz", + "integrity": "sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A==", + "dependencies": { + "glsl-inject-defines": "^1.0.1", + "glsl-token-defines": "^1.0.0", + "glsl-token-depth": "^1.1.1", + "glsl-token-descope": "^1.0.2", + "glsl-token-scope": "^1.1.1", + "glsl-token-string": "^1.0.1", + "glsl-token-whitespace-trim": "^1.0.0", + "glsl-tokenizer": "^2.0.2", + "murmurhash-js": "^1.0.0", + "shallow-copy": "0.0.1" + } + }, + "node_modules/glslify-deps": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.2.tgz", + "integrity": "sha512-7S7IkHWygJRjcawveXQjRXLO2FTjijPDYC7QfZyAQanY+yGLCFHYnPtsGT9bdyHiwPTw/5a1m1M9hamT2aBpag==", + "dependencies": { + "@choojs/findup": "^0.2.0", + "events": "^3.2.0", + "glsl-resolve": "0.0.1", + "glsl-tokenizer": "^2.0.0", + "graceful-fs": "^4.1.2", + "inherits": "^2.0.1", + "map-limit": "0.0.1", + "resolve": "^1.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" + }, + "node_modules/has-hover": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-hover/-/has-hover-1.0.1.tgz", + "integrity": "sha512-0G6w7LnlcpyDzpeGUTuT0CEw05+QlMuGVk1IHNAlHrGJITGodjZu3x8BNDUMfKJSZXNB2ZAclqc1bvrd+uUpfg==", + "dependencies": { + "is-browser": "^2.0.1" + } + }, + "node_modules/has-passive-events": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-passive-events/-/has-passive-events-1.0.0.tgz", + "integrity": "sha512-2vSj6IeIsgvsRMyeQ0JaCX5Q3lX4zMn5HpoVc7MEhQ6pv8Iq9rsXjsp+E5ZwaT7T0xhMT0KmU8gtt1EFVdbJiw==", + "dependencies": { + "is-browser": "^2.0.1" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz", + "integrity": "sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-firefox": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-firefox/-/is-firefox-1.0.3.tgz", + "integrity": "sha512-6Q9ITjvWIm0Xdqv+5U12wgOKEM2KoBw4Y926m0OFkvlCxnbG94HKAsVz8w3fWcfAS5YA2fJORXX1dLrkprCCxA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-mobile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-4.0.0.tgz", + "integrity": "sha512-mlcHZA84t1qLSuWkt2v0I2l61PYdyQDt4aG1mLIXF5FDMm4+haBCxCPYSr/uwqQNRk1MiTizn0ypEuRAOLRAew==" + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string-blank": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", + "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==" + }, + "node_modules/is-svg-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-svg-path/-/is-svg-path-1.0.2.tgz", + "integrity": "sha512-Lj4vePmqpPR1ZnRctHv8ltSh1OrSxHkhUkd7wi+VQdcdP15/KvQFyk7LhNuM7ZW0EVbJz8kZLVmL9quLrfq4Kg==" + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" + }, + "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, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kdbush": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.1.0.tgz", + "integrity": "sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "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==", + "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, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==", + "dependencies": { + "once": "~1.3.0" + } + }, + "node_modules/map-limit/node_modules/once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/mapbox-gl": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.3.tgz", + "integrity": "sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==", + "peer": true, + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/maplibre-gl": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", + "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@types/geojson": "^7946.0.14", + "@types/geojson-vt": "3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.0", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "global-prefix": "^4.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.3.0", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/@mapbox/tiny-sdf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.2.0.tgz", + "integrity": "sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==" + }, + "node_modules/maplibre-gl/node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/maplibre-gl/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==" + }, + "node_modules/maplibre-gl/node_modules/geojson-vt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.3.tgz", + "integrity": "sha512-jR1MwkLaZGa8Zftct9ZFruyWFrdl9ZyD2OliXNy9Qq5bBPeg5wHVpBQF9p5GjnicSDQqvBVpysxTPKmWdsfWMA==" + }, + "node_modules/maplibre-gl/node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==" + }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==" + }, + "node_modules/maplibre-gl/node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/maplibre-gl/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-log2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz", + "integrity": "sha512-9W0yGtkaMAkf74XGYVy4Dqw3YUMnTNB2eeiw9aQbUl4A3KmuCEHTt2DgAB07ENzOYAjsYSAYufkAq0Zd+jU7zA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mouse-event-offset": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz", + "integrity": "sha512-s9sqOs5B1Ykox3Xo8b3Ss2IQju4UwlW6LSR+Q5FXWpprJ5fzMLefIIItr3PH8RwzfGy6gxs/4GAmiNuZScE25w==" + }, + "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==" + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-svg-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz", + "integrity": "sha512-1/kmYej2iedi5+ROxkRESL/pI02pkg0OBnaR4hJkSIX6+ORzepwbuUXfrdZaPjysTsJInj0Rj5NuX027+dMBvA==" + }, + "node_modules/number-is-integer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz", + "integrity": "sha512-Dq3iuiFBkrbmuQjGFFF3zckXNCQoSD37/SdSbgcBailUx6knDvDwb5CympBgcoWHy36sfS12u74MHYkXyHq6bg==", + "dependencies": { + "is-finite": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parenthesis": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz", + "integrity": "sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==" + }, + "node_modules/parse-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", + "integrity": "sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA==", + "dependencies": { + "pick-by-alias": "^1.2.0" + } + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==" + }, + "node_modules/parse-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz", + "integrity": "sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/pick-by-alias": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", + "integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==" + }, + "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 + }, + "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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plotly.js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.6.0.tgz", + "integrity": "sha512-Fu5IaetcuxaeQPULk4wfIik0MnvIsEb5ynOsPAMfhAnjkPOEDFG7eSb/3ZZq1DW5MwYvZFXaTFHpal4U1Q5Yig==", + "dependencies": { + "@plotly/d3": "3.8.2", + "@plotly/d3-sankey": "0.7.2", + "@plotly/d3-sankey-circular": "0.33.1", + "@plotly/mapbox-gl": "1.13.4", + "@plotly/regl": "^2.1.2", + "@turf/area": "^7.1.0", + "@turf/bbox": "^7.1.0", + "@turf/centroid": "^7.1.0", + "base64-arraybuffer": "^1.0.2", + "color-normalize": "1.5.0", + "color-rgba": "3.0.0", + "country-regex": "^1.1.0", + "d3-force": "^1.2.1", + "d3-format": "^1.4.5", + "d3-geo": "^1.12.1", + "d3-geo-projection": "^2.9.0", + "d3-hierarchy": "^1.1.9", + "d3-interpolate": "^3.0.1", + "d3-time": "^1.1.0", + "d3-time-format": "^2.2.3", + "fast-isnumeric": "^1.1.4", + "gl-mat4": "^1.2.0", + "gl-text": "^1.4.0", + "has-hover": "^1.0.1", + "has-passive-events": "^1.0.0", + "is-mobile": "^4.0.0", + "maplibre-gl": "^4.7.1", + "mouse-event-offset": "^3.0.2", + "native-promise-only": "^0.8.1", + "parse-svg-path": "^0.1.2", + "point-in-polygon": "^1.1.0", + "polybooljs": "^1.2.2", + "probe-image-size": "^7.2.3", + "regl-error2d": "^2.0.12", + "regl-line2d": "^3.1.3", + "regl-scatter2d": "^3.3.1", + "regl-splom": "^1.0.14", + "strongly-connected-components": "^1.0.1", + "svg-path-sdf": "^1.1.3", + "tinycolor2": "^1.4.2", + "topojson-client": "^3.1.0", + "webgl-context": "^2.2.0", + "world-calendars": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, + "node_modules/polybooljs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/polybooljs/-/polybooljs-1.2.2.tgz", + "integrity": "sha512-ziHW/02J0XuNuUtmidBc6GXE8YohYydp3DWPWXYsd7O721TjcmN+k6ezjdwkDqep+gnWnFY+yqZHvzElra2oCg==" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/probe-image-size": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.3.0.tgz", + "integrity": "sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "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==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "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==" + }, + "node_modules/react-leaflet": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", + "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==", + "dependencies": { + "@react-leaflet/core": "^3.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/react-plotly.js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-plotly.js/-/react-plotly.js-2.6.0.tgz", + "integrity": "sha512-g93xcyhAVCSt9kV1svqG1clAEdL6k3U+jjuSzfTV7owaSU9Go6Ph8bl25J+jKfKvIGAEYpe4qj++WHJuc9IaeA==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "plotly.js": ">1.34.0", + "react": ">0.13.0" + } + }, + "node_modules/react-router": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", + "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz", + "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==", + "dependencies": { + "react-router": "7.17.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/regl": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/regl/-/regl-2.1.1.tgz", + "integrity": "sha512-+IOGrxl3FZ8ZM9ixCWQZzFRiRn7Rzn9bu3iFHwg/yz4tlOUQgbO4PHLgG+1ZT60zcIV8tief6Qrmyl8qcoJP0g==" + }, + "node_modules/regl-error2d": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/regl-error2d/-/regl-error2d-2.0.12.tgz", + "integrity": "sha512-r7BUprZoPO9AbyqM5qlJesrSRkl+hZnVKWKsVp7YhOl/3RIpi4UDGASGJY0puQ96u5fBYw/OlqV24IGcgJ0McA==", + "dependencies": { + "array-bounds": "^1.0.1", + "color-normalize": "^1.5.0", + "flatten-vertex-data": "^1.0.2", + "object-assign": "^4.1.1", + "pick-by-alias": "^1.2.0", + "to-float32": "^1.1.0", + "update-diff": "^1.1.0" + } + }, + "node_modules/regl-line2d": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/regl-line2d/-/regl-line2d-3.1.3.tgz", + "integrity": "sha512-fkgzW+tTn4QUQLpFKsUIE0sgWdCmXAM3ctXcCgoGBZTSX5FE2A0M7aynz7nrZT5baaftLrk9te54B+MEq4QcSA==", + "dependencies": { + "array-bounds": "^1.0.1", + "array-find-index": "^1.0.2", + "array-normalize": "^1.1.4", + "color-normalize": "^1.5.0", + "earcut": "^2.1.5", + "es6-weak-map": "^2.0.3", + "flatten-vertex-data": "^1.0.2", + "object-assign": "^4.1.1", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0", + "to-float32": "^1.1.0" + } + }, + "node_modules/regl-scatter2d": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/regl-scatter2d/-/regl-scatter2d-3.4.0.tgz", + "integrity": "sha512-DavKQlHsI+iHZuLgOL+yGkg+sPd94CS+7FCBWkcQ6s/TbaNfUsF9eN591fjjSWIoKrGNfb/SEGhsXR5lXjqZ2w==", + "dependencies": { + "@plotly/point-cluster": "^3.1.9", + "array-bounds": "^1.0.1", + "color-id": "^1.1.0", + "color-normalize": "^1.5.0", + "flatten-vertex-data": "^1.0.2", + "glslify": "^7.0.0", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0", + "to-float32": "^1.1.0", + "update-diff": "^1.1.0" + } + }, + "node_modules/regl-splom": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/regl-splom/-/regl-splom-1.0.14.tgz", + "integrity": "sha512-OiLqjmPRYbd7kDlHC6/zDf6L8lxgDC65BhC8JirhP4ykrK4x22ZyS+BnY8EUinXKDeMgmpRwCvUmk7BK4Nweuw==", + "dependencies": { + "array-bounds": "^1.0.1", + "array-range": "^1.0.1", + "color-alpha": "^1.0.4", + "flatten-vertex-data": "^1.0.2", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0", + "raf": "^3.4.1", + "regl-scatter2d": "^3.2.3" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, + "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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" + }, + "node_modules/shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", + "engines": { + "node": "*" + } + }, + "node_modules/static-eval": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", + "dependencies": { + "escodegen": "^2.1.0" + } + }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string-split-by": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string-split-by/-/string-split-by-1.0.0.tgz", + "integrity": "sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==", + "dependencies": { + "parenthesis": "^3.1.5" + } + }, + "node_modules/strongly-connected-components": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz", + "integrity": "sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==" + }, + "node_modules/supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "dependencies": { + "kdbush": "^3.0.0" + } + }, + "node_modules/supercluster/node_modules/kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==" + }, + "node_modules/svg-path-bounds": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz", + "integrity": "sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ==", + "dependencies": { + "abs-svg-path": "^0.1.1", + "is-svg-path": "^1.0.1", + "normalize-svg-path": "^1.0.0", + "parse-svg-path": "^0.1.2" + } + }, + "node_modules/svg-path-bounds/node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, + "node_modules/svg-path-sdf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/svg-path-sdf/-/svg-path-sdf-1.1.3.tgz", + "integrity": "sha512-vJJjVq/R5lSr2KLfVXVAStktfcfa1pNFjFOgyJnzZFXlO/fDZ5DmM8FpnSKKzLPfEYTVeXuVBTHF296TpxuJVg==", + "dependencies": { + "bitmap-sdf": "^1.0.0", + "draw-svg-path": "^1.0.0", + "is-svg-path": "^1.0.1", + "parse-svg-path": "^0.1.2", + "svg-path-bounds": "^1.0.1" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, + "node_modules/to-float32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/to-float32/-/to-float32-1.1.0.tgz", + "integrity": "sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg==" + }, + "node_modules/to-px": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/to-px/-/to-px-1.1.0.tgz", + "integrity": "sha512-bfg3GLYrGoEzrGoE05TAL/Uw+H/qrf2ptr9V3W7U0lkjjyYnIfgxmVLUfhQ1hZpIQwin81uxhDjvUkDYsC0xWw==", + "dependencies": { + "parse-unit": "^1.0.1" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typedarray-pool": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typedarray-pool/-/typedarray-pool-1.2.0.tgz", + "integrity": "sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ==", + "dependencies": { + "bit-twiddle": "^1.0.0", + "dup": "^1.0.0" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "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" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-diff/-/update-diff-1.1.0.tgz", + "integrity": "sha512-rCiBPiHxZwT4+sBhEbChzpO5hYHjm91kScWgdHf4Qeafs6Ba7MBl+d9GlGv72bcTZQO0sLmtQS1pHSWoCLtN/A==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, + "node_modules/weak-map": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", + "integrity": "sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw==" + }, + "node_modules/webgl-context": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webgl-context/-/webgl-context-2.2.0.tgz", + "integrity": "sha512-q/fGIivtqTT7PEoF07axFIlHNk/XCPaYpq64btnepopSWvKNFkoORlQYgqDigBIuGA1ExnFd/GnSUnBNEPQY7Q==", + "dependencies": { + "get-canvas-context": "^1.0.1" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/world-calendars": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.4.tgz", + "integrity": "sha512-VGRnLJS+xJmGDPodgJRnGIDwGu0s+Cr9V2HB3EzlDZ5n0qb8h5SJtGUEkjrphZYAglEiXZ6kiXdmk0H/h/uu/w==", + "dependencies": { + "object-assign": "^4.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..d2234ab --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,38 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.17.0", + "leaflet": "^1.9.4", + "plotly.js": "^3.6.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-leaflet": "^5.0.0", + "react-plotly.js": "^2.6.0", + "react-router-dom": "^7.17.0", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "autoprefixer": "^10.5.0", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "postcss": "^8.5.15", + "tailwindcss": "^4.3.0", + "vite": "^8.0.12" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..f90339d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..9184e86 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,149 @@ +import { useState, useEffect } from 'react' +import './index.css' +import Navbar from './components/Navbar' +import PortfolioBuilder from './components/PortfolioBuilder' +import Dashboard from './pages/Dashboard' +import LandingPage from './pages/LandingPage' +import { usePortfolioStore } from './store/portfolio' + +function useNexusFavicon() { + useEffect(() => { + const canvas = document.createElement('canvas') + canvas.width = 64 + canvas.height = 64 + const ctx = canvas.getContext('2d') + + let link = document.querySelector("link[rel~='icon']") + if (!link) { + link = document.createElement('link') + link.rel = 'icon' + document.head.appendChild(link) + } + + let a1 = 0, a2 = Math.PI / 3, a3 = -Math.PI / 3 + let frame + + function draw() { + const cx = 32, cy = 32 + ctx.clearRect(0, 0, 64, 64) + + // Background circle + ctx.fillStyle = '#080e1a' + ctx.beginPath() + ctx.arc(cx, cy, 30, 0, Math.PI * 2) + ctx.fill() + + // Helper: draw one orbit + satellite + function orbit(angle, color, rx, ry, dotR) { + ctx.save() + ctx.translate(cx, cy) + ctx.rotate(angle) + ctx.strokeStyle = color + ctx.lineWidth = 1 + ctx.globalAlpha = 0.65 + ctx.beginPath() + ctx.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2) + ctx.stroke() + // Satellite + ctx.globalAlpha = 1 + ctx.shadowBlur = 8 + ctx.shadowColor = color + ctx.fillStyle = color + ctx.beginPath() + ctx.arc(rx, 0, dotR, 0, Math.PI * 2) + ctx.fill() + ctx.shadowBlur = 0 + ctx.restore() + } + + orbit(a1, '#00c8f0', 27, 7.5, 3.5) + orbit(a2, '#8b5cf6', 27, 7.5, 3) + orbit(a3, '#3ddc84', 27, 7.5, 2.8) + + // Core glow + const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, 10) + g.addColorStop(0, 'rgba(0,200,240,0.95)') + g.addColorStop(0.45, 'rgba(0,200,240,0.35)') + g.addColorStop(1, 'rgba(0,200,240,0)') + ctx.fillStyle = g + ctx.beginPath() + ctx.arc(cx, cy, 10, 0, Math.PI * 2) + ctx.fill() + + // Core dot + ctx.shadowBlur = 10 + ctx.shadowColor = '#00c8f0' + ctx.fillStyle = '#00c8f0' + ctx.beginPath() + ctx.arc(cx, cy, 4.5, 0, Math.PI * 2) + ctx.fill() + ctx.shadowBlur = 0 + + // White center + ctx.fillStyle = '#ffffff' + ctx.beginPath() + ctx.arc(cx, cy, 1.8, 0, Math.PI * 2) + ctx.fill() + + link.type = 'image/png' + link.href = canvas.toDataURL('image/png') + + const fps = 60 + a1 += (2 * Math.PI) / (7 * fps) + a2 += (2 * Math.PI) / (11 * fps) + a3 -= (2 * Math.PI) / (5 * fps) + + frame = requestAnimationFrame(draw) + } + + draw() + return () => cancelAnimationFrame(frame) + }, []) +} + +export default function App() { + const { fromURL } = usePortfolioStore() + const [inApp, setInApp] = useState(() => sessionStorage.getItem('qfx-v') === '1') + + useNexusFavicon() + + useEffect(() => { fromURL() }, []) + + function enterApp() { + sessionStorage.setItem('qfx-v', '1') + setInApp(true) + } + + if (!inApp) return + + return ( +
+ + + {/* Main layout: sidebar + content */} +
+ + {/* Left sidebar */} +
+ +
+ + {/* Dashboard pane */} +
+ {/* Subtle inner gradient */} +
+ +
+
+
+
+ ) +} diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js new file mode 100644 index 0000000..665a30d --- /dev/null +++ b/frontend/src/api/client.js @@ -0,0 +1,30 @@ +import axios from 'axios' + +const api = axios.create({ baseURL: '/api' }) + +// Market +export const fetchMarket = (ticker) => api.get(`/market/${ticker}`).then(r=>r.data) +export const fetchIVSurface = (ticker) => api.get(`/market/iv-surface/${ticker}`).then(r=>r.data) + +// Forex +export const fetchForexRates = () => api.get('/forex/rates').then(r=>r.data) +export const fetchForexRate = (pair) => api.get(`/forex/rate/${pair}`).then(r=>r.data) +export const fetchOHLC = (pair,iv,period) => api.get(`/forex/ohlc/${pair}`,{params:{interval:iv,period}}).then(r=>r.data) +export const fetchSignals = (pair) => api.get(`/forex/signals/${pair}`).then(r=>r.data) +export const fetchPairs = () => api.get('/forex/pairs').then(r=>r.data) + +// Options analytics +export const fetchStrategies = () => api.get('/strategies').then(r=>r.data) +export const computeGreeks = (p) => api.post('/greeks', p).then(r=>r.data) +export const computeSurface = (p) => api.post('/surface', p).then(r=>r.data) +export const computeMonteCarlo = (p) => api.post('/montecarlo', p).then(r=>r.data) +export const computeScenarios = (p) => api.post('/scenarios', p).then(r=>r.data) +export const exportCSV = (p) => api.post('/export/csv', p, {responseType:'blob'}).then(r=>r.data) + +// Institutional flow +export const fetchInstitutional = (pair, weeks) => + api.get(`/institutional/${pair}`, { params: { weeks } }).then(r => r.data) + +// Economic calendar (Forex Factory) +export const fetchCalendar = () => + api.get('/news/calendar').then(r => r.data) diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/frontend/src/assets/hero.png differ diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/components/BreakevenChart.jsx b/frontend/src/components/BreakevenChart.jsx new file mode 100644 index 0000000..7afe422 --- /dev/null +++ b/frontend/src/components/BreakevenChart.jsx @@ -0,0 +1,163 @@ +import { useEffect, useState } from 'react' +import _Plot from 'react-plotly.js' +const Plot = _Plot?.default ?? _Plot +import { usePortfolioStore } from '../store/portfolio' +import { computeSurface } from '../api/client' + +export default function BreakevenChart() { + const { legs, S, sigma, T, r_d, r_f, pair } = usePortfolioStore() + const [chartData, setChartData] = useState(null) + const [loading, setLoading] = useState(false) + + useEffect(() => { + if (!legs.length) return + setLoading(true) + computeSurface({ + options: legs.map(({ type, K, T, qty }) => ({ type, K, T, qty })), + S_low: S * 0.70, S_high: S * 1.30, S_steps: 80, + vol_low: sigma, vol_high: sigma + 0.001, vol_steps: 2, + T, r_d, r_f, + }) + .then(d => { + const spots = d.S_range + const pnl = d.pnl.map(row => row[0]) + setChartData({ spots, pnl }) + }) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [S, sigma, T, r_d, r_f, JSON.stringify(legs)]) + + if (loading) return ( +
+
+
+ Computing breakeven profile… +
+
+
+ ) + if (!chartData) return null + + const { spots, pnl } = chartData + const zeroLine = spots.map(() => 0) + + // Find breakeven crossings + const breakevens = [] + for (let i = 0; i < pnl.length - 1; i++) { + if (Math.sign(pnl[i]) !== Math.sign(pnl[i + 1])) { + const be = spots[i] + (spots[i+1]-spots[i])*(-pnl[i]/(pnl[i+1]-pnl[i])) + breakevens.push(parseFloat(be.toFixed(5))) + } + } + + // Find min/max pnl idx for annotation + const maxPnlIdx = pnl.reduce((mi,v,i,a) => v>a[mi]?i:mi, 0) + const minPnlIdx = pnl.reduce((mi,v,i,a) => v ({ + type:'scatter', mode:'markers+text', + x:[be], y:[0], + marker:{ color:'#00ff88', size:9, symbol:'diamond', line:{ color:'#0a0e1a', width:2 } }, + text:[`BE ${be}`], textposition:'top center', + textfont:{ color:'#00ff88', size:9 }, + name:`Breakeven ${i+1}`, + })), + // Max profit / loss markers + { + type:'scatter', mode:'markers', + x:[spots[maxPnlIdx]], y:[pnl[maxPnlIdx]], + marker:{ color:'#00ff88', size:7, symbol:'triangle-up', opacity:0.8 }, + showlegend:false, hovertemplate:'Max Profit: %{y:.5f}', + }, + { + type:'scatter', mode:'markers', + x:[spots[minPnlIdx]], y:[pnl[minPnlIdx]], + marker:{ color:'#ff3d5a', size:7, symbol:'triangle-down', opacity:0.8 }, + showlegend:false, hovertemplate:'Max Loss: %{y:.5f}', + }, + ] + + return ( +
+ {/* Header */} +
+
+
P&L at Expiry vs Spot ({pair})
+
+ Fixed vol σ={sigma} · r_d={r_d} · r_f={r_f} +
+
+ {breakevens.length > 0 && ( +
+ {breakevens.map((be, i) => ( + + BE: {be} + + ))} +
+ )} +
+ + {/* Stats row */} +
+ {[ + { label:'Max Profit', value: Math.max(...pnl).toFixed(5), pos:true }, + { label:'Max Loss', value: Math.min(...pnl).toFixed(5), pos:false }, + { label:'Breakevens', value: breakevens.length, pos: true }, + { label:'Spot', value: S, pos:true }, + ].map(({ label, value, pos }) => ( +
+ {label}: + + {value} + +
+ ))} +
+ +
+ +
+
+ ) +} diff --git a/frontend/src/components/CandlestickChart.jsx b/frontend/src/components/CandlestickChart.jsx new file mode 100644 index 0000000..bb23930 --- /dev/null +++ b/frontend/src/components/CandlestickChart.jsx @@ -0,0 +1,354 @@ +import { useState, useEffect, useCallback, useRef } from 'react' +import _Plot from 'react-plotly.js' +const Plot = _Plot?.default ?? _Plot +import { usePortfolioStore } from '../store/portfolio' +import { fetchOHLC } from '../api/client' + +const INTERVALS = [ + { label: '1m', val: '1m', period: '1d' }, + { label: '5m', val: '5m', period: '2d' }, + { label: '15m', val: '15m', period: '5d' }, + { label: '1h', val: '1h', period: '1mo' }, + { label: '1d', val: '1d', period: '1mo' }, +] + +function ema(arr, period) { + const k = 2 / (period + 1) + const out = [] + let e = null + for (let i = 0; i < arr.length; i++) { + if (i < period - 1) { out.push(null); continue } + if (i === period - 1) { + e = arr.slice(0, period).reduce((s, x) => s + x, 0) / period + } else { + e = arr[i] * k + e * (1 - k) + } + out.push(e) + } + return out +} + +// Inject SVG linearGradient into Plotly's SVG after render +function injectSvgGradient(container, colorHex, gradId = 'qfx-price-grad') { + if (!container) return + const svg = container.querySelector('svg.main-svg') + if (!svg) return + + svg.getElementById(gradId + '-defs')?.remove() + + const ns = 'http://www.w3.org/2000/svg' + const defs = document.createElementNS(ns, 'defs') + defs.id = gradId + '-defs' + + const grad = document.createElementNS(ns, 'linearGradient') + grad.id = gradId + grad.setAttribute('x1', '0%'); grad.setAttribute('y1', '0%') + grad.setAttribute('x2', '0%'); grad.setAttribute('y2', '100%') + + const r = parseInt(colorHex.slice(1,3), 16) + const g = parseInt(colorHex.slice(3,5), 16) + const b = parseInt(colorHex.slice(5,7), 16) + + ;[ + { offset: '0%', opacity: 0.45 }, + { offset: '35%', opacity: 0.20 }, + { offset: '75%', opacity: 0.06 }, + { offset: '100%', opacity: 0.0 }, + ].forEach(({ offset, opacity }) => { + const stop = document.createElementNS(ns, 'stop') + stop.setAttribute('offset', offset) + stop.setAttribute('stop-color', `rgb(${r},${g},${b})`) + stop.setAttribute('stop-opacity', String(opacity)) + grad.appendChild(stop) + }) + + defs.appendChild(grad) + svg.prepend(defs) + + // Apply gradient to the main price area fill (2nd fill path — index 1) + const fillPaths = svg.querySelectorAll('.fills path') + if (fillPaths.length >= 2) { + fillPaths[1].setAttribute('fill', `url(#${gradId})`) + } +} + +export default function PriceChart() { + const { pair } = usePortfolioStore() + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [interval, setInterval] = useState('5m') + const [error, setError] = useState(null) + const [fading, setFading] = useState(false) + const containerRef = useRef(null) + + const load = useCallback(async (ivl) => { + setFading(true) + setLoading(true); setError(null) + const cfg = INTERVALS.find(x => x.val === ivl) || INTERVALS[1] + try { + const d = await fetchOHLC(pair, cfg.val, cfg.period) + await new Promise(r => setTimeout(r, 200)) + setData(d) + setFading(false) + } catch (e) { + setError(e?.response?.data?.detail || 'Data unavailable.') + setFading(false) + } finally { setLoading(false) } + }, [pair]) + + useEffect(() => { load(interval) }, [pair, interval]) + + // Derived values — all computed BEFORE any hooks that depend on them + const closes = data?.close || [] + const last = closes.at(-1) + const first = closes[0] + const periodHigh = data ? Math.max(...data.high) : null + const periodLow = data ? Math.min(...data.low) : null + const change = (last && first) ? ((last - first) / first * 100) : null + const isUp = change !== null ? change >= 0 : true + + const lineColor = isUp ? '#00ff88' : '#ff3d5a' + const fillColor = isUp ? 'rgba(0,255,136,0.18)' : 'rgba(255,61,90,0.18)' + const glowColor = isUp ? 'rgba(0,255,136,0.10)' : 'rgba(255,61,90,0.10)' + const ema20 = closes.length >= 20 ? ema(closes, 20) : [] + const ema9 = closes.length >= 9 ? ema(closes, 9) : [] + + // Gradient injection whenever data or direction changes + useEffect(() => { + const t = setTimeout(() => injectSvgGradient(containerRef.current, lineColor), 150) + return () => clearTimeout(t) + }, [data, lineColor]) + + const handlePlotRender = useCallback(() => { + setTimeout(() => injectSvgGradient(containerRef.current, lineColor), 80) + }, [lineColor]) + + // Baseline just below data minimum — keeps fill bounded within visible chart area + const minClose = data ? Math.min(...closes) * 0.9997 : 0 + const baseDates = data?.dates || [] + + // Build traces + const plotData = data ? [ + // Invisible baseline at bottom — fill target for tonexty + { + type: 'scatter', mode: 'lines', + x: baseDates, y: baseDates.map(() => minClose), + line: { color: 'rgba(0,0,0,0)', width: 0 }, + showlegend: false, hoverinfo: 'skip', + }, + // Main price line — fills down to the baseline above + { + type: 'scatter', mode: 'lines', + x: data.dates, y: closes, + name: pair, + line: { color: lineColor, width: 2.5, shape: 'spline', smoothing: 0.5 }, + fill: 'tonexty', + fillcolor: fillColor, + hovertemplate: '%{x|%H:%M %b %d}
%{y:.5f}', + }, + // Glow layer + { + type: 'scatter', mode: 'lines', + x: data.dates, y: closes, + line: { color: glowColor, width: 18, shape: 'spline', smoothing: 0.5 }, + showlegend: false, hoverinfo: 'skip', + }, + ...(ema20.length ? [{ + type: 'scatter', mode: 'lines', + x: data.dates, y: ema20, + name: 'EMA 20', + line: { color: 'rgba(167,139,250,0.85)', width: 1.5, shape: 'spline', smoothing: 0.5 }, + hovertemplate: 'EMA20: %{y:.5f}', + }] : []), + ...(ema9.length ? [{ + type: 'scatter', mode: 'lines', + x: data.dates, y: ema9, + name: 'EMA 9', + line: { color: 'rgba(255,215,0,0.7)', width: 1.2, shape: 'spline', smoothing: 0.5 }, + hovertemplate: 'EMA9: %{y:.5f}', + }] : []), + ] : [] + + const layout = { + paper_bgcolor: 'transparent', + plot_bgcolor: 'rgba(4,10,28,0.8)', + font: { color: '#475569', family: 'JetBrains Mono, monospace', size: 10 }, + margin: { l: 12, r: 68, t: 12, b: 44 }, + xaxis: { + type: 'date', + gridcolor: 'rgba(0,212,255,0.05)', + linecolor: 'rgba(0,212,255,0.1)', + color: '#475569', + rangeslider: { visible: false }, + showspikes: true, spikecolor: 'rgba(0,212,255,0.35)', spikethickness: 1, + tickfont: { size: 9 }, zeroline: false, + }, + yaxis: { + gridcolor: 'rgba(0,212,255,0.05)', + linecolor: 'rgba(0,212,255,0.1)', + color: '#475569', + side: 'right', tickformat: '.5f', + showspikes: true, spikecolor: 'rgba(0,212,255,0.35)', spikethickness: 1, + tickfont: { size: 9 }, zeroline: false, + }, + legend: { + bgcolor: 'rgba(4,10,28,0.85)', + bordercolor: 'rgba(0,212,255,0.12)', borderwidth: 1, + font: { color: '#64748b', size: 9 }, + orientation: 'h', x: 0.01, y: 0.99, xanchor: 'left', yanchor: 'top', + }, + dragmode: 'pan', + hovermode: 'x unified', + hoverlabel: { + bgcolor: 'rgba(4,10,28,0.95)', + bordercolor: isUp ? 'rgba(0,255,136,0.4)' : 'rgba(255,61,90,0.4)', + font: { color: '#e0f2fe', family: 'JetBrains Mono, monospace', size: 11 }, + }, + } + + return ( +
+ + {/* Header */} +
+
+
+ {pair} — LINE CHART + {loading &&
} +
+ {data && ( +
+ + {last?.toFixed(5)} + + {change !== null && ( + + {isUp ? '▲' : '▼'} {Math.abs(change).toFixed(3)}% + + )} +
+ )} +
+ + {data && ( +
+ {[ + { label:'H', value: periodHigh?.toFixed(5), c:'#00ff88' }, + { label:'L', value: periodLow?.toFixed(5), c:'#ff3d5a' }, + { label:'N', value: closes.length + ' bars', c:'var(--text-muted)' }, + ].map(({ label, value, c }) => ( +
+ {label} + {value} +
+ ))} +
+ )} + +
+ {INTERVALS.map(iv => { + const active = interval === iv.val + return ( + + ) + })} +
+
+ + {error && ( +
+ {error} +
+ )} + + {/* Chart container with fade+slide animation */} +
+ + {loading && !data && ( +
+
+ {[32,48,28,52,38,44,30,46,36].map((h, i) => ( +
+ ))} +
+ + Loading {pair} {interval}… + +
+ )} + + {data && ( + + )} + + {!data && !loading && ( +
+ 📈 + Select a pair to load chart +
+ )} +
+ + {/* Legend strip */} + {data && ( +
+ {[ + { color: lineColor, label:`${pair} Close`, dot:true }, + { color: 'rgba(167,139,250,0.9)', label:'EMA 20', dot:false }, + { color: 'rgba(255,215,0,0.8)', label:'EMA 9', dot:false }, + ].map(({ color, label, dot }) => ( +
+ {dot + ?
+ :
+ } + {label} +
+ ))} +
+ + {interval} · {data.dates?.at(-1)?.slice(0, 10)} · Garman-Kohlhagen + +
+ )} +
+ ) +} diff --git a/frontend/src/components/EconomicCalendar.jsx b/frontend/src/components/EconomicCalendar.jsx new file mode 100644 index 0000000..2350e7a --- /dev/null +++ b/frontend/src/components/EconomicCalendar.jsx @@ -0,0 +1,493 @@ +import { useState, useEffect, useCallback, Fragment, useMemo } from 'react' +import { fetchCalendar } from '../api/client' + +/* ─── constants ────────────────────────────────────────── */ + +const FLAGS = { + USD:'🇺🇸', EUR:'🇪🇺', GBP:'🇬🇧', JPY:'🇯🇵', + AUD:'🇦🇺', CAD:'🇨🇦', CHF:'🇨🇭', NZD:'🇳🇿', + CNY:'🇨🇳', CNH:'🇨🇳', +} + +const IMP = { + High: { color:'var(--red)', bar:'var(--red)', label:'HIGH', badge:'tbadge-high' }, + Medium: { color:'var(--amber)', bar:'var(--amber)', label:'MED', badge:'tbadge-medium' }, + Low: { color:'var(--text-muted)', bar:'var(--text-faint)', label:'LOW', badge:'' }, +} + +const ALL_CCY = ['USD','EUR','GBP','JPY','AUD','CAD','CHF','NZD'] +const DAY_ABBR = ['SUN','MON','TUE','WED','THU','FRI','SAT'] + +/* ─── helpers ──────────────────────────────────────────── */ + +function todayStr() { + return new Date().toISOString().slice(0, 10) +} + +function toDateStr(d) { + // local calendar date → "YYYY-MM-DD" + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2,'0') + const day = String(d.getDate()).padStart(2,'0') + return `${y}-${m}-${day}` +} + +function getMondayOf(d) { + const copy = new Date(d) + const dow = copy.getDay() // 0=Sun + const diff = dow === 0 ? -6 : 1 - dow + copy.setDate(copy.getDate() + diff) + copy.setHours(0, 0, 0, 0) + return copy +} + +function fmtUtcTime(dtStr) { + if (!dtStr) return 'All Day' + try { + return new Date(dtStr).toLocaleTimeString('en-GB', + { hour:'2-digit', minute:'2-digit', timeZone:'UTC' }) + } catch { return '--:--' } +} + +function fmtFullDay(dateStr) { + if (!dateStr) return '' + try { + return new Date(dateStr + 'T12:00:00Z').toLocaleDateString('en-US', + { weekday:'long', month:'long', day:'numeric', year:'numeric', timeZone:'UTC' }) + } catch { return dateStr } +} + +function isPast(dtStr) { + if (!dtStr) return false + return new Date(dtStr) < Date.now() +} + +function countdown(dtStr) { + if (!dtStr) return null + const diff = new Date(dtStr) - Date.now() + if (diff <= 0 || diff > 24 * 3600000) return null + const h = Math.floor(diff / 3600000) + const m = Math.floor((diff % 3600000) / 60000) + return h > 0 ? `${h}h ${m}m` : `${m}m` +} + +/* ─── sub-components ───────────────────────────────────── */ + +function ImpactDots({ counts }) { + return ( +
+ {counts.high > 0 && [...Array(Math.min(counts.high, 3))].map((_,i) => + + )} + {counts.medium > 0 && [...Array(Math.min(counts.medium,3))].map((_,i) => + + )} +
+ ) +} + +function EventRow({ ev }) { + const imp = IMP[ev.impact] || IMP.Low + const flag = FLAGS[ev.country] || '🏳️' + const past = isPast(ev.datetime_utc) + const cd = countdown(ev.datetime_utc) + const actual = ev.actual?.trim() + + // beat/miss + let bm = null + if (actual && ev.forecast?.trim()) { + const a = parseFloat(actual), f = parseFloat(ev.forecast) + if (!isNaN(a) && !isNaN(f)) bm = a > f ? 'beat' : a < f ? 'miss' : 'inline' + } + const actualColor = bm === 'beat' ? 'var(--green)' : bm === 'miss' ? 'var(--red)' : 'var(--text-secondary)' + + return ( + e.currentTarget.style.background = 'color-mix(in srgb, var(--cyan) 4%, transparent)'} + onMouseLeave={e => e.currentTarget.style.background = cd ? 'color-mix(in srgb, var(--cyan) 2%, transparent)' : 'transparent'} + > + {/* Impact bar */} + +
+ + + {/* Time */} + +
+ {fmtUtcTime(ev.datetime_utc)} +
+ {cd && ( +
+ in {cd} +
+ )} + + + {/* Currency */} + +
+ {flag} + + {ev.country} + +
+ + + {/* Impact label */} + + {imp.badge + ? {imp.label} + : {imp.label} + } + + + {/* Event name */} + + + {ev.title} + + + + {/* Actual */} + + {actual ? ( + + {bm === 'beat' ? '▲ ' : bm === 'miss' ? '▼ ' : ''}{actual} + + ) : ( + + )} + + + {/* Forecast */} + + + {ev.forecast?.trim() || '—'} + + + + {/* Previous */} + + + {ev.previous?.trim() || '—'} + + + + ) +} + +/* ─── main component ───────────────────────────────────── */ + +export default function EconomicCalendar() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [selDate, setSelDate] = useState(todayStr()) + const [weekStart, setWeekStart] = useState(() => getMondayOf(new Date())) + const [impFilter, setImpFilter] = useState('All') + const [ccyFilter, setCcyFilter] = useState([]) + + /* load calendar data */ + const load = useCallback(async () => { + setLoading(true); setError(null) + try { setData(await fetchCalendar()) } + catch { setError('Could not load Forex Factory data.') } + finally { setLoading(false) } + }, []) + + useEffect(() => { load() }, []) + useEffect(() => { + const id = setInterval(load, 10 * 60 * 1000) // refresh every 10 min + return () => clearInterval(id) + }, []) + + /* derived values */ + const allEvents = data?.events || [] + + // dates that have at least one event + const availDates = useMemo(() => + new Set(allEvents.map(e => e.datetime_utc?.slice(0, 10)).filter(Boolean)), + [allEvents] + ) + + // week days: Mon-Sun for current weekStart + const weekDays = useMemo(() => + [...Array(7)].map((_, i) => { + const d = new Date(weekStart) + d.setDate(weekStart.getDate() + i) + return d + }), + [weekStart] + ) + + // events for selected day + const dayEvents = useMemo(() => + allEvents.filter(ev => { + if (!ev.datetime_utc?.startsWith(selDate)) return false + if (impFilter !== 'All' && ev.impact !== impFilter) return false + if (ccyFilter.length > 0 && !ccyFilter.includes(ev.country)) return false + return true + }), + [allEvents, selDate, impFilter, ccyFilter] + ) + + // impact counts per day (for dot indicators) + const impactCounts = useMemo(() => { + const map = {} + for (const ev of allEvents) { + const d = ev.datetime_utc?.slice(0, 10) + if (!d) continue + if (!map[d]) map[d] = { high:0, medium:0, low:0, total:0 } + const k = ev.impact?.toLowerCase() + if (k === 'high') map[d].high++ + if (k === 'medium') map[d].medium++ + if (k === 'low') map[d].low++ + map[d].total++ + } + return map + }, [allEvents]) + + // day event counts for selected date (unfiltered, for header) + const selDayAll = allEvents.filter(e => e.datetime_utc?.startsWith(selDate)) + const selImpCounts = impactCounts[selDate] || { high:0, medium:0, low:0 } + + /* week navigation */ + function prevWeek() { + const next = new Date(weekStart) + next.setDate(weekStart.getDate() - 7) + setWeekStart(next) + } + function nextWeek() { + const next = new Date(weekStart) + next.setDate(weekStart.getDate() + 7) + setWeekStart(next) + } + + /* ── render ─────────────────────────────────────────── */ + + const today = todayStr() + + return ( +
+ + {/* ── Terminal header + Week navigation ───────── */} +
+ + ECONOMIC CALENDAR + · + Forex Factory · UTC +
+ +
+ + {/* ── Week navigation bar ──────────────────────── */} +
+ + +
+ {weekDays.map(d => { + const ds = toDateStr(d) + const isToday = ds === today + const isSel = ds === selDate + const hasData = availDates.has(ds) + const counts = impactCounts[ds] || { high:0, medium:0, low:0 } + + return ( + + ) + })} +
+ + +
+ + {/* ── Day header ───────────────────────────────── */} +
+
+ + {fmtFullDay(selDate)} + +
+ {selImpCounts.high > 0 && ( + {selImpCounts.high} High + )} + {selImpCounts.medium > 0 && ( + {selImpCounts.medium} Med + )} + {selImpCounts.low > 0 && ( + + {selImpCounts.low} Low + + )} +
+
+ + {/* Filters */} +
+ {['All','High','Medium','Low'].map(v => { + const active = impFilter === v + const accentVar = v === 'High' ? 'var(--red)' : v === 'Medium' ? 'var(--amber)' : 'var(--text-muted)' + return ( + + ) + })} +
+ {ALL_CCY.map(ccy => { + const active = ccyFilter.includes(ccy) + return ( + + ) + })} + {ccyFilter.length > 0 && ( + + )} +
+
+ + {/* ── Event table ──────────────────────────────── */} +
+ {loading && !data ? ( +
+ {[...Array(6)].map((_,i) => ( +
+ ))} +
+ ) : error ? ( +
+
{error}
+ +
+ ) : !availDates.has(selDate) ? ( +
+
📭
+
No data for {fmtFullDay(selDate)}
+
+ Forex Factory publishes the current week's calendar each Sunday. +
Navigate to a date within the available range. +
+
+ {[...availDates].sort().map(d => ( + + ))} +
+
+ ) : dayEvents.length === 0 ? ( +
+ No events match the current filters. +
+ ) : ( + + + + + + + + + + + + + + + ))} + + + + {dayEvents.map((ev, i) => )} + +
+ {['Time (UTC)','Currency','Imp.','Event','Actual','Forecast','Previous'].map((h,i) => ( + = 4 ? 'right' : 'left' }}>{h}
+ )} +
+ + {/* ── Footer ───────────────────────────────────── */} +
+ + Forex Factory · nfs.faireconomy.media · published weekly · all times UTC + + + {data?.count || 0} events + +
+
+ ) +} diff --git a/frontend/src/components/GreeksDashboard.jsx b/frontend/src/components/GreeksDashboard.jsx new file mode 100644 index 0000000..3af4e55 --- /dev/null +++ b/frontend/src/components/GreeksDashboard.jsx @@ -0,0 +1,164 @@ +import { useState, useEffect } from 'react' +import { usePortfolioStore } from '../store/portfolio' +import { computeGreeks, exportCSV } from '../api/client' + +const GREEK_INFO = { + delta: { label:'Δ DELTA', sym:'Δ', color:'var(--cyan)', desc:'Spot price sensitivity', tip:'Change in option value per unit change in spot. Delta 0.8 = option moves 80¢ per $1 of spot.', cls:'cyan' }, + gamma: { label:'Γ GAMMA', sym:'Γ', color:'var(--purple)', desc:'Delta convexity', tip:'Rate of change of Delta w.r.t. spot. High Gamma = Delta shifts rapidly near the strike price.', cls:'purple' }, + vega: { label:'ν VEGA', sym:'ν', color:'var(--green)', desc:'Volatility sensitivity /1%', tip:'P&L impact per 1% move in implied vol. Long options = positive Vega (vol rising helps you).', cls:'green' }, + theta: { label:'Θ THETA', sym:'Θ', color:'var(--red)', desc:'Time decay per year', tip:'Daily P&L erosion due to time passing. Long options pay theta — short options earn it.', cls:'red' }, + rho_d: { label:'ρ RHO_D', sym:'ρ', color:'var(--amber)', desc:'Domestic rate sensitivity', tip:'Sensitivity to the domestic (quote currency) interest rate. Usually small on short expirations.', cls:'gold' }, + phi: { label:'φ PHI', sym:'φ', color:'#fb923c', desc:'Foreign rate sensitivity', tip:'Sensitivity to the foreign (base currency) rate. This greek is unique to FX options.', cls:'orange' }, +} + +export default function GreeksDashboard() { + const { legs, S, sigma, T, r_d, r_f, pair } = usePortfolioStore() + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const payload = { + options: legs.map(({ type, K, T, qty }) => ({ type, K, T, qty })), + S, sigma, T, r_d, r_f, + } + + useEffect(() => { + if (!legs.length) return + setLoading(true); setError(null) + computeGreeks(payload) + .then(setData) + .catch(e => setError(e?.response?.data?.detail || 'Greeks computation failed')) + .finally(() => setLoading(false)) + }, [S, sigma, T, r_d, r_f, JSON.stringify(legs)]) + + const handleExport = async () => { + const blob = await exportCSV(payload) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url; a.download = `greeks_${pair}.csv`; a.click() + URL.revokeObjectURL(url) + } + + if (loading) return ( +
+
+
+ Computing Garman-Kohlhagen Greeks… +
+ {[...Array(3)].map((_,i) => ( +
+ ))} +
+ ) + + if (error) return ( +
+
{error}
+
+ ) + + if (!data) return null + + return ( +
+ {/* Terminal header */} +
+ PORTFOLIO GREEKS + · + {pair} + · + + S={S} · σ={sigma} · r_d={r_d} · r_f={r_f} + +
+ +
+ + {/* Greek cards — left-border Bloomberg style, 3+3 grid */} +
+ {Object.entries(GREEK_INFO).map(([key, info]) => { + const val = data.total[key] + const formatted = val !== undefined ? (val >= 0 ? '+' : '') + val.toFixed(4) : '—' + return ( +
+
+ {info.desc} + + {info.sym} + +
+
{formatted}
+
{info.label}
+
+ ) + })} +
+ + {/* Quick stats */} +
+
+ Daily Theta: + + {data.total.theta !== undefined ? (data.total.theta / 365).toFixed(5) : '—'} /day + +
+
+ Vega per 1% vol: + + {data.total.vega !== undefined ? (data.total.vega / 100).toFixed(5) : '—'} + +
+
+ Net Delta: + = 0 ? 'var(--green)' : 'var(--red)' }}> + {data.total.delta !== undefined ? (data.total.delta >= 0 ? '+' : '') + data.total.delta.toFixed(4) : '—'} + +
+
+ + {/* Per-leg breakdown */} +
+ Per-Leg Breakdown +
+
+ + + + {['Leg','Type','Strike','Delta','Gamma','Vega','Theta','Rho_d','Phi'].map(h => ( + + ))} + + + + {data.legs.map((leg, i) => ( + + + + + + + + + + + + ))} + +
{h}
{leg.label}{leg.type?.toUpperCase()} + {leg.K?.toFixed(5)} + = 0 ? 'var(--green)' : 'var(--red)' }}> + {leg.delta !== undefined ? (leg.delta >= 0 ? '+' : '') + leg.delta : '—'} + {leg.gamma}{leg.vega}{leg.theta}{leg.rho_d ?? '—'}{leg.phi ?? '—'}
+
+
+ ) +} diff --git a/frontend/src/components/IVSurface.jsx b/frontend/src/components/IVSurface.jsx new file mode 100644 index 0000000..571239d --- /dev/null +++ b/frontend/src/components/IVSurface.jsx @@ -0,0 +1,109 @@ +import { useState } from 'react' +import Plot from 'react-plotly.js' +import { fetchIVSurface } from '../api/client' +import { usePortfolioStore } from '../store/portfolio' + +export default function IVSurface() { + const { ticker } = usePortfolioStore() + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [optType, setOptType] = useState('call') + + const load = async () => { + setLoading(true); setError(null) + try { + const result = await fetchIVSurface(ticker) + setData(result) + } catch (e) { + setError(e?.response?.data?.detail || 'Failed to fetch option chain.') + } finally { + setLoading(false) + } + } + + const filtered = data?.data.filter(d => d.type === optType) ?? [] + const expiries = [...new Set(filtered.map(d => d.expiry))].sort() + const strikes = [...new Set(filtered.map(d => d.strike))].sort((a, b) => a - b) + + // Build IV matrix: rows=strikes, cols=expiries + const Z = strikes.map(k => + expiries.map(exp => { + const pt = filtered.find(d => d.strike === k && d.expiry === exp) + return pt ? pt.iv * 100 : null + }) + ) + + const plotData = data ? [{ + type: 'surface', + x: expiries, + y: strikes, + z: Z, + colorscale: 'RdYlGn_r', + colorbar: { title: 'IV (%)', tickfont: { color: '#e2e8f0' } }, + contours: { z: { show: true, usecolormap: true, project: { z: true } } }, + }] : [] + + return ( +
+
+

+ Live IV Surface — {ticker} +

+
+ {['call', 'put'].map(t => ( + + ))} +
+ + {data && ( + + Spot: ${data.spot?.toFixed(2)} + + )} +
+ + {error && ( +
+ {error} +
+ )} + + {data ? ( + + ) : ( +
+ 📡 + + Fetch live option chain data from Yahoo Finance + + + Works with any US equity ticker (SPY, AAPL, TSLA, QQQ...) + +
+ )} +
+ ) +} diff --git a/frontend/src/components/InstitutionalFlow.jsx b/frontend/src/components/InstitutionalFlow.jsx new file mode 100644 index 0000000..95c5f6a --- /dev/null +++ b/frontend/src/components/InstitutionalFlow.jsx @@ -0,0 +1,439 @@ +import { useState, useEffect } from 'react' +import _Plot from 'react-plotly.js' +const Plot = _Plot?.default ?? _Plot +import { usePortfolioStore } from '../store/portfolio' +import { fetchInstitutional } from '../api/client' + +const C = { + am: '#00ff88', // asset managers — real money — green + lm: '#a78bfa', // leveraged money (hedge funds) — purple + dealer: '#ff3d5a', // dealers — red + oi: '#00d4ff', // open interest — cyan + poc: '#ffd700', // POC — gold + vah: 'rgba(0,212,255,0.6)', + val: 'rgba(0,212,255,0.6)', + now: '#00ff88', +} + +const BIAS_BG = { BULLISH:'rgba(0,255,136,0.1)', BEARISH:'rgba(255,61,90,0.1)', NEUTRAL:'rgba(255,215,0,0.1)' } +const BIAS_C = { BULLISH:'#00ff88', BEARISH:'#ff3d5a', NEUTRAL:'#ffd700' } +const BIAS_BD = { BULLISH:'rgba(0,255,136,0.3)', BEARISH:'rgba(255,61,90,0.3)', NEUTRAL:'rgba(255,215,0,0.3)' } + +const Badge = ({ v }) => ( + {v} +) + +const StatCard = ({ label, value, sub, color='var(--cyan)', border }) => ( +
+
{label}
+
{value}
+ {sub &&
{sub}
} +
+) + +const bgLayout = { + paper_bgcolor:'transparent', plot_bgcolor:'rgba(4,10,28,0.7)', + font:{ color:'#475569', family:'JetBrains Mono, monospace', size:9 }, + margin:{ l:56, r:12, t:20, b:40 }, + xaxis:{ gridcolor:'rgba(0,212,255,0.05)', color:'#475569', zeroline:false, tickangle:-30 }, + yaxis:{ gridcolor:'rgba(0,212,255,0.05)', color:'#475569', zeroline:false }, + hovermode:'x unified', + hoverlabel:{ bgcolor:'rgba(4,10,28,0.95)', bordercolor:'rgba(0,212,255,0.3)', + font:{ color:'#e0f2fe', family:'JetBrains Mono', size:10 } }, + legend:{ bgcolor:'transparent', font:{ size:9, color:'#64748b' }, + orientation:'h', x:0, y:1.04 }, +} + +export default function InstitutionalFlow() { + const { pair } = usePortfolioStore() + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [weeks, setWeeks] = useState(26) + + const load = async () => { + setLoading(true); setError(null) + try { setData(await fetchInstitutional(pair, weeks)) } + catch (e) { setError(e?.response?.data?.detail || 'Data unavailable.') } + finally { setLoading(false) } + } + + useEffect(() => { load() }, [pair, weeks]) + + if (loading) return ( +
+
+
+ + Fetching CFTC TFF data + computing volume profile… + +
+ {[...Array(6)].map((_,i) => ( +
+ ))} +
+ ) + + if (error) return ( +
+
{error}
+ +
+ ) + if (!data) return null + + const { cot: c, volume_profile: vp } = data + + // ── Build Plotly traces ───────────────────────────────── + + // 1. Asset Manager net bars + const amColors = (c.am_net||[]).map(v => v >= 0 ? 'rgba(0,255,136,0.75)' : 'rgba(255,61,90,0.55)') + const amBar = { + type:'bar', name:'Asset Mgr Net', + x:c.dates, y:c.am_net, + marker:{ color:amColors, line:{ width:0 } }, + hovertemplate:'%{x}
AM Net: %{y:,}', + } + + // 2. Leveraged money net line + const lmLine = { + type:'scatter', mode:'lines+markers', name:'Hedge Fund Net', + x:c.dates, y:c.lm_net, + line:{ color:C.lm, width:2 }, + marker:{ size:4, color:C.lm }, + hovertemplate:'HF Net: %{y:,}', + } + + // 3. Dealer net line + const dealerLine = { + type:'scatter', mode:'lines', name:'Dealer Net', + x:c.dates, y:c.dealer_net, + line:{ color:C.dealer, width:1.5, dash:'dot' }, + hovertemplate:'Dealer Net: %{y:,}', + } + + // 4. Open interest + const oiTrace = { + type:'scatter', mode:'lines+markers', name:'Open Interest', + x:c.dates, y:c.open_interest, + line:{ color:C.oi, width:2 }, + marker:{ size:4, color:C.oi }, + fill:'tozeroy', fillcolor:'rgba(0,212,255,0.05)', + hovertemplate:'OI: %{y:,.0f}', + } + + // 5. COT Index lines (rolling percentile) + const amIdx = (c.am_net||[]).map((_, i, a) => { + const sl = a.slice(0, i + 1), lo = Math.min(...sl), hi = Math.max(...sl) + return hi > lo ? Math.round((a[i] - lo) / (hi - lo) * 100) : 50 + }) + const lmIdx = (c.lm_net||[]).map((_, i, a) => { + const sl = a.slice(0, i + 1), lo = Math.min(...sl), hi = Math.max(...sl) + return hi > lo ? Math.round((a[i] - lo) / (hi - lo) * 100) : 50 + }) + const amIdxTrace = { + type:'scatter', mode:'lines', name:'AM Index', + x:c.dates, y:amIdx, + line:{ color:C.am, width:2 }, + hovertemplate:'AM Index: %{y}', + } + const lmIdxTrace = { + type:'scatter', mode:'lines', name:'HF Index', + x:c.dates, y:lmIdx, + line:{ color:C.lm, width:1.5, dash:'dot' }, + hovertemplate:'HF Index: %{y}', + } + + // 6. Long/short stacked bars + const amLongBar = { type:'bar', name:'AM Long', x:c.dates, y:c.am_long, + marker:{ color:'rgba(0,255,136,0.65)' }, + hovertemplate:'AM Long: %{y:,}' } + const amShortBar = { type:'bar', name:'AM Short', x:c.dates, y:c.am_short.map(v => -v), + marker:{ color:'rgba(255,61,90,0.55)' }, + hovertemplate:'AM Short: %{y:,}' } + const lmLongBar = { type:'bar', name:'HF Long', x:c.dates, y:c.lm_long, + marker:{ color:'rgba(167,139,250,0.6)' }, + hovertemplate:'HF Long: %{y:,}' } + const lmShortBar = { type:'bar', name:'HF Short', x:c.dates, y:c.lm_short.map(v => -v), + marker:{ color:'rgba(167,139,250,0.35)' }, + hovertemplate:'HF Short: %{y:,}' } + + // 7. Volume Profile + const vpColors = (vp.volumes||[]).map((_, i) => { + const p = (vp.prices||[])[i] + if (!p) return 'rgba(0,212,255,0.22)' + const bktSize = (vp.global_hi - vp.global_lo) / (vp.prices?.length || 40) + if (Math.abs(p - vp.poc) < bktSize * 0.6) return 'rgba(255,215,0,0.9)' + if (p >= vp.val && p <= vp.vah) return 'rgba(0,212,255,0.55)' + return 'rgba(0,212,255,0.22)' + }) + const vpTrace = { + type:'bar', orientation:'h', + x:vp.volumes, y:vp.prices, + name:'Volume', + marker:{ color:vpColors, line:{ width:0 } }, + hovertemplate:'Price: %{y:.5f}
Vol: %{x:,.0f}', + } + + const vpShapes = [ + { type:'line', x0:0, x1:1, xref:'paper', y0:vp.current_price, y1:vp.current_price, + line:{ color:'#00ff88', width:1.5, dash:'dash' } }, + { type:'line', x0:0, x1:1, xref:'paper', y0:vp.poc, y1:vp.poc, + line:{ color:'#ffd700', width:1.5, dash:'solid' } }, + { type:'line', x0:0, x1:1, xref:'paper', y0:vp.vah, y1:vp.vah, + line:{ color:'rgba(0,212,255,0.5)', width:1, dash:'dash' } }, + { type:'line', x0:0, x1:1, xref:'paper', y0:vp.val, y1:vp.val, + line:{ color:'rgba(0,212,255,0.5)', width:1, dash:'dash' } }, + ].filter(s => s.line && s.y0) + + const indexShapes = [ + { type:'rect', x0:0, x1:1, xref:'paper', y0:65, y1:100, + fillcolor:'rgba(0,255,136,0.04)', line:{ width:0 } }, + { type:'rect', x0:0, x1:1, xref:'paper', y0:0, y1:35, + fillcolor:'rgba(255,61,90,0.04)', line:{ width:0 } }, + { type:'line', x0:0, x1:1, xref:'paper', y0:65, y1:65, + line:{ color:'rgba(0,255,136,0.3)', width:1, dash:'dot' } }, + { type:'line', x0:0, x1:1, xref:'paper', y0:35, y1:35, + line:{ color:'rgba(255,61,90,0.3)', width:1, dash:'dot' } }, + ] + + const fmtNum = n => n >= 0 ? `+${n.toLocaleString()}` : n.toLocaleString() + + return ( +
+ + {/* ── Header ──────────────────────────────────────── */} +
+
+ Institutional Flow — {pair} + + + {c.weeks}W · CFTC TFF · {c.latest_date} + +
+
+ {[13, 26, 52].map(w => ( + + ))} + +
+
+ + {/* ── Composite Index gauge ───────────────────────── */} +
+
+
Composite Institutional Index
+
+ + {c.composite_index} + +
+ +
+ 0 = max institutional short · 100 = max institutional long +
+
+
+
+ {/* Progress bar */} +
+
+ BEARNEUTRALBULL +
+
+
+
+
+
+
+ + {/* ── Trader category cards ──────────────────────── */} +
+ = 0 ? C.am : '#ff3d5a'} /> + = 0 ? C.am : '#ff3d5a'} /> + + {vp.poc && ( + + )} +
+ + {/* ── Charts ─────────────────────────────────────── */} +
+ + {/* Left column */} +
+ + {/* Asset Manager + Hedge Fund net positioning */} +
+
+ + Asset Manager & Hedge Fund Net Positioning + + + Bars = Asset Mgr · Line = Hedge Funds + +
+ +
+ + {/* COT Index (rolling percentile) */} +
+
+ + Institutional Positioning Index (0–100 Percentile) + +
+ +
+ + {/* Long/short breakdown stacked */} +
+
+ + Long / Short Breakdown (above = long, below = short) + +
+ +
+ + {/* Open interest */} +
+
+ + Open Interest + +
+ +
+
+ + {/* Right: Volume Profile */} + {vp.prices && ( +
+
+ Volume Profile — 3 Months +
+ + {/* Legend */} +
+ {[ + { color:C.poc, label:`POC ${vp.poc?.toFixed(5)}`, dash:false }, + { color:'rgba(0,212,255,0.7)', label:`VAH ${vp.vah?.toFixed(5)}`, dash:true }, + { color:'rgba(0,212,255,0.7)', label:`VAL ${vp.val?.toFixed(5)}`, dash:true }, + { color:C.now, label:`Now ${vp.current_price?.toFixed(5)}`, dash:true }, + ].map(({ color, label, dash }) => ( +
+
+ {label} +
+ ))} +
+ Value Area (70% vol)
+ POC — max volume +
+
+ + +
+ )} +
+ + {/* ── Attribution ─────────────────────────────────── */} +
+ {(data.data_sources||[]).map(src => ( +
+ 📡 {src} +
+ ))} +
+
+ ) +} diff --git a/frontend/src/components/LiveFeeds.jsx b/frontend/src/components/LiveFeeds.jsx new file mode 100644 index 0000000..23ebc28 --- /dev/null +++ b/frontend/src/components/LiveFeeds.jsx @@ -0,0 +1,244 @@ +import { useState, useEffect, useCallback } from 'react' + +const CITIES = [ + { name:'New York', sub:'NYSE · Wall St', lat:40.71, lon:-74.00 }, + { name:'London', sub:'LSE · Canary Wharf', lat:51.51, lon:-0.09 }, + { name:'Tokyo', sub:'TSE · Marunouchi', lat:35.68, lon:139.76 }, + { name:'Frankfurt', sub:'XETRA · ECB', lat:50.11, lon:8.68 }, + { name:'Singapore', sub:'SGX · Marina Bay', lat:1.28, lon:103.85 }, + { name:'Dubai', sub:'DFM · DIFC', lat:25.21, lon:55.28 }, +] + +function windyUrl(lat, lon) { + return `https://embed.windy.com/embed2.html?type=map&location=coordinates&zoom=12&overlay=webcams&product=ecmwf&level=surface&lat=${lat}&lon=${lon}&menu=&message=&marker=&pressure=&metricWind=kt&metricTemp=%C2%B0C&radarRange=-1` +} + +function CryptoCard({ coin }) { + const up = coin.price_change_percentage_24h >= 0 + const color = up ? 'var(--green)' : 'var(--red)' + const fmt = (n, d=2) => n == null ? '—' : n.toLocaleString('en-US', { maximumFractionDigits: d }) + + return ( +
e.currentTarget.style.background='var(--surface-2)'} + onMouseLeave={e => e.currentTarget.style.background='var(--surface)'} + > +
+
+ {coin.symbol} +
+
+ {coin.symbol} +
+
{coin.name}
+
+
+ + {up ? '▲' : '▼'} {Math.abs(coin.price_change_percentage_24h).toFixed(2)}% + +
+
+ ${fmt(coin.current_price)} +
+
+ MCap ${coin.market_cap > 1e9 ? (coin.market_cap/1e9).toFixed(1)+'B' : (coin.market_cap/1e6).toFixed(0)+'M'} + Vol ${coin.total_volume > 1e9 ? (coin.total_volume/1e9).toFixed(1)+'B' : (coin.total_volume/1e6).toFixed(0)+'M'} +
+
+ ) +} + +export default function LiveFeeds() { + const [crypto, setCrypto] = useState([]) + const [cryptoErr, setCryptoErr] = useState(null) + const [loadCrypto, setLoadCrypto] = useState(false) + const [activeCity, setActiveCity] = useState(0) + const [lastCrypto, setLastCrypto] = useState(null) + + const fetchCrypto = useCallback(async () => { + setLoadCrypto(true); setCryptoErr(null) + try { + const r = await fetch( + 'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=12&page=1&sparkline=false&price_change_percentage=24h' + ) + if (!r.ok) throw new Error(`HTTP ${r.status}`) + const d = await r.json() + setCrypto(d) + setLastCrypto(new Date().toLocaleTimeString()) + } catch (e) { + setCryptoErr('CoinGecko rate limited — retrying in 60s') + } finally { setLoadCrypto(false) } + }, []) + + useEffect(() => { + fetchCrypto() + const iv = setInterval(fetchCrypto, 60000) + return () => clearInterval(iv) + }, [fetchCrypto]) + + return ( +
+ + {/* ── Crypto section ───────────────── */} +
+ + CRYPTO MARKETS + · + CoinGecko · top 12 by market cap + {lastCrypto && <> + · + {lastCrypto} + } +
+ {loadCrypto && ⟳ loading…} + +
+ +
+ {cryptoErr ? ( +
+ {cryptoErr} + +
+ ) : crypto.length === 0 ? ( +
+ {[...Array(12)].map((_,i) => ( +
+ ))} +
+ ) : ( +
+ {crypto.map(c => )} +
+ )} +
+ + {/* ── Live Webcams section ──────────── */} +
+ + LIVE GLOBAL CAMERAS + · + + Windy.com · click camera pins on map for live feeds + +
+
+ + {/* City tabs */} +
+ {CITIES.map((c, i) => { + const active = activeCity === i + return ( + + ) + })} +
+ + {/* Active city info bar */} +
+
+ + {CITIES[activeCity].name} + + + {CITIES[activeCity].sub} + +
+ + {CITIES[activeCity].lat.toFixed(2)}°N, {CITIES[activeCity].lon.toFixed(2)}°E + +
+ + Click camera pins on the map to open live feeds + +
+ + {/* Windy webcam embed */} +
+