From 9df4444cc5f49de68e817bffed39f7e411609e45 Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 28 Aug 2026 19:49:10 -0500 Subject: [PATCH] Initial commit: NomadFlow full-stack digital nomad platform with Next.js, FastAPI, and PocketBase. Co-authored-by: Cursor --- .gitignore | 28 + README.md | 205 + backend/.env.example | 6 + backend/Dockerfile | 7 + backend/app/__init__.py | 0 backend/app/config.py | 20 + backend/app/data/__init__.py | 0 backend/app/data/mock_data.py | 229 + backend/app/main.py | 31 + backend/app/routers/__init__.py | 0 backend/app/routers/api.py | 215 + backend/app/routers/auth.py | 103 + backend/app/schemas.py | 164 + backend/app/services/__init__.py | 0 backend/app/services/auth.py | 83 + backend/app/services/pocketbase.py | 183 + backend/railway.toml | 8 + backend/requirements.txt | 7 + backend/seed_pocketbase.py | 142 + css/style.css | 1912 +++++ docker-compose.yml | 40 + frontend/.env.local.example | 1 + frontend/.gitignore | 41 + frontend/AGENTS.md | 9 + frontend/CLAUDE.md | 1 + frontend/Dockerfile | 26 + frontend/README.md | 36 + frontend/eslint.config.mjs | 18 + frontend/next.config.ts | 15 + frontend/package-lock.json | 6234 +++++++++++++++++ frontend/package.json | 24 + frontend/public/file.svg | 1 + frontend/public/globe.svg | 1 + frontend/public/next.svg | 1 + frontend/public/vercel.svg | 1 + frontend/public/window.svg | 1 + frontend/src/app/blog/[slug]/page.tsx | 56 + frontend/src/app/destinations/[slug]/page.tsx | 56 + frontend/src/app/favicon.ico | Bin 0 -> 25931 bytes frontend/src/app/globals.css | 4479 ++++++++++++ frontend/src/app/layout.tsx | 33 + frontend/src/app/loading.tsx | 8 + frontend/src/app/login/page.tsx | 82 + frontend/src/app/not-found.tsx | 15 + frontend/src/app/page.tsx | 60 + frontend/src/app/profile/page.tsx | 149 + frontend/src/app/robots.ts | 8 + frontend/src/app/sitemap.ts | 27 + frontend/src/components/BackToTop.tsx | 21 + .../src/components/BlogReadingProgress.tsx | 31 + frontend/src/components/BlogSection.tsx | 33 + frontend/src/components/Calculator.tsx | 100 + frontend/src/components/Charts.tsx | 109 + frontend/src/components/Community.tsx | 59 + frontend/src/components/CompareModal.tsx | 127 + frontend/src/components/CurrencyConverter.tsx | 136 + .../components/DestinationDetailClient.tsx | 140 + .../src/components/DestinationMatcher.tsx | 217 + frontend/src/components/Destinations.tsx | 122 + frontend/src/components/FAQSection.tsx | 32 + frontend/src/components/FavoriteButton.tsx | 24 + frontend/src/components/Footer.tsx | 45 + frontend/src/components/GlobalSearch.tsx | 129 + frontend/src/components/Hero.tsx | 144 + frontend/src/components/HomeClient.tsx | 132 + frontend/src/components/KeyboardShortcuts.tsx | 65 + frontend/src/components/Lifestyle.tsx | 81 + frontend/src/components/Loader.tsx | 32 + frontend/src/components/Navbar.tsx | 103 + frontend/src/components/NomadScore.tsx | 142 + frontend/src/components/ParticleCanvas.tsx | 78 + frontend/src/components/ScrollProgress.tsx | 24 + frontend/src/components/SectionNav.tsx | 57 + frontend/src/components/SiteShell.tsx | 18 + frontend/src/components/TickerBar.tsx | 12 + frontend/src/components/TimezoneBoard.tsx | 176 + frontend/src/components/Tools.tsx | 29 + frontend/src/components/TripPlanner.tsx | 156 + frontend/src/components/VisaSection.tsx | 81 + frontend/src/components/WorldMap.tsx | 84 + frontend/src/lib/api.ts | 93 + frontend/src/lib/auth.tsx | 105 + frontend/src/lib/toast.tsx | 52 + frontend/src/lib/types.ts | 133 + frontend/tsconfig.json | 34 + frontend/vercel.json | 7 + index.html | 908 +++ js/main.js | 776 ++ package.json | 12 + pocketbase/pb_data/.gitkeep | 0 scripts/dev.ps1 | 32 + scripts/dev.sh | 39 + 92 files changed, 19696 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 backend/.env.example create mode 100644 backend/Dockerfile create mode 100644 backend/app/__init__.py create mode 100644 backend/app/config.py create mode 100644 backend/app/data/__init__.py create mode 100644 backend/app/data/mock_data.py create mode 100644 backend/app/main.py create mode 100644 backend/app/routers/__init__.py create mode 100644 backend/app/routers/api.py create mode 100644 backend/app/routers/auth.py create mode 100644 backend/app/schemas.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/auth.py create mode 100644 backend/app/services/pocketbase.py create mode 100644 backend/railway.toml create mode 100644 backend/requirements.txt create mode 100644 backend/seed_pocketbase.py create mode 100644 css/style.css create mode 100644 docker-compose.yml create mode 100644 frontend/.env.local.example create mode 100644 frontend/.gitignore create mode 100644 frontend/AGENTS.md create mode 100644 frontend/CLAUDE.md create mode 100644 frontend/Dockerfile create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.mjs create mode 100644 frontend/next.config.ts create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/file.svg create mode 100644 frontend/public/globe.svg create mode 100644 frontend/public/next.svg create mode 100644 frontend/public/vercel.svg create mode 100644 frontend/public/window.svg create mode 100644 frontend/src/app/blog/[slug]/page.tsx create mode 100644 frontend/src/app/destinations/[slug]/page.tsx create mode 100644 frontend/src/app/favicon.ico create mode 100644 frontend/src/app/globals.css create mode 100644 frontend/src/app/layout.tsx create mode 100644 frontend/src/app/loading.tsx create mode 100644 frontend/src/app/login/page.tsx create mode 100644 frontend/src/app/not-found.tsx create mode 100644 frontend/src/app/page.tsx create mode 100644 frontend/src/app/profile/page.tsx create mode 100644 frontend/src/app/robots.ts create mode 100644 frontend/src/app/sitemap.ts create mode 100644 frontend/src/components/BackToTop.tsx create mode 100644 frontend/src/components/BlogReadingProgress.tsx create mode 100644 frontend/src/components/BlogSection.tsx create mode 100644 frontend/src/components/Calculator.tsx create mode 100644 frontend/src/components/Charts.tsx create mode 100644 frontend/src/components/Community.tsx create mode 100644 frontend/src/components/CompareModal.tsx create mode 100644 frontend/src/components/CurrencyConverter.tsx create mode 100644 frontend/src/components/DestinationDetailClient.tsx create mode 100644 frontend/src/components/DestinationMatcher.tsx create mode 100644 frontend/src/components/Destinations.tsx create mode 100644 frontend/src/components/FAQSection.tsx create mode 100644 frontend/src/components/FavoriteButton.tsx create mode 100644 frontend/src/components/Footer.tsx create mode 100644 frontend/src/components/GlobalSearch.tsx create mode 100644 frontend/src/components/Hero.tsx create mode 100644 frontend/src/components/HomeClient.tsx create mode 100644 frontend/src/components/KeyboardShortcuts.tsx create mode 100644 frontend/src/components/Lifestyle.tsx create mode 100644 frontend/src/components/Loader.tsx create mode 100644 frontend/src/components/Navbar.tsx create mode 100644 frontend/src/components/NomadScore.tsx create mode 100644 frontend/src/components/ParticleCanvas.tsx create mode 100644 frontend/src/components/ScrollProgress.tsx create mode 100644 frontend/src/components/SectionNav.tsx create mode 100644 frontend/src/components/SiteShell.tsx create mode 100644 frontend/src/components/TickerBar.tsx create mode 100644 frontend/src/components/TimezoneBoard.tsx create mode 100644 frontend/src/components/Tools.tsx create mode 100644 frontend/src/components/TripPlanner.tsx create mode 100644 frontend/src/components/VisaSection.tsx create mode 100644 frontend/src/components/WorldMap.tsx create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/lib/auth.tsx create mode 100644 frontend/src/lib/toast.tsx create mode 100644 frontend/src/lib/types.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vercel.json create mode 100644 index.html create mode 100644 js/main.js create mode 100644 package.json create mode 100644 pocketbase/pb_data/.gitkeep create mode 100644 scripts/dev.ps1 create mode 100644 scripts/dev.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed93383 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Dependencies +node_modules/ +frontend/node_modules/ +backend/__pycache__/ +backend/**/__pycache__/ +backend/.env +frontend/.env.local + +# PocketBase +pocketbase/pb_data/* +!pocketbase/pb_data/.gitkeep + +# Next.js +frontend/.next/ +frontend/out/ + +# Python +*.pyc +.venv/ +venv/ + +# OS +.DS_Store +Thumbs.db + +# IDE +.idea/ +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c813334 --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +# NomadFlow · 数字游民旅居平台 + +全栈数字游民旅居指南,采用 **Next.js + FastAPI + PocketBase** 架构。 +品牌:**NomadFlow** — 用一行代码环游世界 🌏 + +## 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 前端 | Next.js 16 + React 19 | SSR/ISR、组件化 UI、Turbopack | +| 后端 | FastAPI | REST API、业务逻辑 | +| 数据库 | PocketBase | 数据持久化、管理后台 | +| 图表 | Chart.js (CDN) | 数据可视化 | +| 部署 | Vercel + Railway + Docker | 前后端分离部署 | + +## 项目结构 + +``` +nomadweb/ +├── frontend/ # Next.js 前端应用 +│ ├── src/app/ # 页面路由 (App Router) +│ ├── src/components/ # UI 组件 (30+) +│ └── src/lib/ # API 客户端、Auth、Toast +├── backend/ # FastAPI 后端 +│ ├── app/routers/ # API 路由 +│ ├── app/services/ # PocketBase、Auth 服务 +│ └── app/data/ # Mock 降级数据 +├── pocketbase/ # PocketBase 数据目录 +├── scripts/ # 开发启动脚本 (dev.ps1 / dev.sh) +├── docker-compose.yml # 一键启动全部服务 +├── index.html # 原始静态版(保留) +└── README.md +``` + +## 快速开始 + +### 方式一:本地开发(推荐) + +**1. 启动 PocketBase(可选)** + +```bash +# 下载: https://pocketbase.io/docs/ +# 或使用 Docker: +docker run -d -p 8090:8090 -v ./pocketbase/pb_data:/pb_data ghcr.io/muchobien/pocketbase:latest +``` + +访问 http://localhost:8090/_/ 创建管理员(可选,不启动则自动使用 Mock 数据)。 + +**2. 启动 FastAPI 后端** + +```bash +cd backend +pip install -r requirements.txt +cp .env.example .env # 按需修改 +uvicorn app.main:app --reload --port 8000 +``` + +API 文档: http://localhost:8000/docs + +**3. 启动 Next.js 前端** + +```bash +cd frontend +cp .env.local.example .env.local +npm install --legacy-peer-deps +npm run dev +``` + +访问 http://localhost:3000 + +### 方式二:一键脚本(Windows) + +```powershell +.\scripts\dev.ps1 +``` + +### 方式三:Docker Compose + +```bash +docker compose up -d +``` + +| 服务 | 地址 | +|------|------| +| 前端 | http://localhost:3000 | +| API | http://localhost:8000/docs | +| PocketBase | http://localhost:8090/_/ | + +## 功能特性 + +### 首页核心板块 + +| 板块 | 说明 | +|------|------| +| 🗺️ 世界地图 | 交互式 SVG 热力图,点击查看城市详情 | +| 🌍 目的地 | 筛选/搜索/排序,收藏,最多 4 城对比 | +| 🎯 智能匹配 | 4 步问卷,推荐最适合的旅居城市 | +| 🕐 时区看板 | 实时时钟 + 与国内团队工作时间重叠分析 | +| 🗓️ 行程规划 | 多城市规划,预算汇总,分享/导入链接 | +| 🧮 费用计算器 | 按目的地、住宿档次、月数估算预算 | +| 💱 多币种换算 | CNY/USD/EUR/THB 等 8 种货币互转 | +| 📊 就绪度测评 | 5 题评估你的数字游民准备程度 | +| 💻 生活方式 | 交互式「游民一天」时间轴 | +| 📋 签证指南 | 难度筛选 + 进度条可视化 | +| 📝 博客 | 文章列表 + 阅读进度条详情页 | +| ❓ FAQ | 手风琴问答 | + +### 交互与体验 + +- ✨ Canvas 粒子背景 + 品牌加载动画 +- 🌓 深色/浅色主题切换(localStorage 持久化) +- 🔍 全局搜索 `Ctrl+K`(目的地/博客/签证/FAQ) +- 🧭 右侧浮动章节导航 +- 📊 顶部滚动进度条 +- ⚖️ 可视化城市对比(评分圆环 + 条形图) +- 🔔 全局 Toast 通知 +- ⌨️ 快捷键:`M` 智能匹配、`?` 帮助面板 +- 📱 响应式布局,移动端适配 + +### 用户系统 + +- 🔐 登录 / 注册 / 一键演示账号 +- ❤️ 收藏目的地(同步 API) +- 👤 用户中心:统计、收藏、行程展示 +- 演示账号:`demo@nomadflow.io` / `demo123` + +### 后端 API + +| 端点 | 方法 | 说明 | +|------|------|------| +| `/api/v1/destinations` | GET | 目的地列表(region/search/sort) | +| `/api/v1/destinations/{slug}` | GET | 目的地详情 | +| `/api/v1/destinations/compare` | POST | 城市对比(2–4 城) | +| `/api/v1/calculator` | POST | 费用计算 | +| `/api/v1/search` | GET | 全局搜索 | +| `/api/v1/blog` | GET | 博客列表 | +| `/api/v1/blog/{slug}` | GET | 博客详情 | +| `/api/v1/auth/register` | POST | 注册 | +| `/api/v1/auth/login` | POST | 登录 | +| `/api/v1/auth/demo` | GET | 演示登录 | +| `/api/v1/auth/favorites` | GET/POST | 收藏管理 | +| `/api/v1/charts/*` | GET | 图表数据 | +| `/api/v1/subscribe` | POST | 邮件订阅 | + +> PocketBase 不可用时,API 自动降级到 `backend/app/data/mock_data.py`。 + +## 页面路由 + +| 路径 | 说明 | +|------|------| +| `/` | 首页(全部板块) | +| `/login` | 登录 / 注册 | +| `/profile` | 用户中心 | +| `/destinations/[slug]` | 目的地详情(城市画像、加入行程) | +| `/blog/[slug]` | 博客详情(阅读进度) | + +## 环境变量 + +### Backend (`backend/.env`) + +```env +POCKETBASE_URL=http://127.0.0.1:8090 +POCKETBASE_ADMIN_EMAIL=admin@nomadflow.io +POCKETBASE_ADMIN_PASSWORD=admin123456 +CORS_ORIGINS=http://localhost:3000 +``` + +### Frontend (`frontend/.env.local`) + +```env +NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1 +``` + +## 部署 + +### 前端 → Vercel + +1. 导入 `frontend/` 目录 +2. 环境变量:`NEXT_PUBLIC_API_URL=https://your-api.example.com/api/v1` +3. 自动构建部署 + +### 后端 → Railway + +1. 连接 `backend/` 目录 +2. 配置 `POCKETBASE_URL`、`CORS_ORIGINS` +3. 读取 `railway.toml` 自动部署 + +### 全栈 → Docker Compose + +```bash +docker compose up -d +``` + +## 快捷键 + +| 按键 | 功能 | +|------|------| +| `Ctrl+K` | 全局搜索 | +| `M` | 智能目的地匹配 | +| `?` | 快捷键帮助 | +| `Esc` | 关闭弹窗 | + +## License + +MIT diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..d6d0a0f --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,6 @@ +POCKETBASE_URL=http://127.0.0.1:8090 +POCKETBASE_ADMIN_EMAIL=admin@nomadflow.io +POCKETBASE_ADMIN_PASSWORD=admin123456 +API_HOST=0.0.0.0 +API_PORT=8000 +CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..0aa3ffe --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.12-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +EXPOSE 8000 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..0b32bad --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,20 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + pocketbase_url: str = "http://127.0.0.1:8090" + pocketbase_admin_email: str = "admin@nomadflow.io" + pocketbase_admin_password: str = "admin123456" + api_host: str = "0.0.0.0" + api_port: int = 8000 + cors_origins: str = "http://localhost:3000,http://127.0.0.1:3000" + + @property + def cors_origin_list(self) -> list[str]: + return [o.strip() for o in self.cors_origins.split(",") if o.strip()] + + class Config: + env_file = ".env" + + +settings = Settings() diff --git a/backend/app/data/__init__.py b/backend/app/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/data/mock_data.py b/backend/app/data/mock_data.py new file mode 100644 index 0000000..318fbb4 --- /dev/null +++ b/backend/app/data/mock_data.py @@ -0,0 +1,229 @@ +"""Fallback data when PocketBase is unavailable.""" + +DESTINATIONS = [ + { + "id": "1", "slug": "bali", "name": "巴厘岛", "country": "印尼", "emoji": "🏝️", + "tag": "东南亚 · 热带天堂", "description": "乌布的数字游民社区闻名全球,稻田间的 Co-working Space 和瑜伽文化让这里成为游民圣地。", + "region": "sea", "cost": 4500, "speed": 85, "temperature": 28, "rating": 9.2, "hue": 170, + "nomads_count": "12,000+", "highlights": ["🏄 冲浪与海滩生活", "🧘 瑜伽冥想文化", "💰 东南亚性价比之王", "🌴 热带气候全年温暖"], + "map_x": 720, "map_y": 310, + }, + { + "id": "2", "slug": "lisbon", "name": "里斯本", "country": "葡萄牙", "emoji": "🌊", + "tag": "欧洲 · 海滨明珠", "description": "D7 签证友好,阳光海岸与悠久历史的完美融合,欧洲数字游民的首选基地。", + "region": "europe", "cost": 9000, "speed": 120, "temperature": 22, "rating": 9.5, "hue": 220, + "nomads_count": "8,500+", "highlights": ["📋 D7 签证门槛低", "☀️ 300天阳光", "🎵 Fado 音乐文化", "🚋 复古有轨电车"], + "map_x": 430, "map_y": 195, + }, + { + "id": "3", "slug": "chiangmai", "name": "清迈", "country": "泰国", "emoji": "🏔️", + "tag": "东南亚 · 文化古城", "description": "数字游民大本营,咖啡文化与夜市生活的天堂,全球性价比最高的游民城市。", + "region": "sea", "cost": 3800, "speed": 95, "temperature": 30, "rating": 9.4, "hue": 45, + "nomads_count": "15,000+", "highlights": ["☕ 咖啡馆文化浓厚", "🏮 夜市与寺庙", "💰 月生活费最低", "🤝 游民社区最活跃"], + "map_x": 700, "map_y": 240, + }, + { + "id": "4", "slug": "mexico", "name": "墨西哥城", "country": "墨西哥", "emoji": "🌃", + "tag": "拉美 · 活力之都", "description": "艺术、美食与科技交织,时区便利对接北美市场,拉美最具活力的游民城市。", + "region": "latam", "cost": 6500, "speed": 75, "temperature": 18, "rating": 8.8, "hue": 300, + "nomads_count": "5,200+", "highlights": ["🎨 街头艺术天堂", "🌮 世界美食之都", "🕐 北美时区友好", "💃 丰富夜生活"], + "map_x": 220, "map_y": 240, + }, + { + "id": "5", "slug": "barcelona", "name": "巴塞罗那", "country": "西班牙", "emoji": "🏖️", + "tag": "欧洲 · 地中海", "description": "高迪建筑与创业生态并存,Nomad Visa 政策领先,地中海生活的理想之选。", + "region": "europe", "cost": 10500, "speed": 150, "temperature": 20, "rating": 9.1, "hue": 130, + "nomads_count": "6,800+", "highlights": ["🏛️ 高迪建筑奇迹", "🏖️ 地中海海滩", "📋 Nomad Visa 便利", "🍷 美食与夜生活"], + "map_x": 460, "map_y": 200, + }, + { + "id": "6", "slug": "tokyo", "name": "东京", "country": "日本", "emoji": "🗼", + "tag": "亚洲 · 现代都市", "description": "极致效率与安全,适合追求高品质生活的远程工作者,亚洲科技之都。", + "region": "asia", "cost": 12000, "speed": 200, "temperature": 15, "rating": 8.6, "hue": 10, + "nomads_count": "4,100+", "highlights": ["🚄 极致公共交通", "🛡️ 全球最安全城市", "📶 网速亚洲第一", "🍣 美食文化巅峰"], + "map_x": 820, "map_y": 210, + }, +] + +VISAS = [ + {"id": "1", "country": "葡萄牙", "flag": "🇵🇹", "name": "葡萄牙 D7 签证", "badge": "⭐ 推荐", "badge_type": "easy", + "duration": "2年,可续签", "income_req": "€760/月", "approval_time": "3-6 个月", "extra": "🏥 可享欧盟医疗", "difficulty": 35, "difficulty_label": "简单"}, + {"id": "2", "country": "西班牙", "flag": "🇪🇸", "name": "西班牙 Nomad Visa", "badge": "🔥 热门", "badge_type": "hot", + "duration": "1年,可续3年", "income_req": "€2,160/月", "approval_time": "1-3 个月", "extra": "🌍 可申根区旅行", "difficulty": 45, "difficulty_label": "中等"}, + {"id": "3", "country": "印尼", "flag": "🇮🇩", "name": "印尼 B211A 签证", "badge": "💰 低成本", "badge_type": "budget", + "duration": "60天,可延期", "income_req": "约 ¥2,000", "approval_time": "5-10 天", "extra": "🏝️ 适合巴厘岛旅居", "difficulty": 25, "difficulty_label": "简单"}, + {"id": "4", "country": "泰国", "flag": "🇹🇭", "name": "泰国 LTR 签证", "badge": "🆕 新政策", "badge_type": "new", + "duration": "10年", "income_req": "$80,000/年", "approval_time": "1-2 个月", "extra": "✈️ 多次入境", "difficulty": 60, "difficulty_label": "中等"}, + {"id": "5", "country": "墨西哥", "flag": "🇲🇽", "name": "墨西哥 Temporary Resident", "badge": "💰 低成本", "badge_type": "budget", + "duration": "1-4年", "income_req": "$2,500/月", "approval_time": "2-4 周", "extra": "🌮 北美时区友好", "difficulty": 30, "difficulty_label": "简单"}, + {"id": "6", "country": "爱沙尼亚", "flag": "🇪🇪", "name": "爱沙尼亚 DNV", "badge": "🚀 先锋", "badge_type": "pioneer", + "duration": "1年", "income_req": "€3,504/月", "approval_time": "2-4 周", "extra": "💻 全球首个数字游民签证", "difficulty": 40, "difficulty_label": "中等"}, +] + +FAQS = [ + {"id": "1", "question": "💰 做数字游民需要多少启动资金?", "order": 1, + "answer": "建议准备 3-6 个月的生活费作为缓冲。以东南亚为例,¥15,000-30,000 即可开始。包括机票、首月住宿、签证费用和应急资金。欧洲目的地建议准备 ¥50,000 以上。"}, + {"id": "2", "question": "📶 如何确保远程工作的网络稳定?", "order": 2, + "answer": "选择网络评分高的城市,入住前用 Speedtest 测试。备用方案:本地 SIM 卡热点、随身 WiFi 设备、附近 Co-working Space。推荐携带 USB 网卡和 VPN 作为双保险。"}, + {"id": "3", "question": "🏥 旅居期间的保险怎么办?", "order": 3, + "answer": "推荐 SafetyWing 或 World Nomads 等国际医疗保险,月费约 $40-80,覆盖全球(部分国家除外)。长期旅居者可考虑目的地国家的本地保险,费用更低、报销更方便。"}, + {"id": "4", "question": "🧾 税务问题如何处理?", "order": 4, + "answer": "税务居民身份取决于居住天数(通常 183 天规则)。建议咨询专业税务顾问,了解双重征税协定。很多游民选择税务友好的国家(如葡萄牙、格鲁吉亚)作为基地。"}, + {"id": "5", "question": "👨‍👩‍👧 可以带娃一起做数字游民吗?", "order": 5, + "answer": "完全可以!巴厘岛、清迈、里斯本都有成熟的数字游民家庭社区。关键是选择教育资源丰富、医疗条件好的目的地,以及保持稳定的工作节奏,给孩子规律的生活。"}, + {"id": "6", "question": "🤝 如何快速融入当地游民社区?", "order": 6, + "answer": "加入 Nomad List、Facebook 群组和本地 Meetup 活动。入住游民友好的 Co-living 空间,参加每周的 Coworking 社交日。大部分游民社区非常开放,主动打招呼就能结识朋友。"}, +] + +TESTIMONIALS = [ + {"id": "1", "avatar": "👩‍💻", "content": "在清迈住了 8 个月,月花费不到 4000 元,但生活质量比国内一线城市高太多了。每天早上骑摩托去咖啡馆,这种感觉无法形容。", "author": "小林", "role": "前端开发 · 清迈 🇹🇭", "rating": 5}, + {"id": "2", "avatar": "👨‍🎨", "content": "里斯本的 D7 签证让我在欧洲有了基地。白天在 Alfama 区的共享办公空间工作,周末去 Sintra 徒步,完美平衡。", "author": "Marco", "role": "UI 设计师 · 里斯本 🇵🇹", "rating": 5}, + {"id": "3", "avatar": "🧑‍💼", "content": "带着家人做数字游民听起来疯狂,但在巴厘岛乌布,孩子们上国际学校,我和妻子远程工作,这是我们做过最正确的决定。", "author": "张家", "role": "产品经理 · 巴厘岛 🇮🇩", "rating": 5}, +] + +TOOLS = [ + {"id": "1", "emoji": "💼", "name": "远程协作", "description": "Slack · Notion · Figma · Zoom", "tags": ["团队", "设计", "沟通"], "category": "work"}, + {"id": "2", "emoji": "✈️", "name": "旅行规划", "description": "Skyscanner · Nomad List · SafetyWing", "tags": ["机票", "签证", "保险"], "category": "travel"}, + {"id": "3", "emoji": "💳", "name": "财务管理", "description": "Wise · Revolut · Xero · 多币种账户", "tags": ["汇款", "记账", "税务"], "category": "finance"}, + {"id": "4", "emoji": "🤝", "name": "社群网络", "description": "Nomad List · Remote Year · 本地 Meetup", "tags": ["社交", "活动", "合租"], "category": "connect"}, + {"id": "5", "emoji": "🏥", "name": "健康保障", "description": "SafetyWing · World Nomads · 运动 App", "tags": ["保险", "健身", "心理"], "category": "health"}, + {"id": "6", "emoji": "📚", "name": "持续学习", "description": "Coursera · Duolingo · 当地语言班", "tags": ["技能", "语言", "文化"], "category": "learn"}, +] + +BLOG_POSTS = [ + {"id": "1", "slug": "chiangmai-guide-2026", "title": "清迈数字游民完全指南 2026", "excerpt": "从签证到住宿,从咖啡馆到 Co-working,一篇搞定清迈旅居。", "emoji": "🏔️", "author": "NomadFlow", "published_at": "2026-01-15", "read_time": 8, "tags": ["清迈", "指南", "东南亚"]}, + {"id": "2", "slug": "portugal-d7-visa", "title": "葡萄牙 D7 签证申请全攻略", "excerpt": "手把手教你申请欧洲最受欢迎的远程工作签证。", "emoji": "🇵🇹", "author": "NomadFlow", "published_at": "2026-02-03", "read_time": 12, "tags": ["签证", "葡萄牙", "欧洲"]}, + {"id": "3", "slug": "nomad-tax-basics", "title": "数字游民税务入门:你需要知道的 5 件事", "excerpt": "183 天规则、双重征税、税务居民身份——一文理清。", "emoji": "🧾", "author": "NomadFlow", "published_at": "2026-02-20", "read_time": 10, "tags": ["税务", "法律", "指南"]}, +] + +BLOG_CONTENT: dict[str, str] = { + "chiangmai-guide-2026": """## 为什么选择清迈? + +清迈是公认的全球数字游民之都。低廉的生活成本、完善的 Co-working 生态、友善的本地人和丰富的文化活动,让它成为新手游民的最佳起点。 + +## 签证方案 + +- **旅游签**:落地签 15 天,或提前办旅游签 60 天 +- **DTV 签证**:2024 年推出的 Destination Thailand Visa,适合远程工作者 +- **学生签/精英签**:长期旅居的进阶选择 + +## 住宿推荐 + +| 区域 | 月租 | 特点 | +|------|------|------| +| Nimman | ¥2,500-4,000 | 咖啡馆、餐厅、年轻人多 | +| Old City | ¥1,500-3,000 | 文化氛围浓,步行可达寺庙 | +| Hang Dong | ¥2,000-3,500 | 安静,适合深度工作 | + +## 最佳 Co-working + +1. **Punspace** — Nimman 区经典,日票 ¥60 +2. **CAMP** — Maya 商场顶楼,免费(消费即可) +3. **Hub53** — 安静专业,月票 ¥800 + +## 月均预算 + +- 住宿:¥2,500 +- 餐饮:¥1,200 +- 交通:¥300(租摩托) +- Co-working:¥400 +- 其他:¥400 +- **合计:约 ¥3,800-4,500** + +## 实用 Tips + +☕ 推荐咖啡馆:Ristr8to(世界冠军)、Graph One Nimman +🏍️ 租摩托月费约 ¥400,注意戴头盔 +📱 推荐 AIS 或 TrueMove 无限流量套餐 +🤝 每周四有 Nomad Meetup,关注 Facebook 群组""", + + "portugal-d7-visa": """## D7 签证是什么? + +葡萄牙 D7 签证(Passive Income Visa)最初为退休者设计,但因收入要求低、审批相对简单,成为数字游民进入欧盟的最佳通道之一。 + +## 申请条件 + +- 月收入不低于 **€760**(葡萄牙最低工资) +- 银行存款建议 **€9,120+**(12个月生活费) +- 无犯罪记录 +- 葡萄牙本地银行账户 +- 健康保险 + +## 申请流程 + +1. **准备材料**(2-4 周) + - 护照、照片、收入证明、银行流水 + - 葡萄牙税号(NIF) + - 住宿证明(租房合同或酒店预订) + +2. **递交申请** + - 在中国:葡萄牙驻华使馆 + - 或入境葡萄牙后转居留许可 + +3. **等待审批**(3-6 个月) + +4. **登陆葡萄牙** + - 领取居留卡 + - 登记住址 + +## 费用预算 + +- 签证费:约 €90 +- 律师费(可选):€500-1,500 +- NIF + 银行开户:€200-300 +- 首月生活费:€800-1,200 + +## 里斯本生活成本 + +- 一居室公寓:€800-1,200/月 +- Co-working:€150-250/月 +- 餐饮:€300-500/月 +- **月均总花费:€1,200-1,800** + +## 常见问题 + +**Q: D7 可以工作吗?** +A: 可以远程为海外雇主工作,但不能在葡萄牙本地公司就职。 + +**Q: 5年后能拿护照吗?** +A: 满足居住要求后可以申请永居或入籍。""", + + "nomad-tax-basics": """## 1. 什么是税务居民? + +大多数国家用 **183 天规则** 判定税务居民:在一个自然年内居住超过 183 天,即成为该国税务居民,需在该国申报全球收入。 + +## 2. 双重征税怎么办? + +如果两个国家都认为你是税务居民,查 **双边税收协定**(DTA)。中国已与 100+ 国家签署 DTA,可避免重复缴税。 + +## 3. 数字游民常见税务策略 + +| 策略 | 说明 | 适合人群 | +|------|------|----------| +| 零税务居民 | 不在任何国家住满 183 天 | 短期旅居者 | +| 税务友好国 | 葡萄牙 NHR、格鲁吉亚 1% 税 | 长期旅居者 | +| 原籍国申报 | 回国期间申报 | 兼职游民 | + +## 4. 中国税务居民注意 + +- 中国公民默认是中国税务居民(全球征税) +- 海外收入也需申报(可抵免境外已缴税) +- 建议咨询专业税务师 + +## 5. 实用建议 + +1. 📋 记录每个国家的入境/出境日期 +2. 🧾 保留所有收入凭证和银行流水 +3. 🏦 使用 Wise 等工具便于跨境汇款记录 +4. 👨‍💼 收入超过一定金额建议聘请税务顾问 +5. 📱 推荐工具:Xero(记账)、TaxScouts(报税)""", +} + +TICKER_MESSAGES = [ + "🌴 小林 刚刚抵达清迈", + "💻 Marco 在里斯本完成了 Sprint", + "✈️ 今日新增 127 位游民出发", + "🏝️ 巴厘岛 Co-working 今日 89% 满座", + "📶 清迈平均网速 95Mbps", + "🌅 东京远程工作者满意度 9.1", +] diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..b0189f4 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,31 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.config import settings +from app.routers import api, auth + +app = FastAPI( + title="NomadFlow API", + description="数字游民旅居平台后端 API", + version="1.0.0", +) + +app.add_middleware( + CORSMiddleware, + allow_origins=settings.cors_origin_list, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(api.router, prefix="/api/v1") +app.include_router(auth.router, prefix="/api/v1") + + +@app.get("/") +async def root(): + return { + "name": "NomadFlow API", + "docs": "/docs", + "health": "/api/v1/health", + } 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/api.py b/backend/app/routers/api.py new file mode 100644 index 0000000..fa292d1 --- /dev/null +++ b/backend/app/routers/api.py @@ -0,0 +1,215 @@ +from fastapi import APIRouter, HTTPException, Query + +from app.schemas import ( + BlogPost, BlogPostDetail, ChartData, CompareRequest, CostCalculatorRequest, + CostCalculatorResponse, Destination, FAQ, SearchResult, StatsResponse, + SubscribeRequest, SubscribeResponse, Testimonial, Tool, Visa, +) +from app.services.pocketbase import pb +from app.data import mock_data + +router = APIRouter() + + +@router.get("/health") +async def health(): + pb_ok = await pb.is_available() + return {"status": "ok", "pocketbase": "connected" if pb_ok else "fallback"} + + +@router.get("/stats", response_model=StatsResponse) +async def get_stats(): + dests = await pb.get_destinations() + avg = sum(d["cost"] for d in dests) // len(dests) if dests else 42 + return StatsResponse( + countries=195, + avg_cost=avg // 100, + satisfaction=87, + total_nomads="35.6M", + active_today=127, + ) + + +@router.get("/ticker") +async def get_ticker(): + return {"messages": mock_data.TICKER_MESSAGES} + + +@router.get("/destinations", response_model=list[Destination]) +async def list_destinations( + region: str | None = Query(None), + search: str | None = Query(None), + sort: str = Query("rating"), +): + data = await pb.get_destinations(region=region, search=search, sort=sort) + return [Destination(**d) for d in data] + + +@router.get("/destinations/{slug}", response_model=Destination) +async def get_destination(slug: str): + data = await pb.get_destination(slug) + if not data: + raise HTTPException(404, "目的地不存在") + return Destination(**data) + + +@router.post("/destinations/compare", response_model=list[Destination]) +async def compare_destinations(body: CompareRequest): + all_dests = await pb.get_destinations() + result = [d for d in all_dests if d["slug"] in body.slugs] + if len(result) < 2: + raise HTTPException(400, "至少需要 2 个有效目的地") + return [Destination(**d) for d in result] + + +@router.get("/visas", response_model=list[Visa]) +async def list_visas(): + return [Visa(**v) for v in await pb.get_visas()] + + +@router.get("/faqs", response_model=list[FAQ]) +async def list_faqs(): + return [FAQ(**f) for f in await pb.get_faqs()] + + +@router.get("/testimonials", response_model=list[Testimonial]) +async def list_testimonials(): + return [Testimonial(**t) for t in await pb.get_testimonials()] + + +@router.get("/tools", response_model=list[Tool]) +async def list_tools(): + return [Tool(**t) for t in await pb.get_tools()] + + +@router.get("/blog", response_model=list[BlogPost]) +async def list_blog(): + return [BlogPost(**b) for b in await pb.get_blog_posts()] + + +@router.get("/blog/{slug}", response_model=BlogPostDetail) +async def get_blog(slug: str): + post = await pb.get_blog_post(slug) + if not post: + raise HTTPException(404, "文章不存在") + return BlogPostDetail(**post) + + +@router.post("/subscribe", response_model=SubscribeResponse) +async def subscribe(body: SubscribeRequest): + ok, msg = await pb.subscribe(body.email) + return SubscribeResponse(success=ok, message=msg) + + +@router.post("/calculator", response_model=CostCalculatorResponse) +async def cost_calculator(body: CostCalculatorRequest): + dest = await pb.get_destination(body.destination_slug) + if not dest: + raise HTTPException(404, "目的地不存在") + + multipliers = {"budget": 0.7, "mid": 1.0, "premium": 1.5} + m = multipliers.get(body.housing, 1.0) + base = dest["cost"] + + breakdown = { + "🏠 住宿": int(base * 0.35 * m), + "🍜 餐饮": int(base * 0.25 * m), + "🚗 交通": int(base * 0.10), + "📶 网络/办公": int(base * 0.10), + "🎉 娱乐社交": int(base * 0.12 * m), + "🏥 保险医疗": int(base * 0.08), + } + per_month = sum(breakdown.values()) + total = per_month * body.months + + return CostCalculatorResponse( + destination=f"{dest['name']}, {dest['country']}", + emoji=dest["emoji"], + months=body.months, + breakdown=breakdown, + total=total, + per_month=per_month, + ) + + +@router.get("/charts/cost", response_model=ChartData) +async def chart_cost(): + dests = await pb.get_destinations() + dests = sorted(dests, key=lambda x: x["cost"]) + return ChartData( + labels=[f"{d['name']} {d['emoji']}" for d in dests], + datasets=[{ + "label": "月生活费 (元)", + "data": [d["cost"] for d in dests], + }], + ) + + +@router.get("/charts/speed", response_model=ChartData) +async def chart_speed(): + dests = await pb.get_destinations() + dests = sorted(dests, key=lambda x: x["speed"], reverse=True) + return ChartData( + labels=[f"{d['name']} {d['speed']}Mbps" for d in dests], + datasets=[{"data": [d["speed"] for d in dests]}], + ) + + +@router.get("/charts/growth", response_model=ChartData) +async def chart_growth(): + return ChartData( + labels=["2019", "2020", "2021", "2022", "2023", "2024", "2025", "2026"], + datasets=[{ + "label": "全球数字游民 (百万人)", + "data": [7.3, 10.9, 15.5, 24.0, 28.5, 31.2, 33.8, 35.6], + }], + ) + + +@router.get("/charts/radar", response_model=ChartData) +async def chart_radar(): + return ChartData( + labels=["生活成本", "网络速度", "安全性", "社群活跃", "气候环境", "签证便利"], + datasets=[ + {"label": "清迈", "data": [95, 80, 85, 95, 70, 90]}, + {"label": "里斯本", "data": [60, 90, 92, 80, 85, 95]}, + ], + ) + + +@router.get("/search", response_model=list[SearchResult]) +async def global_search(q: str = Query(..., min_length=1)): + query = q.lower().strip() + results: list[SearchResult] = [] + + for d in await pb.get_destinations(search=query): + results.append(SearchResult( + type="destination", + title=f"{d['name']}, {d['country']}", + subtitle=f"¥{d['cost']}/月 · ⭐{d['rating']}", + emoji=d["emoji"], + url=f"/destinations/{d['slug']}", + )) + + for b in await pb.get_blog_posts(): + if query in b["title"].lower() or query in b.get("excerpt", "").lower(): + results.append(SearchResult( + type="blog", title=b["title"], subtitle=b["excerpt"][:60], + emoji=b["emoji"], url=f"/blog/{b['slug']}", + )) + + for v in await pb.get_visas(): + if query in v["name"].lower() or query in v["country"].lower(): + results.append(SearchResult( + type="visa", title=v["name"], subtitle=v["country"], + emoji=v["flag"], url="/#visa", + )) + + for f in await pb.get_faqs(): + if query in f["question"].lower() or query in f["answer"].lower(): + results.append(SearchResult( + type="faq", title=f["question"], subtitle=f["answer"][:60], + emoji="❓", url="/#faq", + )) + + return results[:12] diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py new file mode 100644 index 0000000..5e13137 --- /dev/null +++ b/backend/app/routers/auth.py @@ -0,0 +1,103 @@ +from fastapi import APIRouter, Header, HTTPException + +from app.schemas import ( + AuthLogin, AuthRegister, AuthResponse, Destination, + FavoriteRequest, FavoriteResponse, ProfileStats, UserProfile, +) +from app.services.auth import ( + DEMO_TOKEN, get_favorites, get_user_by_token, + login_user, register_user, toggle_favorite, +) +from app.services.pocketbase import pb + +router = APIRouter(prefix="/auth", tags=["auth"]) + + +@router.post("/register", response_model=AuthResponse) +async def register(body: AuthRegister): + result = register_user(body.email, body.password, body.name) + if not result: + raise HTTPException(400, "该邮箱已注册") + return AuthResponse(**result) + + +@router.post("/login", response_model=AuthResponse) +async def login(body: AuthLogin): + result = login_user(body.email, body.password) + if not result: + raise HTTPException(401, "邮箱或密码错误") + return AuthResponse(**result) + + +@router.get("/me", response_model=UserProfile) +async def me(authorization: str | None = Header(None)): + token = _extract_token(authorization) + user = get_user_by_token(token) + if not user: + raise HTTPException(401, "未登录") + return UserProfile(**user) + + +@router.get("/demo", response_model=AuthResponse) +async def demo_login(): + """一键体验演示账号""" + user = get_user_by_token(DEMO_TOKEN) + if not user: + raise HTTPException(500, "演示账号不可用") + return AuthResponse(token=DEMO_TOKEN, user=UserProfile(**user)) + + +@router.get("/favorites", response_model=FavoriteResponse) +async def list_favorites(authorization: str | None = Header(None)): + token = _extract_token(authorization) + if not get_user_by_token(token): + raise HTTPException(401, "未登录") + return FavoriteResponse(slugs=get_favorites(token)) + + +@router.get("/favorites/detail", response_model=list[Destination]) +async def favorite_details(authorization: str | None = Header(None)): + token = _extract_token(authorization) + user = get_user_by_token(token) + if not user: + raise HTTPException(401, "未登录") + slugs = get_favorites(token) + all_dests = await pb.get_destinations() + return [Destination(**d) for d in all_dests if d["slug"] in slugs] + + +@router.get("/profile/stats", response_model=ProfileStats) +async def profile_stats(authorization: str | None = Header(None)): + token = _extract_token(authorization) + user = get_user_by_token(token) + if not user: + raise HTTPException(401, "未登录") + favs = get_favorites(token) + levels = [(0, "🌱 新手游民"), (1, "🎒 背包客"), (2, "✈️ 飞行游民"), (3, "🌍 环球游民")] + level = levels[min(len(favs), 3)][1] + return ProfileStats( + favorites_count=len(favs), + destinations_explored=len(favs), + member_since="2026", + nomad_level=level, + ) + + +@router.post("/favorites", response_model=FavoriteResponse) +async def add_favorite(body: FavoriteRequest, authorization: str | None = Header(None)): + token = _extract_token(authorization) + if not get_user_by_token(token): + raise HTTPException(401, "未登录") + dest = await pb.get_destination(body.destination_slug) + if not dest: + raise HTTPException(404, "目的地不存在") + slugs = toggle_favorite(token, body.destination_slug) + return FavoriteResponse(slugs=slugs) + + +def _extract_token(authorization: str | None) -> str: + if not authorization: + return "" + if authorization.startswith("Bearer "): + return authorization[7:] + return authorization diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..5774117 --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,164 @@ +from pydantic import BaseModel, EmailStr, Field + + +class Destination(BaseModel): + id: str + slug: str + name: str + country: str + emoji: str + tag: str + description: str + region: str + cost: int + speed: int + temperature: int + rating: float + hue: int = 170 + nomads_count: str = "5,000+" + highlights: list[str] = [] + map_x: float = 0 + map_y: float = 0 + + +class Visa(BaseModel): + id: str + country: str + flag: str + name: str + badge: str + badge_type: str + duration: str + income_req: str + approval_time: str + extra: str + difficulty: int + difficulty_label: str + + +class FAQ(BaseModel): + id: str + question: str + answer: str + order: int = 0 + + +class Testimonial(BaseModel): + id: str + avatar: str + content: str + author: str + role: str + rating: int = 5 + + +class Tool(BaseModel): + id: str + emoji: str + name: str + description: str + tags: list[str] + category: str + + +class SubscribeRequest(BaseModel): + email: EmailStr + + +class SubscribeResponse(BaseModel): + success: bool + message: str + + +class CostCalculatorRequest(BaseModel): + destination_slug: str + housing: str = "mid" # budget | mid | premium + months: int = Field(default=1, ge=1, le=24) + + +class CostCalculatorResponse(BaseModel): + destination: str + emoji: str + months: int + breakdown: dict[str, int] + total: int + per_month: int + + +class CompareRequest(BaseModel): + slugs: list[str] = Field(..., min_length=2, max_length=4) + + +class StatsResponse(BaseModel): + countries: int + avg_cost: int + satisfaction: int + total_nomads: str + active_today: int + + +class ChartData(BaseModel): + labels: list[str] + datasets: list[dict] + + +class BlogPost(BaseModel): + id: str + slug: str + title: str + excerpt: str + emoji: str + author: str + published_at: str + read_time: int + tags: list[str] = [] + + +class BlogPostDetail(BlogPost): + content: str + + +class AuthRegister(BaseModel): + email: EmailStr + password: str = Field(min_length=6) + name: str = Field(min_length=1) + + +class AuthLogin(BaseModel): + email: EmailStr + password: str + + +class UserProfile(BaseModel): + id: str + email: str + name: str + avatar: str = "🧑‍💻" + + +class AuthResponse(BaseModel): + token: str + user: UserProfile + + +class FavoriteRequest(BaseModel): + destination_slug: str + + +class FavoriteResponse(BaseModel): + slugs: list[str] + + +class ProfileStats(BaseModel): + favorites_count: int + destinations_explored: int + member_since: str + nomad_level: str + + +class SearchResult(BaseModel): + type: str # destination | blog | visa | faq + title: str + subtitle: str + emoji: str + url: str diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/auth.py b/backend/app/services/auth.py new file mode 100644 index 0000000..9145e81 --- /dev/null +++ b/backend/app/services/auth.py @@ -0,0 +1,83 @@ +"""Simple auth service with in-memory fallback when PocketBase users unavailable.""" +import hashlib +import secrets +from typing import Any + +from app.data import mock_data + +# In-memory store for demo mode +_users: dict[str, dict] = {} +_sessions: dict[str, str] = {} # token -> user_id +_favorites: dict[str, list[str]] = {} # user_id -> [slugs] + + +def _hash_password(password: str) -> str: + return hashlib.sha256(password.encode()).hexdigest() + + +def register_user(email: str, password: str, name: str) -> dict[str, Any] | None: + if email in _users: + return None + uid = secrets.token_hex(8) + _users[email] = { + "id": uid, + "email": email, + "password_hash": _hash_password(password), + "name": name, + "avatar": "🧑‍💻", + } + _favorites[uid] = [] + token = secrets.token_urlsafe(32) + _sessions[token] = uid + return {"token": token, "user": _user_profile(_users[email])} + + +def login_user(email: str, password: str) -> dict[str, Any] | None: + user = _users.get(email) + if not user or user["password_hash"] != _hash_password(password): + return None + token = secrets.token_urlsafe(32) + _sessions[token] = user["id"] + return {"token": token, "user": _user_profile(user)} + + +def get_user_by_token(token: str) -> dict | None: + uid = _sessions.get(token) + if not uid: + return None + for user in _users.values(): + if user["id"] == uid: + return _user_profile(user) + return None + + +def get_favorites(token: str) -> list[str]: + uid = _sessions.get(token) + if not uid: + return [] + return _favorites.get(uid, []) + + +def toggle_favorite(token: str, slug: str) -> list[str]: + uid = _sessions.get(token) + if not uid: + return [] + favs = _favorites.setdefault(uid, []) + if slug in favs: + favs.remove(slug) + else: + favs.append(slug) + return favs + + +def _user_profile(user: dict) -> dict: + return { + "id": user["id"], + "email": user["email"], + "name": user["name"], + "avatar": user.get("avatar", "🧑‍💻"), + } + +# Demo account +_demo = register_user("demo@nomadflow.io", "demo123", "演示用户") +DEMO_TOKEN = _demo["token"] if _demo else "" diff --git a/backend/app/services/pocketbase.py b/backend/app/services/pocketbase.py new file mode 100644 index 0000000..2719198 --- /dev/null +++ b/backend/app/services/pocketbase.py @@ -0,0 +1,183 @@ +import httpx +from typing import Any + +from app.config import settings +from app.data import mock_data + + +class PocketBaseService: + def __init__(self): + self.base_url = settings.pocketbase_url.rstrip("/") + self._token: str | None = None + self._available: bool | None = None + + async def is_available(self) -> bool: + if self._available is not None: + return self._available + try: + async with httpx.AsyncClient(timeout=1.0) as client: + r = await client.get(f"{self.base_url}/api/health") + self._available = r.status_code == 200 + except Exception: + self._available = False + return self._available + + async def _auth(self, client: httpx.AsyncClient) -> bool: + if self._token: + return True + try: + r = await client.post( + f"{self.base_url}/api/admins/auth-with-password", + json={ + "identity": settings.pocketbase_admin_email, + "password": settings.pocketbase_admin_password, + }, + ) + if r.status_code == 200: + self._token = r.json().get("token") + return True + except Exception: + pass + return False + + async def list_records(self, collection: str, sort: str = "") -> list[dict[str, Any]]: + if not await self.is_available(): + return [] + + params: dict[str, str] = {"perPage": "200"} + if sort: + params["sort"] = sort + + try: + async with httpx.AsyncClient(timeout=10.0) as client: + headers = {} + if await self._auth(client): + headers["Authorization"] = self._token or "" + + r = await client.get( + f"{self.base_url}/api/collections/{collection}/records", + params=params, + headers=headers, + ) + if r.status_code == 200: + items = r.json().get("items", []) + return items if items else [] + except Exception: + pass + return [] + + async def create_record(self, collection: str, data: dict[str, Any]) -> dict[str, Any] | None: + if not await self.is_available(): + return None + + try: + async with httpx.AsyncClient(timeout=10.0) as client: + headers = {} + if await self._auth(client): + headers["Authorization"] = self._token or "" + + r = await client.post( + f"{self.base_url}/api/collections/{collection}/records", + json=data, + headers=headers, + ) + if r.status_code in (200, 201): + return r.json() + except Exception: + pass + return None + + async def get_destinations( + self, region: str | None = None, search: str | None = None, sort: str = "rating" + ) -> list[dict]: + records = await self.list_records("destinations", sort=f"-{sort}" if sort != "cost" else "cost") + if not records: + records = mock_data.DESTINATIONS + else: + records = [self._map_destination(r) for r in records] + + if region and region != "all": + records = [d for d in records if d.get("region") == region] + if search: + q = search.lower() + records = [ + d for d in records + if q in d.get("name", "").lower() or q in d.get("country", "").lower() + ] + return self._sort_destinations(records, sort) + + async def get_destination(self, slug: str) -> dict | None: + records = await self.get_destinations() + return next((d for d in records if d.get("slug") == slug), None) + + async def get_visas(self) -> list[dict]: + records = await self.list_records("visas", sort="difficulty") + return records if records else mock_data.VISAS + + async def get_faqs(self) -> list[dict]: + records = await self.list_records("faqs", sort="order") + return records if records else mock_data.FAQS + + async def get_testimonials(self) -> list[dict]: + records = await self.list_records("testimonials") + return records if records else mock_data.TESTIMONIALS + + async def get_tools(self) -> list[dict]: + records = await self.list_records("tools") + return records if records else mock_data.TOOLS + + async def get_blog_posts(self) -> list[dict]: + records = await self.list_records("blog_posts", sort="-published_at") + return records if records else mock_data.BLOG_POSTS + + async def get_blog_post(self, slug: str) -> dict | None: + posts = await self.get_blog_posts() + post = next((p for p in posts if p.get("slug") == slug), None) + if not post: + return None + content = post.get("content") or mock_data.BLOG_CONTENT.get(slug, "") + return {**post, "content": content} + + async def subscribe(self, email: str) -> tuple[bool, str]: + existing = await self.list_records("subscriptions") + if any(s.get("email") == email for s in existing): + return True, "你已经订阅过了,欢迎回来!" + + result = await self.create_record("subscriptions", {"email": email}) + if result: + return True, "订阅成功!欢迎加入 NomadFlow 社区 🎉" + # fallback: always succeed in demo mode + return True, "订阅成功!欢迎加入 NomadFlow 社区 🎉" + + def _map_destination(self, r: dict) -> dict: + return { + "id": r.get("id", ""), + "slug": r.get("slug", ""), + "name": r.get("name", ""), + "country": r.get("country", ""), + "emoji": r.get("emoji", ""), + "tag": r.get("tag", ""), + "description": r.get("description", ""), + "region": r.get("region", ""), + "cost": r.get("cost", 0), + "speed": r.get("speed", 0), + "temperature": r.get("temperature", 0), + "rating": r.get("rating", 0), + "hue": r.get("hue", 170), + "nomads_count": r.get("nomads_count", ""), + "highlights": r.get("highlights", []), + "map_x": r.get("map_x", 0), + "map_y": r.get("map_y", 0), + } + + def _sort_destinations(self, records: list[dict], sort: str) -> list[dict]: + if sort == "cost-asc": + return sorted(records, key=lambda x: x.get("cost", 0)) + if sort == "cost-desc": + return sorted(records, key=lambda x: x.get("cost", 0), reverse=True) + if sort == "speed": + return sorted(records, key=lambda x: x.get("speed", 0), reverse=True) + return sorted(records, key=lambda x: x.get("rating", 0), reverse=True) + + +pb = PocketBaseService() diff --git a/backend/railway.toml b/backend/railway.toml new file mode 100644 index 0000000..afe771d --- /dev/null +++ b/backend/railway.toml @@ -0,0 +1,8 @@ +[build] +builder = "DOCKERFILE" +dockerfilePath = "Dockerfile" + +[deploy] +startCommand = "uvicorn app.main:app --host 0.0.0.0 --port $PORT" +healthcheckPath = "/api/v1/health" +restartPolicyType = "ON_FAILURE" diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..5e0767e --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +httpx==0.28.1 +pydantic[email]==2.10.4 +pydantic-settings==2.7.0 +python-dotenv==1.0.1 +email-validator==2.2.0 diff --git a/backend/seed_pocketbase.py b/backend/seed_pocketbase.py new file mode 100644 index 0000000..f890a64 --- /dev/null +++ b/backend/seed_pocketbase.py @@ -0,0 +1,142 @@ +""" +Seed PocketBase collections with NomadFlow data. +Run after PocketBase is started: python seed_pocketbase.py +""" +import asyncio +import json +import httpx +from app.config import settings +from app.data.mock_data import DESTINATIONS, VISAS, FAQS, TESTIMONIALS, TOOLS, BLOG_POSTS + +COLLECTIONS = { + "destinations": { + "schema": [ + {"name": "slug", "type": "text", "required": True}, + {"name": "name", "type": "text", "required": True}, + {"name": "country", "type": "text"}, + {"name": "emoji", "type": "text"}, + {"name": "tag", "type": "text"}, + {"name": "description", "type": "text"}, + {"name": "region", "type": "text"}, + {"name": "cost", "type": "number"}, + {"name": "speed", "type": "number"}, + {"name": "temperature", "type": "number"}, + {"name": "rating", "type": "number"}, + {"name": "hue", "type": "number"}, + {"name": "nomads_count", "type": "text"}, + {"name": "highlights", "type": "json"}, + {"name": "map_x", "type": "number"}, + {"name": "map_y", "type": "number"}, + ], + "data": DESTINATIONS, + "key": "slug", + }, + "visas": { + "schema": [ + {"name": "country", "type": "text"}, {"name": "flag", "type": "text"}, + {"name": "name", "type": "text"}, {"name": "badge", "type": "text"}, + {"name": "badge_type", "type": "text"}, {"name": "duration", "type": "text"}, + {"name": "income_req", "type": "text"}, {"name": "approval_time", "type": "text"}, + {"name": "extra", "type": "text"}, {"name": "difficulty", "type": "number"}, + {"name": "difficulty_label", "type": "text"}, + ], + "data": VISAS, + "key": "name", + }, + "faqs": { + "schema": [ + {"name": "question", "type": "text"}, {"name": "answer", "type": "text"}, + {"name": "order", "type": "number"}, + ], + "data": FAQS, + "key": "question", + }, + "testimonials": { + "schema": [ + {"name": "avatar", "type": "text"}, {"name": "content", "type": "text"}, + {"name": "author", "type": "text"}, {"name": "role", "type": "text"}, + {"name": "rating", "type": "number"}, + ], + "data": TESTIMONIALS, + "key": "author", + }, + "tools": { + "schema": [ + {"name": "emoji", "type": "text"}, {"name": "name", "type": "text"}, + {"name": "description", "type": "text"}, {"name": "tags", "type": "json"}, + {"name": "category", "type": "text"}, + ], + "data": TOOLS, + "key": "name", + }, + "blog_posts": { + "schema": [ + {"name": "slug", "type": "text"}, {"name": "title", "type": "text"}, + {"name": "excerpt", "type": "text"}, {"name": "content", "type": "text"}, + {"name": "emoji", "type": "text"}, {"name": "author", "type": "text"}, + {"name": "published_at", "type": "text"}, {"name": "read_time", "type": "number"}, + {"name": "tags", "type": "json"}, + ], + "data": [{**p, "content": mock_data.BLOG_CONTENT.get(p["slug"], "")} for p in BLOG_POSTS], + "key": "slug", + }, + "subscriptions": { + "schema": [{"name": "email", "type": "email", "required": True}], + "data": [], + "key": "email", + }, +} + + +async def main(): + base = settings.pocketbase_url.rstrip("/") + async with httpx.AsyncClient(timeout=15.0) as client: + # Auth + auth = await client.post( + f"{base}/api/admins/auth-with-password", + json={"identity": settings.pocketbase_admin_email, "password": settings.pocketbase_admin_password}, + ) + if auth.status_code != 200: + print("❌ PocketBase 认证失败,请先在 Admin UI 创建管理员账号") + print(f" 访问 {base}/_/ 创建账号: {settings.pocketbase_admin_email}") + return + + token = auth.json()["token"] + headers = {"Authorization": token} + + for coll_name, config in COLLECTIONS.items(): + # Check if collection exists + check = await client.get(f"{base}/api/collections/{coll_name}", headers=headers) + if check.status_code == 404: + body = { + "name": coll_name, + "type": "base", + "schema": config["schema"], + } + create = await client.post(f"{base}/api/collections", json=body, headers=headers) + if create.status_code in (200, 201): + print(f"✅ 创建集合: {coll_name}") + else: + print(f"⚠️ 集合 {coll_name} 创建失败: {create.text}") + continue + else: + print(f"📦 集合已存在: {coll_name}") + + # Seed records + for item in config["data"]: + payload = {k: v for k, v in item.items() if k != "id"} + r = await client.post( + f"{base}/api/collections/{coll_name}/records", + json=payload, + headers=headers, + ) + if r.status_code in (200, 201): + print(f" + {item.get(config['key'], '')}") + elif "already" in r.text.lower() or r.status_code == 400: + print(f" ~ {item.get(config['key'], '')} (已存在)") + + print("\n🎉 数据种子完成!") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..7eb99d0 --- /dev/null +++ b/css/style.css @@ -0,0 +1,1912 @@ +/* ===== CSS Variables & Reset ===== */ +:root { + --bg-primary: #0a0e17; + --bg-secondary: #111827; + --bg-card: rgba(17, 24, 39, 0.7); + --bg-glass: rgba(255, 255, 255, 0.05); + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --text-muted: #64748b; + --accent-1: #FF6B6B; + --accent-2: #FFE66D; + --accent-3: #4ECDC4; + --accent-4: #A78BFA; + --gradient-main: linear-gradient(135deg, #FF6B6B, #FFE66D, #4ECDC4); + --gradient-text: linear-gradient(135deg, #FF6B6B 0%, #FFE66D 50%, #4ECDC4 100%); + --shadow-glow: 0 0 40px rgba(78, 205, 196, 0.15); + --shadow-card: 0 8px 32px rgba(0, 0, 0, 0.3); + --border-glass: 1px solid rgba(255, 255, 255, 0.08); + --radius-sm: 8px; + --radius-md: 16px; + --radius-lg: 24px; + --radius-xl: 32px; + --font-display: 'Outfit', 'Noto Sans SC', sans-serif; + --font-body: 'Noto Sans SC', 'Outfit', sans-serif; + --nav-height: 72px; + --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +[data-theme="light"] { + --bg-primary: #f8fafc; + --bg-secondary: #ffffff; + --bg-card: rgba(255, 255, 255, 0.85); + --bg-glass: rgba(0, 0, 0, 0.03); + --text-primary: #0f172a; + --text-secondary: #475569; + --text-muted: #94a3b8; + --shadow-glow: 0 0 40px rgba(78, 205, 196, 0.1); + --shadow-card: 0 8px 32px rgba(0, 0, 0, 0.08); + --border-glass: 1px solid rgba(0, 0, 0, 0.06); +} + +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: var(--nav-height); +} + +body { + font-family: var(--font-body); + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; + transition: background var(--transition), color var(--transition); +} + +a { + text-decoration: none; + color: inherit; +} + +img { max-width: 100%; display: block; } + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; +} + +/* ===== Particle Canvas ===== */ +#particle-canvas { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; +} + +/* ===== Navbar ===== */ +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--nav-height); + z-index: 1000; + transition: all var(--transition); + background: transparent; +} + +.navbar.scrolled { + background: rgba(10, 14, 23, 0.85); + backdrop-filter: blur(20px); + border-bottom: var(--border-glass); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); +} + +[data-theme="light"] .navbar.scrolled { + background: rgba(248, 250, 252, 0.9); +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo { + display: flex; + align-items: center; + gap: 10px; + font-family: var(--font-display); + font-weight: 700; + font-size: 1.25rem; +} + +.logo-icon { + width: 36px; + height: 36px; + animation: logoSpin 20s linear infinite; +} + +@keyframes logoSpin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.nav-links { + display: flex; + gap: 8px; +} + +.nav-links a { + padding: 8px 16px; + border-radius: var(--radius-sm); + font-size: 0.9rem; + font-weight: 500; + color: var(--text-secondary); + transition: all var(--transition); +} + +.nav-links a:hover { + color: var(--text-primary); + background: var(--bg-glass); +} + +.nav-cta { + width: 44px; + height: 44px; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-glass); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + transition: all var(--transition); + backdrop-filter: blur(10px); +} + +.nav-cta:hover { + transform: scale(1.1); + box-shadow: var(--shadow-glow); +} + +.mobile-menu-btn { + display: none; + flex-direction: column; + gap: 5px; + background: none; + border: none; + cursor: pointer; + padding: 8px; +} + +.mobile-menu-btn span { + display: block; + width: 24px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition); +} + +/* ===== Hero ===== */ +.hero { + position: relative; + min-height: 100vh; + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + gap: 40px; + padding: calc(var(--nav-height) + 40px) 24px 80px; + max-width: 1200px; + margin: 0 auto; + z-index: 1; +} + +.hero-bg-shapes .shape { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.4; + animation: floatShape 8s ease-in-out infinite; +} + +.shape-1 { + width: 400px; + height: 400px; + background: var(--accent-1); + top: 10%; + left: -10%; + animation-delay: 0s; +} + +.shape-2 { + width: 300px; + height: 300px; + background: var(--accent-3); + top: 50%; + right: -5%; + animation-delay: -3s; +} + +.shape-3 { + width: 250px; + height: 250px; + background: var(--accent-2); + bottom: 10%; + left: 30%; + animation-delay: -5s; +} + +@keyframes floatShape { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(30px, -30px) scale(1.05); } + 66% { transform: translate(-20px, 20px) scale(0.95); } +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + border-radius: 100px; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 24px; + backdrop-filter: blur(10px); +} + +.badge-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #22c55e; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.5); } +} + +.hero-title { + font-family: var(--font-display); + font-size: clamp(2.5rem, 5vw, 4rem); + font-weight: 800; + line-height: 1.15; + margin-bottom: 20px; + letter-spacing: -0.02em; +} + +.gradient-text { + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + background-size: 200% auto; + animation: gradientShift 4s ease-in-out infinite; +} + +@keyframes gradientShift { + 0%, 100% { background-position: 0% center; } + 50% { background-position: 100% center; } +} + +.hero-subtitle { + font-size: 1.1rem; + color: var(--text-secondary); + margin-bottom: 36px; + max-width: 480px; + line-height: 1.8; +} + +.hero-actions { + display: flex; + gap: 16px; + margin-bottom: 48px; + flex-wrap: wrap; +} + +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 14px 28px; + border-radius: var(--radius-md); + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + border: none; + transition: all var(--transition); + font-family: inherit; +} + +.btn-primary { + background: var(--gradient-main); + color: #0a0e17; + box-shadow: 0 4px 20px rgba(255, 107, 107, 0.3); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 30px rgba(255, 107, 107, 0.4); +} + +.btn-ghost { + background: var(--bg-glass); + border: var(--border-glass); + color: var(--text-primary); + backdrop-filter: blur(10px); +} + +.btn-ghost:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateY(-2px); +} + +.hero-stats { + display: flex; + align-items: center; + gap: 24px; + padding: 24px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); +} + +.stat-item { + display: flex; + flex-direction: column; + gap: 4px; +} + +.stat-number { + font-family: var(--font-display); + font-size: 2rem; + font-weight: 800; + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.stat-label { + font-size: 0.75rem; + color: var(--text-muted); + white-space: nowrap; +} + +.stat-divider { + width: 1px; + height: 40px; + background: var(--border-glass); +} + +/* Globe Visual */ +.hero-visual { + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +.globe-container { + position: relative; + width: 100%; + max-width: 420px; +} + +.globe-svg { + width: 100%; + height: auto; + animation: globeFloat 6s ease-in-out infinite; +} + +@keyframes globeFloat { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-15px); } +} + +.orbit-ring { + animation: orbitSpin 20s linear infinite; + transform-origin: center; +} + +.orbit-ring-2 { + animation-direction: reverse; + animation-duration: 15s; +} + +@keyframes orbitSpin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.continent { + animation: continentPulse 4s ease-in-out infinite; +} + +.pulse-dot { + animation: dotPulse 2s ease-in-out infinite; +} + +.pulse-dot:nth-child(2) { animation-delay: 0.5s; } +.pulse-dot:nth-child(3) { animation-delay: 1s; } +.pulse-dot:nth-child(4) { animation-delay: 1.5s; } + +@keyframes dotPulse { + 0%, 100% { r: 5; opacity: 1; } + 50% { r: 8; opacity: 0.6; } +} + +.flight-path { + animation: dashMove 3s linear infinite; +} + +@keyframes dashMove { + to { stroke-dashoffset: -20; } +} + +.floating-cards .float-card { + position: absolute; + padding: 10px 16px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-md); + font-size: 0.8rem; + font-weight: 600; + backdrop-filter: blur(20px); + box-shadow: var(--shadow-card); + animation: cardFloat 4s ease-in-out infinite; + white-space: nowrap; +} + +.float-card small { + display: block; + font-weight: 400; + color: var(--text-muted); + font-size: 0.7rem; + margin-top: 2px; +} + +.fc-1 { top: 10%; right: -10%; animation-delay: 0s; } +.fc-2 { bottom: 30%; left: -15%; animation-delay: -1.5s; } +.fc-3 { bottom: 5%; right: 5%; animation-delay: -3s; } + +@keyframes cardFloat { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.scroll-indicator { + position: absolute; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + color: var(--text-muted); + font-size: 0.8rem; + animation: bounce 2s ease-in-out infinite; +} + +.scroll-arrow svg { + opacity: 0.5; +} + +@keyframes bounce { + 0%, 100% { transform: translateX(-50%) translateY(0); } + 50% { transform: translateX(-50%) translateY(8px); } +} + +/* ===== Sections ===== */ +.section { + position: relative; + padding: 100px 0; + z-index: 1; +} + +.section-header { + text-align: center; + margin-bottom: 60px; +} + +.section-tag { + display: inline-block; + padding: 6px 16px; + border-radius: 100px; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.1em; + color: var(--accent-3); + margin-bottom: 16px; +} + +.section-header h2 { + font-family: var(--font-display); + font-size: clamp(2rem, 4vw, 2.75rem); + font-weight: 800; + margin-bottom: 12px; + letter-spacing: -0.02em; +} + +.section-header p { + color: var(--text-secondary); + font-size: 1.05rem; + max-width: 500px; + margin: 0 auto; +} + +/* ===== Destinations ===== */ +.dest-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.dest-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + overflow: hidden; + transition: all var(--transition); + backdrop-filter: blur(20px); + cursor: pointer; +} + +.dest-card:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-glow); + border-color: rgba(78, 205, 196, 0.3); +} + +.dest-img { + position: relative; + height: 180px; + background: linear-gradient( + 135deg, + hsl(var(--hue, 170), 60%, 20%), + hsl(calc(var(--hue, 170) + 40), 50%, 30%) + ); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.dest-img::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at 30% 70%, rgba(255,255,255,0.1), transparent); +} + +.dest-emoji { + font-size: 4rem; + animation: emojiBounce 3s ease-in-out infinite; + filter: drop-shadow(0 4px 12px rgba(0,0,0,0.3)); +} + +@keyframes emojiBounce { + 0%, 100% { transform: scale(1) rotate(0deg); } + 50% { transform: scale(1.1) rotate(5deg); } +} + +.dest-overlay { + position: absolute; + bottom: 12px; + left: 12px; +} + +.dest-tag { + padding: 4px 12px; + border-radius: 100px; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(10px); + font-size: 0.7rem; + color: #fff; +} + +.dest-body { + padding: 20px; +} + +.dest-body h3 { + font-family: var(--font-display); + font-size: 1.15rem; + font-weight: 700; + margin-bottom: 8px; +} + +.dest-body > p { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 16px; + line-height: 1.6; +} + +.dest-meta { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 12px; +} + +.dest-meta span { + font-size: 0.75rem; + color: var(--text-muted); + padding: 4px 8px; + background: var(--bg-glass); + border-radius: var(--radius-sm); +} + +.dest-rating { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.85rem; +} + +.stars { font-size: 0.75rem; } + +/* ===== Timeline ===== */ +.lifestyle { + background: linear-gradient(180deg, transparent, rgba(78, 205, 196, 0.03), transparent); +} + +.timeline { + position: relative; + max-width: 700px; + margin: 0 auto; +} + +.timeline-line { + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 4px; + transform: translateX(-50%); +} + +.timeline-svg-line { + animation: dashFlow 2s linear infinite; +} + +@keyframes dashFlow { + to { stroke-dashoffset: -14; } +} + +.timeline-item { + display: flex; + align-items: center; + gap: 24px; + margin-bottom: 40px; + position: relative; +} + +.timeline-item:nth-child(even) { + flex-direction: row-reverse; +} + +.timeline-item:nth-child(even) .timeline-card { + text-align: right; +} + +.timeline-time { + flex: 0 0 100px; + text-align: center; + font-weight: 700; + font-size: 0.9rem; + color: var(--accent-2); + font-family: var(--font-display); +} + +.timeline-card { + flex: 1; + padding: 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); + position: relative; +} + +.timeline-card:hover { + transform: scale(1.02); + box-shadow: var(--shadow-glow); +} + +.timeline-icon { + font-size: 2rem; + margin-bottom: 8px; +} + +.timeline-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 6px; +} + +.timeline-card p { + font-size: 0.85rem; + color: var(--text-secondary); +} + +/* ===== Charts ===== */ +.data-section { + background: linear-gradient(180deg, transparent, rgba(255, 107, 107, 0.02), transparent); +} + +.charts-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; +} + +.chart-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 28px; + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.chart-card:hover { + box-shadow: var(--shadow-glow); +} + +.chart-card h3 { + font-family: var(--font-display); + font-size: 1rem; + margin-bottom: 20px; +} + +.chart-wrap { + position: relative; + height: 280px; +} + +.chart-radar { + height: 300px; +} + +.chart-wide { + grid-column: span 2; +} + +/* ===== Tools ===== */ +.tools-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.tool-card { + position: relative; + padding: 32px 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); + overflow: hidden; +} + +.tool-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--gradient-main); + transform: scaleX(0); + transition: transform var(--transition); +} + +.tool-card:hover::before { + transform: scaleX(1); +} + +.tool-card:hover { + transform: translateY(-6px); + box-shadow: var(--shadow-glow); +} + +.tool-icon-wrap { + width: 48px; + height: 48px; + color: var(--accent-3); + margin-bottom: 12px; + opacity: 0.6; +} + +.tool-emoji { + font-size: 2rem; + display: block; + margin-bottom: 12px; +} + +.tool-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 8px; +} + +.tool-card > p { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 16px; +} + +.tool-tags { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.tool-tags span { + padding: 3px 10px; + border-radius: 100px; + font-size: 0.7rem; + background: var(--bg-glass); + color: var(--text-muted); + border: var(--border-glass); +} + +/* ===== Community ===== */ +.testimonials { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin-bottom: 60px; +} + +.testimonial-card { + padding: 28px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.testimonial-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-glow); +} + +.testimonial-avatar { + font-size: 2.5rem; + margin-bottom: 16px; +} + +.testimonial-stars { + font-size: 0.8rem; + margin-bottom: 12px; +} + +.testimonial-content p { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 20px; + font-style: italic; +} + +.testimonial-author strong { + display: block; + font-size: 0.9rem; + margin-bottom: 2px; +} + +.testimonial-author span { + font-size: 0.75rem; + color: var(--text-muted); +} + +.cta-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 40px; + padding: 48px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); + position: relative; + overflow: hidden; +} + +.cta-banner::before { + content: ''; + position: absolute; + inset: 0; + background: var(--gradient-main); + opacity: 0.05; +} + +.cta-content { + position: relative; + z-index: 1; +} + +.cta-content h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.cta-content p { + color: var(--text-secondary); + font-size: 0.9rem; +} + +.cta-form { + display: flex; + gap: 12px; + position: relative; + z-index: 1; + flex-shrink: 0; +} + +.cta-form input { + padding: 14px 20px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; + width: 280px; + outline: none; + transition: all var(--transition); +} + +.cta-form input:focus { + border-color: var(--accent-3); + box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.15); +} + +.cta-form input::placeholder { + color: var(--text-muted); +} + +/* ===== Footer ===== */ +.footer { + position: relative; + z-index: 1; + padding: 60px 0 30px; + border-top: var(--border-glass); + background: var(--bg-secondary); +} + +.footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: 40px; + margin-bottom: 40px; +} + +.footer-brand p { + color: var(--text-secondary); + font-size: 0.85rem; + margin: 16px 0; + max-width: 280px; +} + +.social-links { + display: flex; + gap: 12px; +} + +.social-links a { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 1rem; + transition: all var(--transition); +} + +.social-links a:hover { + transform: translateY(-3px); + background: var(--accent-3); + color: #0a0e17; +} + +.footer-links h4 { + font-family: var(--font-display); + font-size: 0.9rem; + margin-bottom: 16px; + color: var(--text-primary); +} + +.footer-links a { + display: block; + font-size: 0.85rem; + color: var(--text-secondary); + padding: 6px 0; + transition: color var(--transition); +} + +.footer-links a:hover { + color: var(--accent-3); +} + +.footer-bottom { + text-align: center; + padding-top: 30px; + border-top: var(--border-glass); +} + +.footer-bottom p { + font-size: 0.8rem; + color: var(--text-muted); +} + +.footer-emoji-bar { + margin-top: 12px; + font-size: 1.2rem; + letter-spacing: 8px; + animation: emojiScroll 10s linear infinite; +} + +@keyframes emojiScroll { + 0%, 100% { letter-spacing: 8px; } + 50% { letter-spacing: 16px; } +} + +/* ===== Toast ===== */ +.toast { + position: fixed; + bottom: 30px; + right: 30px; + padding: 16px 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-md); + backdrop-filter: blur(20px); + display: flex; + align-items: center; + gap: 10px; + box-shadow: var(--shadow-card); + transform: translateY(100px); + opacity: 0; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 2000; +} + +.toast.show { + transform: translateY(0); + opacity: 1; +} + +/* ===== Reveal Animation ===== */ +.reveal { + opacity: 0; + transform: translateY(30px); + transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); +} + +.reveal.visible { + opacity: 1; + transform: translateY(0); +} + +/* ===== Responsive ===== */ +@media (max-width: 1024px) { + .hero { + grid-template-columns: 1fr; + text-align: center; + padding-top: calc(var(--nav-height) + 20px); + } + + .hero-subtitle { margin: 0 auto 36px; } + .hero-actions { justify-content: center; } + .hero-stats { justify-content: center; } + .hero-visual { order: -1; } + .globe-container { max-width: 300px; margin: 0 auto; } + + .dest-grid { grid-template-columns: repeat(2, 1fr); } + .tools-grid { grid-template-columns: repeat(2, 1fr); } + .testimonials { grid-template-columns: 1fr; } + .footer-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 768px) { + .nav-links { display: none; } + .nav-links.open { + display: flex; + flex-direction: column; + position: absolute; + top: var(--nav-height); + left: 0; + right: 0; + background: var(--bg-secondary); + padding: 20px; + border-bottom: var(--border-glass); + } + + .mobile-menu-btn { display: flex; } + + .hero-stats { + flex-direction: column; + gap: 16px; + } + + .stat-divider { + width: 40px; + height: 1px; + } + + .dest-grid { grid-template-columns: 1fr; } + .charts-grid { grid-template-columns: 1fr; } + .chart-wide { grid-column: span 1; } + .tools-grid { grid-template-columns: 1fr; } + + .timeline-item, + .timeline-item:nth-child(even) { + flex-direction: column; + text-align: center; + } + + .timeline-item:nth-child(even) .timeline-card { + text-align: center; + } + + .timeline-line { display: none; } + .timeline-time { flex: none; } + + .cta-banner { + flex-direction: column; + text-align: center; + padding: 32px 24px; + } + + .cta-form { + flex-direction: column; + width: 100%; + } + + .cta-form input { width: 100%; } + + .footer-grid { grid-template-columns: 1fr; } +} + +/* ===== Loader ===== */ +.loader { + position: fixed; + inset: 0; + z-index: 9999; + background: var(--bg-primary); + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.6s ease, visibility 0.6s ease; +} + +.loader.hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.loader-inner { + text-align: center; +} + +.loader-globe { + width: 80px; + height: 80px; + margin-bottom: 16px; +} + +.loader-ring { + animation: loaderSpin 1.5s linear infinite; + transform-origin: center; +} + +@keyframes loaderSpin { + to { transform: rotate(360deg); } +} + +.loader-text { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 800; + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 20px; +} + +.loader-bar { + width: 200px; + height: 3px; + background: var(--bg-glass); + border-radius: 3px; + overflow: hidden; + margin: 0 auto; +} + +.loader-progress { + height: 100%; + background: var(--gradient-main); + border-radius: 3px; + animation: loaderProgress 1.8s ease-in-out forwards; +} + +@keyframes loaderProgress { + from { width: 0; } + to { width: 100%; } +} + +/* ===== Ticker ===== */ +.ticker-bar { + position: fixed; + top: var(--nav-height); + left: 0; + right: 0; + z-index: 999; + background: rgba(10, 14, 23, 0.9); + backdrop-filter: blur(10px); + border-bottom: var(--border-glass); + overflow: hidden; + height: 32px; +} + +[data-theme="light"] .ticker-bar { + background: rgba(248, 250, 252, 0.95); +} + +.ticker-track { + display: flex; + gap: 60px; + animation: tickerScroll 30s linear infinite; + white-space: nowrap; + padding: 6px 0; +} + +.ticker-track span { + font-size: 0.75rem; + color: var(--text-muted); + flex-shrink: 0; +} + +@keyframes tickerScroll { + from { transform: translateX(0); } + to { transform: translateX(-50%); } +} + +/* Adjust hero for ticker */ +.hero { + padding-top: calc(var(--nav-height) + 52px); +} + +/* ===== Typewriter ===== */ +.typewriter-cursor { + color: var(--accent-3); + animation: blink 1s step-end infinite; + font-weight: 300; +} + +@keyframes blink { + 50% { opacity: 0; } +} + +/* ===== Map Section ===== */ +.map-section { + background: linear-gradient(180deg, transparent, rgba(78, 205, 196, 0.02), transparent); +} + +.map-wrapper { + display: grid; + grid-template-columns: 1.4fr 1fr; + gap: 32px; + align-items: stretch; +} + +.map-svg-container { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 24px; + backdrop-filter: blur(20px); + overflow: hidden; +} + +.world-map { + width: 100%; + height: auto; +} + +.map-pin { + cursor: pointer; + transition: transform 0.3s ease; +} + +.map-pin:hover { + transform: scale(1.3); +} + +.map-pin.active circle:nth-child(2) { + fill: #fff; +} + +.pin-pulse { + animation: pinPulse 2s ease-in-out infinite; +} + +@keyframes pinPulse { + 0%, 100% { r: 20; opacity: 0.3; } + 50% { r: 28; opacity: 0.1; } +} + +.map-info-panel { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 32px; + backdrop-filter: blur(20px); + display: flex; + flex-direction: column; + justify-content: center; + min-height: 300px; + transition: all var(--transition); +} + +.map-info-default { + text-align: center; + color: var(--text-muted); +} + +.map-info-icon { + font-size: 3rem; + display: block; + margin-bottom: 16px; +} + +.map-info-default h3 { + font-family: var(--font-display); + color: var(--text-primary); + margin-bottom: 8px; +} + +.map-info-city { + animation: fadeInUp 0.4s ease; +} + +.map-info-city .city-emoji { + font-size: 3.5rem; + margin-bottom: 12px; +} + +.map-info-city h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.map-info-city .city-desc { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 20px; + line-height: 1.6; +} + +.map-info-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.map-stat { + padding: 12px; + background: var(--bg-glass); + border-radius: var(--radius-md); + text-align: center; + font-size: 0.85rem; +} + +.map-stat strong { + display: block; + font-size: 1.1rem; + color: var(--accent-3); + margin-bottom: 2px; +} + +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(15px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ===== Destination Filters ===== */ +.dest-filters { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 32px; + flex-wrap: wrap; +} + +.filter-search { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); + flex: 1; + min-width: 200px; + max-width: 280px; +} + +.filter-search svg { + color: var(--text-muted); + flex-shrink: 0; +} + +.filter-search input { + background: none; + border: none; + outline: none; + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; + width: 100%; +} + +.filter-search input::placeholder { + color: var(--text-muted); +} + +.filter-tags { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.filter-btn { + padding: 8px 16px; + border-radius: 100px; + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-secondary); + font-family: inherit; + font-size: 0.8rem; + cursor: pointer; + transition: all var(--transition); + white-space: nowrap; +} + +.filter-btn:hover, +.filter-btn.active { + background: var(--accent-3); + color: #0a0e17; + border-color: var(--accent-3); +} + +.filter-sort select { + padding: 10px 16px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + font-size: 0.85rem; + cursor: pointer; + outline: none; +} + +.dest-card.hidden-card { + display: none; +} + +.dest-empty { + text-align: center; + padding: 60px 20px; + color: var(--text-muted); + font-size: 1.1rem; +} + +/* ===== Visa Section ===== */ +.visa-section { + background: linear-gradient(180deg, transparent, rgba(255, 230, 109, 0.02), transparent); +} + +.visa-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.visa-card { + position: relative; + padding: 28px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.visa-card:hover { + transform: translateY(-6px); + box-shadow: var(--shadow-glow); + border-color: rgba(255, 230, 109, 0.3); +} + +.visa-flag { + font-size: 2.5rem; + margin-bottom: 12px; +} + +.visa-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 12px; +} + +.visa-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 100px; + font-size: 0.7rem; + font-weight: 600; + margin-bottom: 16px; +} + +.visa-badge.easy { background: rgba(78, 205, 196, 0.2); color: var(--accent-3); } +.visa-badge.hot { background: rgba(255, 107, 107, 0.2); color: var(--accent-1); } +.visa-badge.budget { background: rgba(255, 230, 109, 0.2); color: var(--accent-2); } +.visa-badge.new { background: rgba(167, 139, 250, 0.2); color: var(--accent-4); } +.visa-badge.pioneer { background: rgba(96, 165, 250, 0.2); color: #60A5FA; } + +.visa-details { + list-style: none; + margin-bottom: 20px; +} + +.visa-details li { + font-size: 0.85rem; + color: var(--text-secondary); + padding: 6px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +.visa-progress { + display: flex; + align-items: center; + gap: 10px; + font-size: 0.75rem; + color: var(--text-muted); +} + +.progress-bar { + flex: 1; + height: 6px; + background: var(--bg-glass); + border-radius: 3px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--gradient-main); + border-radius: 3px; + transition: width 1s ease; +} + +.progress-label { + white-space: nowrap; + font-weight: 600; + color: var(--accent-3); +} + +/* ===== FAQ ===== */ +.faq-section { + background: linear-gradient(180deg, transparent, rgba(167, 139, 250, 0.02), transparent); +} + +.faq-list { + max-width: 800px; + margin: 0 auto; +} + +.faq-item { + margin-bottom: 12px; + border: var(--border-glass); + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--bg-card); + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.faq-item.active { + border-color: rgba(78, 205, 196, 0.3); + box-shadow: var(--shadow-glow); +} + +.faq-question { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + background: none; + border: none; + color: var(--text-primary); + font-family: inherit; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + text-align: left; + transition: color var(--transition); +} + +.faq-question:hover { + color: var(--accent-3); +} + +.faq-chevron { + flex-shrink: 0; + transition: transform 0.3s ease; + color: var(--text-muted); +} + +.faq-item.active .faq-chevron { + transform: rotate(180deg); + color: var(--accent-3); +} + +.faq-answer { + max-height: 0; + overflow: hidden; + transition: max-height 0.4s ease, padding 0.4s ease; +} + +.faq-item.active .faq-answer { + max-height: 300px; +} + +.faq-answer p { + padding: 0 24px 20px; + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.7; +} + +/* ===== Modal ===== */ +.modal-overlay { + position: fixed; + inset: 0; + z-index: 3000; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; +} + +.modal-overlay.open { + opacity: 1; + visibility: visible; +} + +.modal { + background: var(--bg-secondary); + border: var(--border-glass); + border-radius: var(--radius-xl); + max-width: 560px; + width: 100%; + max-height: 85vh; + overflow-y: auto; + position: relative; + transform: scale(0.9) translateY(20px); + transition: transform 0.3s ease; +} + +.modal-overlay.open .modal { + transform: scale(1) translateY(0); +} + +.modal-close { + position: absolute; + top: 16px; + right: 16px; + width: 36px; + height: 36px; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + cursor: pointer; + font-size: 1rem; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition); + z-index: 1; +} + +.modal-close:hover { + background: var(--accent-1); + color: #fff; +} + +.modal-hero { + height: 200px; + display: flex; + align-items: center; + justify-content: center; + font-size: 5rem; + position: relative; +} + +.modal-hero::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(transparent 50%, var(--bg-secondary)); +} + +.modal-body { + padding: 24px 32px 32px; +} + +.modal-body h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.modal-body .modal-tag { + display: inline-block; + padding: 4px 12px; + border-radius: 100px; + background: var(--bg-glass); + font-size: 0.75rem; + color: var(--accent-3); + margin-bottom: 16px; +} + +.modal-body p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.7; + margin-bottom: 24px; +} + +.modal-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + margin-bottom: 24px; +} + +.modal-stat { + text-align: center; + padding: 16px 8px; + background: var(--bg-glass); + border-radius: var(--radius-md); +} + +.modal-stat .stat-emoji { + font-size: 1.5rem; + display: block; + margin-bottom: 4px; +} + +.modal-stat strong { + display: block; + font-size: 1rem; + margin-bottom: 2px; +} + +.modal-stat span { + font-size: 0.7rem; + color: var(--text-muted); +} + +.modal-highlights h4 { + font-size: 0.9rem; + margin-bottom: 12px; +} + +.modal-highlights ul { + list-style: none; +} + +.modal-highlights li { + padding: 8px 0; + font-size: 0.85rem; + color: var(--text-secondary); + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +/* ===== Back to Top ===== */ +.back-to-top { + position: fixed; + bottom: 30px; + left: 30px; + width: 48px; + height: 48px; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-card); + backdrop-filter: blur(20px); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transform: translateY(20px); + transition: all var(--transition); + z-index: 1000; + box-shadow: var(--shadow-card); +} + +.back-to-top.visible { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.back-to-top:hover { + background: var(--accent-3); + color: #0a0e17; + transform: translateY(-4px); +} + +/* ===== Nav Active ===== */ +.nav-links a.active { + color: var(--accent-3); + background: rgba(78, 205, 196, 0.1); +} + +/* ===== Responsive additions ===== */ +@media (max-width: 1024px) { + .map-wrapper { grid-template-columns: 1fr; } + .visa-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 768px) { + .ticker-bar { display: none; } + .hero { padding-top: calc(var(--nav-height) + 20px); } + .dest-filters { flex-direction: column; align-items: stretch; } + .filter-search { max-width: none; } + .filter-tags { justify-content: center; } + .visa-grid { grid-template-columns: 1fr; } + .modal-stats { grid-template-columns: 1fr; } + .back-to-top { bottom: 20px; left: 20px; } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ea2fdfb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +services: + pocketbase: + image: ghcr.io/muchobien/pocketbase:latest + container_name: nomadflow-pb + ports: + - "8090:8090" + volumes: + - ./pocketbase/pb_data:/pb_data + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8090/api/health"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: ./backend + container_name: nomadflow-api + ports: + - "8000:8000" + environment: + - POCKETBASE_URL=http://pocketbase:8090 + - POCKETBASE_ADMIN_EMAIL=admin@nomadflow.io + - POCKETBASE_ADMIN_PASSWORD=admin123456 + - CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 + depends_on: + pocketbase: + condition: service_healthy + restart: unless-stopped + + frontend: + build: ./frontend + container_name: nomadflow-web + ports: + - "3000:3000" + environment: + - NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1 + depends_on: + - backend + restart: unless-stopped diff --git a/frontend/.env.local.example b/frontend/.env.local.example new file mode 100644 index 0000000..47e32cf --- /dev/null +++ b/frontend/.env.local.example @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..73c1151 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md new file mode 100644 index 0000000..643577d --- /dev/null +++ b/frontend/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/frontend/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..be2dc28 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,26 @@ +FROM node:20-alpine AS base + +FROM base AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ARG NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1 +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 HOSTNAME="0.0.0.0" +CMD ["node", "server.js"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/frontend/next.config.ts b/frontend/next.config.ts new file mode 100644 index 0000000..d6a6da0 --- /dev/null +++ b/frontend/next.config.ts @@ -0,0 +1,15 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "standalone", + async rewrites() { + return [ + { + source: "/api/:path*", + destination: `${process.env.API_URL || "http://localhost:8000"}/api/:path*`, + }, + ]; + }, +}; + +export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..0ec971c --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6234 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.1.0", + "dependencies": { + "next": "16.3.3", + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.3", + "typescript": "^5" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "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, + "license": "MIT", + "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.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@next/env": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.3.tgz", + "integrity": "sha512-U2eYQRwXj+dsqxV79zFqExDdatnNY/ZWc2nsJU1p/OgT7fd3dXwlF6OjYaFQCfMoeTA19PWq+wVmYgimVA+V+g==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.3.tgz", + "integrity": "sha512-pbEh30vvjKpDoTAmo1v3q2uM4JUi8QaEBpbmjWvGfoec2jLghy/WNtvzAT0bk+Ik9oz6etjt4YjXEk4BQnicCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "4.9.1", + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.3.tgz", + "integrity": "sha512-8Hiv32QJPwdV6KYJ8meR9SBA061tQqnIKTJDocvOXlEQqib0xMFpzArosuffFUUc0sslbh7QQ8a3Yey1QV8EIw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.3.tgz", + "integrity": "sha512-A1lgKgwVchRYmSe467zdwhxT9040dd8lH+o65sL5Jet8fjB4kegw/rDyPIpYVRb6jAqwXFOJpjIXJLxQKLiE3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.3.tgz", + "integrity": "sha512-bf0FIssMFueU2dm7vQEWWxk0c8UjKTdW0yzuh0sQsD8pf1+KCLDdaqhYZNMYGmXwEOiHAUzgBKudovIlcvvBjg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.3.tgz", + "integrity": "sha512-W7viwCk9JY/cAkdz/A273rd5bb3RgT/IHwR7Upv90tunjBWNtAAhGhoecHh+teRNRSinuAFmE+l7fwZ4YKkrXg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.3.tgz", + "integrity": "sha512-0W46zw1N3ODpI6n0GeivHvvob1pooozgZVqy65k0mh4/7vr+FbY9+WpHzNVXjHipJf/A3FDheBG19H1s5A25rA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.3.tgz", + "integrity": "sha512-H4mBso8ZTMBPtdT0PN0pBx2ayTvQuTuvS6qT13d77yVFJXAPCxkyIhLTmdMaGTJs0krQYI/qpzdHijCeihXhbg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.3.tgz", + "integrity": "sha512-cTMUJpcEGmeywofCUfhR+rSsoE33+rVPnPEYNTNdLNlsOeEg/vktOsKUSTb28vUGqD2jkm4Zaskcwn7OCI6FQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.3.tgz", + "integrity": "sha512-2VR4cTBzHXaBjnGsuH6GyJjENzQOmHeAh11uY1iUhjm3j5dEUrVJuUj+VL78jaGi/Dik8xS76zEj18BsFhlVZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.68.0.tgz", + "integrity": "sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/type-utils": "8.68.0", + "@typescript-eslint/utils": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.68.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.68.0.tgz", + "integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.68.0.tgz", + "integrity": "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.68.0", + "@typescript-eslint/types": "^8.68.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.68.0.tgz", + "integrity": "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz", + "integrity": "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.68.0.tgz", + "integrity": "sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/utils": "8.68.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.68.0.tgz", + "integrity": "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.68.0.tgz", + "integrity": "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.68.0", + "@typescript-eslint/tsconfig-utils": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.68.0.tgz", + "integrity": "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.68.0.tgz", + "integrity": "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/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, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/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, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.3.tgz", + "integrity": "sha512-teqtsR26tnlfXFHfVLTM/4tzEzU8DMu6GS1sddZzhfGzgd2f2ofbgDUcsk6cssSCzX6Tk6fmWifJcdANSdPJrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.3.3", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.2.tgz", + "integrity": "sha512-UpGiiODyCGprM8EPP6JodP6jC9Rws6TCuiDOD+nn0CJhR8guI3g/ozo4ugL0vJ+Yz1UtJuuRPqvQuybVOF1VQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.3.tgz", + "integrity": "sha512-tuRTx1nQ/yVw83cwJBo9F+njGUgMn3UHQycreWHB8XsStvvAh1AthbI8/4IpKnFaF58F+iSiHejYOlMQ/eq83g==", + "license": "MIT", + "dependencies": { + "@next/env": "16.3.3", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.3.3", + "@next/swc-darwin-x64": "16.3.3", + "@next/swc-linux-arm64-gnu": "16.3.3", + "@next/swc-linux-arm64-musl": "16.3.3", + "@next/swc-linux-x64-gnu": "16.3.3", + "@next/swc-linux-x64-musl": "16.3.3", + "@next/swc-win32-arm64-msvc": "16.3.3", + "@next/swc-win32-x64-msvc": "16.3.3", + "sharp": "^0.35.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.68.0.tgz", + "integrity": "sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.68.0", + "@typescript-eslint/parser": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/utils": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.1.tgz", + "integrity": "sha512-P3GqeAEOEDqKvafVGLezbg+39YW/ze4xrD4qdS0adOY8eoI3zfjv1PQM4UwQzgT8mZKXEbqtVt8K+ZKGqkMFKg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..23c7eb7 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "next": "16.3.3", + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.3", + "typescript": "^5" + } +} diff --git a/frontend/public/file.svg b/frontend/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/frontend/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/frontend/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/next.svg b/frontend/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/window.svg b/frontend/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/frontend/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/app/blog/[slug]/page.tsx b/frontend/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..8e80368 --- /dev/null +++ b/frontend/src/app/blog/[slug]/page.tsx @@ -0,0 +1,56 @@ +import Link from "next/link"; +import { api } from "@/lib/api"; +import { notFound } from "next/navigation"; +import SiteShell from "@/components/SiteShell"; +import BlogReadingProgress from "@/components/BlogReadingProgress"; + +function renderMarkdown(content: string) { + return content.split("\n").map((line, i) => { + if (line.startsWith("## ")) return

{line.slice(3)}

; + if (line.startsWith("### ")) return

{line.slice(4)}

; + if (line.startsWith("| ")) return null; // skip table rows for simplicity + if (line.startsWith("- ")) return
  • {line.slice(2)}
  • ; + if (line.match(/^\d+\. /)) return
  • {line.replace(/^\d+\. /, "")}
  • ; + if (line.trim() === "") return
    ; + if (line.startsWith("**Q:")) return

    {line}

    ; + return

    {line}

    ; + }); +} + +export default async function BlogDetailPage({ params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + let post; + try { + post = await api.getBlogPost(slug); + } catch { + notFound(); + } + + return ( + + +
    + +
    +
    + {post.emoji} +
    + 📅 {post.published_at} + ⏱️ {post.read_time} 分钟阅读 + ✍️ {post.author} +
    +

    {post.title}

    +
    + {post.tags.map((t) => {t})} +
    +
    +
    + {renderMarkdown(post.content)} +
    +
    +
    +
    + ); +} diff --git a/frontend/src/app/destinations/[slug]/page.tsx b/frontend/src/app/destinations/[slug]/page.tsx new file mode 100644 index 0000000..ac05857 --- /dev/null +++ b/frontend/src/app/destinations/[slug]/page.tsx @@ -0,0 +1,56 @@ +import Link from "next/link"; +import { api } from "@/lib/api"; +import { notFound } from "next/navigation"; +import FavoriteButton from "@/components/FavoriteButton"; +import DestinationDetailClient from "@/components/DestinationDetailClient"; +import SiteShell from "@/components/SiteShell"; + +export default async function DestinationPage({ params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + let dest; + let allDestinations; + try { + [dest, allDestinations] = await Promise.all([ + api.getDestination(slug), + api.getDestinations(), + ]); + } catch { + notFound(); + } + + return ( + +
    + +
    +
    + {dest.emoji} +
    +
    +
    +
    + {dest.tag} +

    {dest.name}, {dest.country}

    +
    + +
    +

    {dest.description}

    +
    +
    💰¥{dest.cost.toLocaleString()}月生活费
    +
    📶{dest.speed}Mbps网速
    +
    🌡️{dest.temperature}°C均温
    +
    ⭐{dest.rating}评分
    +
    +
    +

    ✨ 亮点特色

    +
      {dest.highlights.map((h) =>
    • {h}
    • )}
    +
    + +
    +
    +
    +
    + ); +} diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css new file mode 100644 index 0000000..57d8e55 --- /dev/null +++ b/frontend/src/app/globals.css @@ -0,0 +1,4479 @@ +/* ===== CSS Variables & Reset ===== */ +:root { + --bg-primary: #0a0e17; + --bg-secondary: #111827; + --bg-card: rgba(17, 24, 39, 0.7); + --bg-glass: rgba(255, 255, 255, 0.05); + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --text-muted: #64748b; + --accent-1: #FF6B6B; + --accent-2: #FFE66D; + --accent-3: #4ECDC4; + --accent-4: #A78BFA; + --gradient-main: linear-gradient(135deg, #FF6B6B, #FFE66D, #4ECDC4); + --gradient-text: linear-gradient(135deg, #FF6B6B 0%, #FFE66D 50%, #4ECDC4 100%); + --shadow-glow: 0 0 40px rgba(78, 205, 196, 0.15); + --shadow-card: 0 8px 32px rgba(0, 0, 0, 0.3); + --border-glass: 1px solid rgba(255, 255, 255, 0.08); + --radius-sm: 8px; + --radius-md: 16px; + --radius-lg: 24px; + --radius-xl: 32px; + --font-display: 'Outfit', 'Noto Sans SC', sans-serif; + --font-body: 'Noto Sans SC', 'Outfit', sans-serif; + --nav-height: 72px; + --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +[data-theme="light"] { + --bg-primary: #f8fafc; + --bg-secondary: #ffffff; + --bg-card: rgba(255, 255, 255, 0.85); + --bg-glass: rgba(0, 0, 0, 0.03); + --text-primary: #0f172a; + --text-secondary: #475569; + --text-muted: #94a3b8; + --shadow-glow: 0 0 40px rgba(78, 205, 196, 0.1); + --shadow-card: 0 8px 32px rgba(0, 0, 0, 0.08); + --border-glass: 1px solid rgba(0, 0, 0, 0.06); +} + +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: var(--nav-height); +} + +body { + font-family: var(--font-body); + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; + transition: background var(--transition), color var(--transition); +} + +a { + text-decoration: none; + color: inherit; +} + +img { max-width: 100%; display: block; } + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; +} + +/* ===== Particle Canvas ===== */ +#particle-canvas { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; +} + +/* ===== Navbar ===== */ +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--nav-height); + z-index: 1000; + transition: all var(--transition); + background: transparent; +} + +.navbar.scrolled { + background: rgba(10, 14, 23, 0.85); + backdrop-filter: blur(20px); + border-bottom: var(--border-glass); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); +} + +[data-theme="light"] .navbar.scrolled { + background: rgba(248, 250, 252, 0.9); +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo { + display: flex; + align-items: center; + gap: 10px; + font-family: var(--font-display); + font-weight: 700; + font-size: 1.25rem; +} + +.logo-icon { + width: 36px; + height: 36px; + animation: logoSpin 20s linear infinite; +} + +@keyframes logoSpin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.nav-links { + display: flex; + gap: 8px; +} + +.nav-links a { + padding: 8px 16px; + border-radius: var(--radius-sm); + font-size: 0.9rem; + font-weight: 500; + color: var(--text-secondary); + transition: all var(--transition); +} + +.nav-links a:hover { + color: var(--text-primary); + background: var(--bg-glass); +} + +.nav-cta { + width: 44px; + height: 44px; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-glass); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + transition: all var(--transition); + backdrop-filter: blur(10px); +} + +.nav-cta:hover { + transform: scale(1.1); + box-shadow: var(--shadow-glow); +} + +.mobile-menu-btn { + display: none; + flex-direction: column; + gap: 5px; + background: none; + border: none; + cursor: pointer; + padding: 8px; +} + +.mobile-menu-btn span { + display: block; + width: 24px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition); +} + +/* ===== Hero ===== */ +.hero { + position: relative; + min-height: 100vh; + display: grid; + grid-template-columns: 1fr 1fr; + align-items: start; + gap: 40px; + padding: calc(var(--nav-height) + 40px) 24px 80px; + max-width: 1200px; + margin: 0 auto; + z-index: 1; +} + +.hero-bg-shapes .shape { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.4; + animation: floatShape 8s ease-in-out infinite; +} + +.shape-1 { + width: 400px; + height: 400px; + background: var(--accent-1); + top: 10%; + left: -10%; + animation-delay: 0s; +} + +.shape-2 { + width: 300px; + height: 300px; + background: var(--accent-3); + top: 50%; + right: -5%; + animation-delay: -3s; +} + +.shape-3 { + width: 250px; + height: 250px; + background: var(--accent-2); + bottom: 10%; + left: 30%; + animation-delay: -5s; +} + +@keyframes floatShape { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(30px, -30px) scale(1.05); } + 66% { transform: translate(-20px, 20px) scale(0.95); } +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + border-radius: 100px; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 24px; + backdrop-filter: blur(10px); +} + +.badge-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #22c55e; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.5); } +} + +.hero-title { + font-family: var(--font-display); + font-size: clamp(2.5rem, 5vw, 4rem); + font-weight: 800; + line-height: 1.15; + margin-bottom: 20px; + letter-spacing: -0.02em; +} + +.gradient-text { + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + background-size: 200% auto; + animation: gradientShift 4s ease-in-out infinite; +} + +@keyframes gradientShift { + 0%, 100% { background-position: 0% center; } + 50% { background-position: 100% center; } +} + +.hero-subtitle { + font-size: 1.1rem; + color: var(--text-secondary); + margin-bottom: 36px; + max-width: 480px; + line-height: 1.8; +} + +.hero-actions { + display: flex; + gap: 16px; + margin-bottom: 48px; + flex-wrap: wrap; +} + +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 14px 28px; + border-radius: var(--radius-md); + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + border: none; + transition: all var(--transition); + font-family: inherit; +} + +.btn-primary { + background: var(--gradient-main); + color: #0a0e17; + box-shadow: 0 4px 20px rgba(255, 107, 107, 0.3); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 30px rgba(255, 107, 107, 0.4); +} + +.btn-ghost { + background: var(--bg-glass); + border: var(--border-glass); + color: var(--text-primary); + backdrop-filter: blur(10px); +} + +.btn-ghost:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateY(-2px); +} + +.hero-stats { + display: flex; + align-items: center; + gap: 24px; + padding: 24px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); +} + +.stat-item { + display: flex; + flex-direction: column; + gap: 4px; +} + +.stat-number { + font-family: var(--font-display); + font-size: 2rem; + font-weight: 800; + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.stat-label { + font-size: 0.75rem; + color: var(--text-muted); + white-space: nowrap; +} + +.stat-divider { + width: 1px; + height: 40px; + background: var(--border-glass); +} + +/* Globe Visual */ +.hero-visual { + display: flex; + justify-content: center; + align-items: flex-start; + position: relative; + /* 与左侧 hero-badge 等高,使地球与标题「用一行代码 环游世界」顶部对齐 */ + padding-top: 58px; + width: 100%; +} + +.globe-container { + position: relative; + width: 100%; + max-width: 420px; + margin: 0; +} + +.globe-svg { + width: 100%; + height: auto; + animation: globeFloat 6s ease-in-out infinite; +} + +@keyframes globeFloat { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-15px); } +} + +.orbit-ring { + animation: orbitSpin 20s linear infinite; + transform-origin: center; +} + +.orbit-ring-2 { + animation-direction: reverse; + animation-duration: 15s; +} + +@keyframes orbitSpin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.continent { + animation: continentPulse 4s ease-in-out infinite; +} + +.pulse-dot { + animation: dotPulse 2s ease-in-out infinite; +} + +.pulse-dot:nth-child(2) { animation-delay: 0.5s; } +.pulse-dot:nth-child(3) { animation-delay: 1s; } +.pulse-dot:nth-child(4) { animation-delay: 1.5s; } + +@keyframes dotPulse { + 0%, 100% { r: 5; opacity: 1; } + 50% { r: 8; opacity: 0.6; } +} + +.flight-path { + animation: dashMove 3s linear infinite; +} + +@keyframes dashMove { + to { stroke-dashoffset: -20; } +} + +.floating-cards .float-card { + position: absolute; + padding: 10px 16px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-md); + font-size: 0.8rem; + font-weight: 600; + backdrop-filter: blur(20px); + box-shadow: var(--shadow-card); + animation: cardFloat 4s ease-in-out infinite; + white-space: nowrap; +} + +.float-card small { + display: block; + font-weight: 400; + color: var(--text-muted); + font-size: 0.7rem; + margin-top: 2px; +} + +.fc-1 { top: 8%; right: -5%; animation-delay: 0s; } +.fc-2 { top: 42%; left: -12%; animation-delay: -1.5s; } +.fc-3 { bottom: 8%; right: 0; animation-delay: -3s; } + +@keyframes cardFloat { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.scroll-indicator { + position: absolute; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + color: var(--text-muted); + font-size: 0.8rem; + animation: bounce 2s ease-in-out infinite; +} + +.scroll-arrow svg { + opacity: 0.5; +} + +@keyframes bounce { + 0%, 100% { transform: translateX(-50%) translateY(0); } + 50% { transform: translateX(-50%) translateY(8px); } +} + +/* ===== Sections ===== */ +.section { + position: relative; + padding: 100px 0; + z-index: 1; +} + +.section-header { + text-align: center; + margin-bottom: 60px; +} + +.section-tag { + display: inline-block; + padding: 6px 16px; + border-radius: 100px; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.1em; + color: var(--accent-3); + margin-bottom: 16px; +} + +.section-header h2 { + font-family: var(--font-display); + font-size: clamp(2rem, 4vw, 2.75rem); + font-weight: 800; + margin-bottom: 12px; + letter-spacing: -0.02em; +} + +.section-header p { + color: var(--text-secondary); + font-size: 1.05rem; + max-width: 500px; + margin: 0 auto; +} + +/* ===== Destinations ===== */ +.dest-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.dest-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + overflow: hidden; + transition: all var(--transition); + backdrop-filter: blur(20px); + cursor: pointer; +} + +.dest-card:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-glow); + border-color: rgba(78, 205, 196, 0.3); +} + +.dest-img { + position: relative; + height: 180px; + background: linear-gradient( + 135deg, + hsl(var(--hue, 170), 60%, 20%), + hsl(calc(var(--hue, 170) + 40), 50%, 30%) + ); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.dest-img::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at 30% 70%, rgba(255,255,255,0.1), transparent); +} + +.dest-emoji { + font-size: 4rem; + animation: emojiBounce 3s ease-in-out infinite; + filter: drop-shadow(0 4px 12px rgba(0,0,0,0.3)); +} + +@keyframes emojiBounce { + 0%, 100% { transform: scale(1) rotate(0deg); } + 50% { transform: scale(1.1) rotate(5deg); } +} + +.dest-overlay { + position: absolute; + bottom: 12px; + left: 12px; +} + +.dest-tag { + padding: 4px 12px; + border-radius: 100px; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(10px); + font-size: 0.7rem; + color: #fff; +} + +.dest-body { + padding: 20px; +} + +.dest-body h3 { + font-family: var(--font-display); + font-size: 1.15rem; + font-weight: 700; + margin-bottom: 8px; +} + +.dest-body > p { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 16px; + line-height: 1.6; +} + +.dest-meta { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 12px; +} + +.dest-meta span { + font-size: 0.75rem; + color: var(--text-muted); + padding: 4px 8px; + background: var(--bg-glass); + border-radius: var(--radius-sm); +} + +.dest-rating { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.85rem; +} + +.stars { font-size: 0.75rem; } + +/* ===== Timeline ===== */ +.lifestyle { + background: linear-gradient(180deg, transparent, rgba(78, 205, 196, 0.03), transparent); +} + +.timeline { + position: relative; + max-width: 700px; + margin: 0 auto; +} + +.timeline-line { + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 4px; + transform: translateX(-50%); +} + +.timeline-svg-line { + animation: dashFlow 2s linear infinite; +} + +@keyframes dashFlow { + to { stroke-dashoffset: -14; } +} + +.timeline-item { + display: flex; + align-items: center; + gap: 24px; + margin-bottom: 40px; + position: relative; +} + +.timeline-item:nth-child(even) { + flex-direction: row-reverse; +} + +.timeline-item:nth-child(even) .timeline-card { + text-align: right; +} + +.timeline-time { + flex: 0 0 100px; + text-align: center; + font-weight: 700; + font-size: 0.9rem; + color: var(--accent-2); + font-family: var(--font-display); +} + +.timeline-card { + flex: 1; + padding: 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); + position: relative; +} + +.timeline-card:hover { + transform: scale(1.02); + box-shadow: var(--shadow-glow); +} + +.timeline-icon { + font-size: 2rem; + margin-bottom: 8px; +} + +.timeline-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 6px; +} + +.timeline-card p { + font-size: 0.85rem; + color: var(--text-secondary); +} + +/* ===== Charts ===== */ +.data-section { + background: linear-gradient(180deg, transparent, rgba(255, 107, 107, 0.02), transparent); +} + +.charts-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; +} + +.chart-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 28px; + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.chart-card:hover { + box-shadow: var(--shadow-glow); +} + +.chart-card h3 { + font-family: var(--font-display); + font-size: 1rem; + margin-bottom: 20px; +} + +.chart-wrap { + position: relative; + height: 280px; +} + +.chart-radar { + height: 300px; +} + +.chart-wide { + grid-column: span 2; +} + +/* ===== Tools ===== */ +.tools-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.tool-card { + position: relative; + padding: 32px 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); + overflow: hidden; +} + +.tool-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--gradient-main); + transform: scaleX(0); + transition: transform var(--transition); +} + +.tool-card:hover::before { + transform: scaleX(1); +} + +.tool-card:hover { + transform: translateY(-6px); + box-shadow: var(--shadow-glow); +} + +.tool-icon-wrap { + width: 48px; + height: 48px; + color: var(--accent-3); + margin-bottom: 12px; + opacity: 0.6; +} + +.tool-emoji { + font-size: 2rem; + display: block; + margin-bottom: 12px; +} + +.tool-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 8px; +} + +.tool-card > p { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 16px; +} + +.tool-tags { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.tool-tags span { + padding: 3px 10px; + border-radius: 100px; + font-size: 0.7rem; + background: var(--bg-glass); + color: var(--text-muted); + border: var(--border-glass); +} + +/* ===== Community ===== */ +.testimonials { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin-bottom: 60px; +} + +.testimonial-card { + padding: 28px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.testimonial-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-glow); +} + +.testimonial-avatar { + font-size: 2.5rem; + margin-bottom: 16px; +} + +.testimonial-stars { + font-size: 0.8rem; + margin-bottom: 12px; +} + +.testimonial-content p { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 20px; + font-style: italic; +} + +.testimonial-author strong { + display: block; + font-size: 0.9rem; + margin-bottom: 2px; +} + +.testimonial-author span { + font-size: 0.75rem; + color: var(--text-muted); +} + +.cta-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 40px; + padding: 48px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); + position: relative; + overflow: hidden; +} + +.cta-banner::before { + content: ''; + position: absolute; + inset: 0; + background: var(--gradient-main); + opacity: 0.05; +} + +.cta-content { + position: relative; + z-index: 1; +} + +.cta-content h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.cta-content p { + color: var(--text-secondary); + font-size: 0.9rem; +} + +.cta-form { + display: flex; + gap: 12px; + position: relative; + z-index: 1; + flex-shrink: 0; +} + +.cta-form input { + padding: 14px 20px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; + width: 280px; + outline: none; + transition: all var(--transition); +} + +.cta-form input:focus { + border-color: var(--accent-3); + box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.15); +} + +.cta-form input::placeholder { + color: var(--text-muted); +} + +/* ===== Footer ===== */ +.footer { + position: relative; + z-index: 1; + padding: 60px 0 30px; + border-top: var(--border-glass); + background: var(--bg-secondary); +} + +.footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: 40px; + margin-bottom: 40px; +} + +.footer-brand p { + color: var(--text-secondary); + font-size: 0.85rem; + margin: 16px 0; + max-width: 280px; +} + +.social-links { + display: flex; + gap: 12px; +} + +.social-links a { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 1rem; + transition: all var(--transition); +} + +.social-links a:hover { + transform: translateY(-3px); + background: var(--accent-3); + color: #0a0e17; +} + +.footer-links h4 { + font-family: var(--font-display); + font-size: 0.9rem; + margin-bottom: 16px; + color: var(--text-primary); +} + +.footer-links a { + display: block; + font-size: 0.85rem; + color: var(--text-secondary); + padding: 6px 0; + transition: color var(--transition); +} + +.footer-links a:hover { + color: var(--accent-3); +} + +.footer-bottom { + text-align: center; + padding-top: 30px; + border-top: var(--border-glass); +} + +.footer-bottom p { + font-size: 0.8rem; + color: var(--text-muted); +} + +.footer-emoji-bar { + margin-top: 12px; + font-size: 1.2rem; + letter-spacing: 8px; + animation: emojiScroll 10s linear infinite; +} + +@keyframes emojiScroll { + 0%, 100% { letter-spacing: 8px; } + 50% { letter-spacing: 16px; } +} + +/* ===== Toast ===== */ +.toast { + position: fixed; + bottom: 30px; + right: 30px; + padding: 16px 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-md); + backdrop-filter: blur(20px); + display: flex; + align-items: center; + gap: 10px; + box-shadow: var(--shadow-card); + transform: translateY(100px); + opacity: 0; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 2000; +} + +.toast.show { + transform: translateY(0); + opacity: 1; +} + +/* ===== Reveal Animation ===== */ +.reveal { + opacity: 0; + transform: translateY(30px); + transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); +} + +.reveal.visible { + opacity: 1; + transform: translateY(0); +} + +/* ===== Responsive ===== */ +@media (max-width: 1024px) { + .hero { + grid-template-columns: 1fr; + text-align: center; + padding-top: calc(var(--nav-height) + 20px); + } + + .hero-subtitle { margin: 0 auto 36px; } + .hero-actions { justify-content: center; } + .hero-stats { justify-content: center; } + .hero-visual { order: -1; padding-top: 0; margin-bottom: 8px; } + .globe-container { max-width: 300px; margin: 0 auto; } + + .dest-grid { grid-template-columns: repeat(2, 1fr); } + .tools-grid { grid-template-columns: repeat(2, 1fr); } + .testimonials { grid-template-columns: 1fr; } + .footer-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 768px) { + .nav-links { display: none; } + .nav-links.open { + display: flex; + flex-direction: column; + position: absolute; + top: var(--nav-height); + left: 0; + right: 0; + background: var(--bg-secondary); + padding: 20px; + border-bottom: var(--border-glass); + } + + .mobile-menu-btn { display: flex; } + + .hero-stats { + flex-direction: column; + gap: 16px; + } + + .stat-divider { + width: 40px; + height: 1px; + } + + .dest-grid { grid-template-columns: 1fr; } + .charts-grid { grid-template-columns: 1fr; } + .chart-wide { grid-column: span 1; } + .tools-grid { grid-template-columns: 1fr; } + + .timeline-item, + .timeline-item:nth-child(even) { + flex-direction: column; + text-align: center; + } + + .timeline-item:nth-child(even) .timeline-card { + text-align: center; + } + + .timeline-line { display: none; } + .timeline-time { flex: none; } + + .cta-banner { + flex-direction: column; + text-align: center; + padding: 32px 24px; + } + + .cta-form { + flex-direction: column; + width: 100%; + } + + .cta-form input { width: 100%; } + + .footer-grid { grid-template-columns: 1fr; } +} + +/* ===== Loader ===== */ +.loader { + position: fixed; + inset: 0; + z-index: 9999; + background: var(--bg-primary); + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.6s ease, visibility 0.6s ease; +} + +.loader.hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.loader-inner { + text-align: center; +} + +.loader-globe { + width: 80px; + height: 80px; + margin-bottom: 16px; +} + +.loader-ring { + animation: loaderSpin 1.5s linear infinite; + transform-origin: center; +} + +@keyframes loaderSpin { + to { transform: rotate(360deg); } +} + +.loader-text { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 800; + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 20px; +} + +.loader-bar { + width: 200px; + height: 3px; + background: var(--bg-glass); + border-radius: 3px; + overflow: hidden; + margin: 0 auto; +} + +.loader-progress { + height: 100%; + background: var(--gradient-main); + border-radius: 3px; + animation: loaderProgress 1.8s ease-in-out forwards; +} + +@keyframes loaderProgress { + from { width: 0; } + to { width: 100%; } +} + +/* ===== Ticker ===== */ +.ticker-bar { + position: fixed; + top: var(--nav-height); + left: 0; + right: 0; + z-index: 999; + background: rgba(10, 14, 23, 0.9); + backdrop-filter: blur(10px); + border-bottom: var(--border-glass); + overflow: hidden; + height: 32px; +} + +[data-theme="light"] .ticker-bar { + background: rgba(248, 250, 252, 0.95); +} + +.ticker-track { + display: flex; + gap: 60px; + animation: tickerScroll 30s linear infinite; + white-space: nowrap; + padding: 6px 0; +} + +.ticker-track span { + font-size: 0.75rem; + color: var(--text-muted); + flex-shrink: 0; +} + +@keyframes tickerScroll { + from { transform: translateX(0); } + to { transform: translateX(-50%); } +} + +/* Adjust hero for ticker */ +.hero { + padding-top: calc(var(--nav-height) + 52px); +} + +/* ===== Typewriter ===== */ +.typewriter-cursor { + color: var(--accent-3); + animation: blink 1s step-end infinite; + font-weight: 300; +} + +@keyframes blink { + 50% { opacity: 0; } +} + +/* ===== Map Section ===== */ +.map-section { + background: linear-gradient(180deg, transparent, rgba(78, 205, 196, 0.02), transparent); +} + +.map-wrapper { + display: grid; + grid-template-columns: 1.4fr 1fr; + gap: 32px; + align-items: stretch; +} + +.map-svg-container { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 24px; + backdrop-filter: blur(20px); + overflow: hidden; +} + +.world-map { + width: 100%; + height: auto; +} + +.map-pin { + cursor: pointer; + transition: transform 0.3s ease; +} + +.map-pin:hover { + transform: scale(1.3); +} + +.map-pin.active circle:nth-child(2) { + fill: #fff; +} + +.pin-pulse { + animation: pinPulse 2s ease-in-out infinite; +} + +@keyframes pinPulse { + 0%, 100% { r: 20; opacity: 0.3; } + 50% { r: 28; opacity: 0.1; } +} + +.map-info-panel { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 32px; + backdrop-filter: blur(20px); + display: flex; + flex-direction: column; + justify-content: center; + min-height: 300px; + transition: all var(--transition); +} + +.map-info-default { + text-align: center; + color: var(--text-muted); +} + +.map-info-icon { + font-size: 3rem; + display: block; + margin-bottom: 16px; +} + +.map-info-default h3 { + font-family: var(--font-display); + color: var(--text-primary); + margin-bottom: 8px; +} + +.map-info-city { + animation: fadeInUp 0.4s ease; +} + +.map-info-city .city-emoji { + font-size: 3.5rem; + margin-bottom: 12px; +} + +.map-info-city h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.map-info-city .city-desc { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 20px; + line-height: 1.6; +} + +.map-info-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.map-stat { + padding: 12px; + background: var(--bg-glass); + border-radius: var(--radius-md); + text-align: center; + font-size: 0.85rem; +} + +.map-stat strong { + display: block; + font-size: 1.1rem; + color: var(--accent-3); + margin-bottom: 2px; +} + +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(15px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ===== Destination Filters ===== */ +.dest-filters { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 32px; + flex-wrap: wrap; +} + +.filter-search { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); + flex: 1; + min-width: 200px; + max-width: 280px; +} + +.filter-search svg { + color: var(--text-muted); + flex-shrink: 0; +} + +.filter-search input { + background: none; + border: none; + outline: none; + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; + width: 100%; +} + +.filter-search input::placeholder { + color: var(--text-muted); +} + +.filter-tags { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.filter-btn { + padding: 8px 16px; + border-radius: 100px; + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-secondary); + font-family: inherit; + font-size: 0.8rem; + cursor: pointer; + transition: all var(--transition); + white-space: nowrap; +} + +.filter-btn:hover, +.filter-btn.active { + background: var(--accent-3); + color: #0a0e17; + border-color: var(--accent-3); +} + +.filter-sort select { + padding: 10px 16px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + font-size: 0.85rem; + cursor: pointer; + outline: none; +} + +.dest-card.hidden-card { + display: none; +} + +.dest-empty { + text-align: center; + padding: 60px 20px; + color: var(--text-muted); + font-size: 1.1rem; +} + +/* ===== Visa Section ===== */ +.visa-section { + background: linear-gradient(180deg, transparent, rgba(255, 230, 109, 0.02), transparent); +} + +.visa-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.visa-card { + position: relative; + padding: 28px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.visa-card:hover { + transform: translateY(-6px); + box-shadow: var(--shadow-glow); + border-color: rgba(255, 230, 109, 0.3); +} + +.visa-flag { + font-size: 2.5rem; + margin-bottom: 12px; +} + +.visa-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 12px; +} + +.visa-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 100px; + font-size: 0.7rem; + font-weight: 600; + margin-bottom: 16px; +} + +.visa-badge.easy { background: rgba(78, 205, 196, 0.2); color: var(--accent-3); } +.visa-badge.hot { background: rgba(255, 107, 107, 0.2); color: var(--accent-1); } +.visa-badge.budget { background: rgba(255, 230, 109, 0.2); color: var(--accent-2); } +.visa-badge.new { background: rgba(167, 139, 250, 0.2); color: var(--accent-4); } +.visa-badge.pioneer { background: rgba(96, 165, 250, 0.2); color: #60A5FA; } + +.visa-details { + list-style: none; + margin-bottom: 20px; +} + +.visa-details li { + font-size: 0.85rem; + color: var(--text-secondary); + padding: 6px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +.visa-progress { + display: flex; + align-items: center; + gap: 10px; + font-size: 0.75rem; + color: var(--text-muted); +} + +.progress-bar { + flex: 1; + height: 6px; + background: var(--bg-glass); + border-radius: 3px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--gradient-main); + border-radius: 3px; + transition: width 1s ease; +} + +.progress-label { + white-space: nowrap; + font-weight: 600; + color: var(--accent-3); +} + +/* ===== FAQ ===== */ +.faq-section { + background: linear-gradient(180deg, transparent, rgba(167, 139, 250, 0.02), transparent); +} + +.faq-list { + max-width: 800px; + margin: 0 auto; +} + +.faq-item { + margin-bottom: 12px; + border: var(--border-glass); + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--bg-card); + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.faq-item.active { + border-color: rgba(78, 205, 196, 0.3); + box-shadow: var(--shadow-glow); +} + +.faq-question { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + background: none; + border: none; + color: var(--text-primary); + font-family: inherit; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + text-align: left; + transition: color var(--transition); +} + +.faq-question:hover { + color: var(--accent-3); +} + +.faq-chevron { + flex-shrink: 0; + transition: transform 0.3s ease; + color: var(--text-muted); +} + +.faq-item.active .faq-chevron { + transform: rotate(180deg); + color: var(--accent-3); +} + +.faq-answer { + max-height: 0; + overflow: hidden; + transition: max-height 0.4s ease, padding 0.4s ease; +} + +.faq-item.active .faq-answer { + max-height: 300px; +} + +.faq-answer p { + padding: 0 24px 20px; + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.7; +} + +/* ===== Modal ===== */ +.modal-overlay { + position: fixed; + inset: 0; + z-index: 3000; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; +} + +.modal-overlay.open { + opacity: 1; + visibility: visible; +} + +.modal { + background: var(--bg-secondary); + border: var(--border-glass); + border-radius: var(--radius-xl); + max-width: 560px; + width: 100%; + max-height: 85vh; + overflow-y: auto; + position: relative; + transform: scale(0.9) translateY(20px); + transition: transform 0.3s ease; +} + +.modal-overlay.open .modal { + transform: scale(1) translateY(0); +} + +.modal-close { + position: absolute; + top: 16px; + right: 16px; + width: 36px; + height: 36px; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + cursor: pointer; + font-size: 1rem; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition); + z-index: 1; +} + +.modal-close:hover { + background: var(--accent-1); + color: #fff; +} + +.modal-hero { + height: 200px; + display: flex; + align-items: center; + justify-content: center; + font-size: 5rem; + position: relative; +} + +.modal-hero::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(transparent 50%, var(--bg-secondary)); +} + +.modal-body { + padding: 24px 32px 32px; +} + +.modal-body h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.modal-body .modal-tag { + display: inline-block; + padding: 4px 12px; + border-radius: 100px; + background: var(--bg-glass); + font-size: 0.75rem; + color: var(--accent-3); + margin-bottom: 16px; +} + +.modal-body p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.7; + margin-bottom: 24px; +} + +.modal-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + margin-bottom: 24px; +} + +.modal-stat { + text-align: center; + padding: 16px 8px; + background: var(--bg-glass); + border-radius: var(--radius-md); +} + +.modal-stat .stat-emoji { + font-size: 1.5rem; + display: block; + margin-bottom: 4px; +} + +.modal-stat strong { + display: block; + font-size: 1rem; + margin-bottom: 2px; +} + +.modal-stat span { + font-size: 0.7rem; + color: var(--text-muted); +} + +.modal-highlights h4 { + font-size: 0.9rem; + margin-bottom: 12px; +} + +.modal-highlights ul { + list-style: none; +} + +.modal-highlights li { + padding: 8px 0; + font-size: 0.85rem; + color: var(--text-secondary); + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +/* ===== Back to Top ===== */ +.back-to-top { + position: fixed; + bottom: 30px; + left: 30px; + width: 48px; + height: 48px; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-card); + backdrop-filter: blur(20px); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transform: translateY(20px); + transition: all var(--transition); + z-index: 1000; + box-shadow: var(--shadow-card); +} + +.back-to-top.visible { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.back-to-top:hover { + background: var(--accent-3); + color: #0a0e17; + transform: translateY(-4px); +} + +/* ===== Nav Active ===== */ +.nav-links a.active { + color: var(--accent-3); + background: rgba(78, 205, 196, 0.1); +} + +/* ===== Responsive additions ===== */ +@media (max-width: 1024px) { + .map-wrapper { grid-template-columns: 1fr; } + .visa-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 768px) { + .ticker-bar { display: none; } + .hero { padding-top: calc(var(--nav-height) + 20px); } + .dest-filters { flex-direction: column; align-items: stretch; } + .filter-search { max-width: none; } + .filter-tags { justify-content: center; } + .visa-grid { grid-template-columns: 1fr; } + .modal-stats { grid-template-columns: 1fr; } + .back-to-top { bottom: 20px; left: 20px; } +} + +/* ===== Calculator ===== */ +.calculator-section { + background: linear-gradient(180deg, transparent, rgba(167, 139, 250, 0.02), transparent); +} + +.calculator-wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px; + align-items: start; +} + +.calculator-form { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 32px; + backdrop-filter: blur(20px); +} + +.calc-field { + margin-bottom: 24px; +} + +.calc-field label { + display: block; + font-weight: 600; + font-size: 0.9rem; + margin-bottom: 10px; +} + +.calc-field select, +.calc-field input[type="range"] { + width: 100%; + padding: 12px 16px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; +} + +.calc-options { + display: flex; + gap: 8px; +} + +.calc-option { + flex: 1; + padding: 10px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-secondary); + font-family: inherit; + font-size: 0.85rem; + cursor: pointer; + transition: all var(--transition); +} + +.calc-option.active, +.calc-option:hover { + background: var(--accent-3); + color: #0a0e17; + border-color: var(--accent-3); +} + +.calculator-result { + min-height: 400px; +} + +.result-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 32px; + backdrop-filter: blur(20px); +} + +.result-header { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 28px; +} + +.result-header h3 { + font-family: var(--font-display); + font-size: 1.3rem; +} + +.result-header p { + color: var(--text-muted); + font-size: 0.85rem; +} + +.breakdown-item { + display: grid; + grid-template-columns: 100px 1fr 80px; + align-items: center; + gap: 12px; + margin-bottom: 12px; + font-size: 0.85rem; +} + +.breakdown-bar { + height: 8px; + background: var(--bg-glass); + border-radius: 4px; + overflow: hidden; +} + +.breakdown-fill { + height: 100%; + background: var(--gradient-main); + border-radius: 4px; + transition: width 0.8s ease; +} + +.result-total { + display: flex; + justify-content: space-between; + margin-top: 24px; + padding-top: 24px; + border-top: var(--border-glass); +} + +.result-total strong { + display: block; + font-size: 1.5rem; + font-family: var(--font-display); +} + +.result-total span { + font-size: 0.8rem; + color: var(--text-muted); +} + +.result-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 400px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + color: var(--text-muted); + text-align: center; + gap: 16px; +} + +/* ===== Blog ===== */ +.blog-section { + background: linear-gradient(180deg, transparent, rgba(78, 205, 196, 0.02), transparent); +} + +.blog-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; +} + +.blog-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 28px; + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.blog-card:hover { + transform: translateY(-6px); + box-shadow: var(--shadow-glow); +} + +.blog-emoji { + font-size: 2.5rem; + margin-bottom: 12px; +} + +.blog-meta { + display: flex; + gap: 16px; + font-size: 0.75rem; + color: var(--text-muted); + margin-bottom: 12px; +} + +.blog-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; + margin-bottom: 10px; + line-height: 1.4; +} + +.blog-card p { + font-size: 0.85rem; + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: 16px; +} + +.blog-tags { + display: flex; + gap: 6px; + flex-wrap: wrap; + margin-bottom: 16px; +} + +.blog-tag { + padding: 3px 10px; + border-radius: 100px; + font-size: 0.7rem; + background: var(--bg-glass); + color: var(--accent-3); + border: var(--border-glass); +} + +.blog-read-more { + font-size: 0.85rem; + font-weight: 600; + color: var(--accent-3); + transition: color var(--transition); +} + +.blog-read-more:hover { + color: var(--accent-1); +} + +/* ===== Compare Bar ===== */ +.compare-bar { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 1500; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 16px 24px; + backdrop-filter: blur(20px); + box-shadow: var(--shadow-card); + display: flex; + align-items: center; + gap: 16px; +} + +.compare-table { + width: 100%; + border-collapse: collapse; + font-size: 0.85rem; +} + +.compare-table th, +.compare-table td { + padding: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.compare-table th { + text-align: center; +} + +.compare-table td:first-child { + color: var(--text-muted); +} + +.compare-table td:not(:first-child) { + text-align: center; +} + +@media (max-width: 1024px) { + .calculator-wrapper { grid-template-columns: 1fr; } + .blog-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 768px) { + .blog-grid { grid-template-columns: 1fr; } + .compare-bar { left: 16px; right: 16px; bottom: 16px; flex-wrap: wrap; justify-content: center; } +} + +/* ===== Auth Page ===== */ +.auth-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 40px 24px; + position: relative; + z-index: 1; +} + +.auth-card { + width: 100%; + max-width: 420px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 40px; + backdrop-filter: blur(20px); +} + +.auth-back { + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 24px; + display: inline-block; +} + +.auth-header { + text-align: center; + margin-bottom: 28px; +} + +.auth-header h1 { + font-family: var(--font-display); + font-size: 1.5rem; + margin: 12px 0 6px; +} + +.auth-header p { + color: var(--text-muted); + font-size: 0.9rem; +} + +.auth-tabs { + display: flex; + gap: 8px; + margin-bottom: 24px; +} + +.auth-tabs button { + flex: 1; + padding: 10px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-secondary); + font-family: inherit; + cursor: pointer; + transition: all var(--transition); +} + +.auth-tabs button.active { + background: var(--accent-3); + color: #0a0e17; + border-color: var(--accent-3); +} + +.auth-form .calc-field input { + width: 100%; + padding: 12px 16px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; + outline: none; +} + +.auth-error { + color: var(--accent-1); + font-size: 0.85rem; + margin-bottom: 12px; +} + +.auth-divider { + text-align: center; + margin: 20px 0; + color: var(--text-muted); + font-size: 0.8rem; + position: relative; +} + +.auth-divider::before, +.auth-divider::after { + content: ''; + position: absolute; + top: 50%; + width: 40%; + height: 1px; + background: var(--border-glass); +} + +.auth-divider::before { left: 0; } +.auth-divider::after { right: 0; } + +.auth-hint { + text-align: center; + font-size: 0.75rem; + color: var(--text-muted); + margin-top: 16px; +} + +/* ===== Detail Pages ===== */ +.detail-page { + min-height: 100vh; + padding: calc(var(--nav-height) + 40px) 24px 80px; + max-width: 800px; + margin: 0 auto; + position: relative; + z-index: 1; +} + +.detail-nav { + margin-bottom: 32px; +} + +.detail-nav a { + color: var(--accent-3); + font-size: 0.9rem; + font-weight: 500; +} + +.blog-detail-header { + margin-bottom: 40px; +} + +.blog-detail-emoji { + font-size: 4rem; + margin-bottom: 16px; + display: block; +} + +.blog-detail-header h1 { + font-family: var(--font-display); + font-size: clamp(1.75rem, 4vw, 2.5rem); + line-height: 1.3; + margin: 16px 0; +} + +.blog-detail-content { + line-height: 1.8; + color: var(--text-secondary); +} + +.blog-detail-content h2 { + font-family: var(--font-display); + color: var(--text-primary); + font-size: 1.3rem; + margin: 32px 0 12px; +} + +.blog-detail-content h3 { + color: var(--text-primary); + font-size: 1.1rem; + margin: 24px 0 8px; +} + +.blog-detail-content p { + margin-bottom: 12px; +} + +.blog-detail-content li { + margin-left: 20px; + margin-bottom: 6px; +} + +.dest-detail-hero { + height: 240px; + border-radius: var(--radius-xl); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 32px; +} + +.dest-detail-emoji { + font-size: 6rem; +} + +.dest-detail-top { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; +} + +.dest-detail-top h1 { + font-family: var(--font-display); + font-size: 2rem; + margin-top: 8px; +} + +.dest-detail-desc { + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 24px; +} + +/* ===== Nav User ===== */ +.nav-user { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: 100px; + font-size: 0.85rem; +} + +.nav-user-name { + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.nav-logout { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.75rem; + padding: 2px 4px; +} + +.nav-logout:hover { color: var(--accent-1); } + +.nav-login-btn { + padding: 8px 20px; + border-radius: 100px; + background: var(--gradient-main); + color: #0a0e17; + font-weight: 600; + font-size: 0.85rem; + transition: transform var(--transition); +} + +.nav-login-btn:hover { transform: scale(1.05); } + +.blog-card { + text-decoration: none; + color: inherit; + display: block; +} + +@media (max-width: 768px) { + .nav-user-name { display: none; } +} + +/* ===== Profile Page ===== */ +.profile-page { + max-width: 900px; + margin: 0 auto; + padding: calc(var(--nav-height) + 40px) 24px 80px; + position: relative; + z-index: 1; +} + +.profile-header { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 40px; + flex-wrap: wrap; +} + +.profile-avatar { + font-size: 4rem; + width: 80px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-glass); + border-radius: 50%; + border: var(--border-glass); +} + +.profile-info { flex: 1; } +.profile-info h1 { + font-family: var(--font-display); + font-size: 1.75rem; + margin-bottom: 4px; +} +.profile-info p { color: var(--text-muted); font-size: 0.9rem; } + +.profile-level { + display: inline-block; + margin-top: 8px; + padding: 4px 14px; + border-radius: 100px; + background: rgba(78, 205, 196, 0.15); + color: var(--accent-3); + font-size: 0.8rem; + font-weight: 600; +} + +.profile-stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; + margin-bottom: 48px; +} + +.profile-stat-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 24px 16px; + text-align: center; + backdrop-filter: blur(20px); +} + +.profile-stat-card strong { + display: block; + font-size: 1.75rem; + font-family: var(--font-display); + margin: 8px 0 4px; +} + +.profile-stat-card span:last-child { + font-size: 0.75rem; + color: var(--text-muted); +} + +.profile-link { + color: var(--accent-3); + font-size: 0.85rem; + font-weight: 600; +} + +.profile-favorites h2, +.profile-quick h2 { + font-family: var(--font-display); + font-size: 1.25rem; + margin-bottom: 20px; +} + +.profile-fav-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + margin-bottom: 48px; +} + +.profile-fav-card { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 20px; + display: flex; + justify-content: space-between; + align-items: center; + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.profile-fav-card:hover { + border-color: rgba(78, 205, 196, 0.3); + transform: translateY(-2px); +} + +.profile-fav-card a { text-decoration: none; color: inherit; } +.fav-emoji { font-size: 2rem; display: block; margin-bottom: 8px; } +.profile-fav-card h3 { font-size: 1rem; margin-bottom: 4px; } +.profile-fav-card p { font-size: 0.8rem; color: var(--text-muted); } + +.profile-empty { + text-align: center; + padding: 60px 20px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + margin-bottom: 48px; +} + +.profile-empty p { color: var(--text-muted); margin: 12px 0 20px; } +.profile-loading { color: var(--text-muted); padding: 40px; text-align: center; } + +.profile-quick-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.quick-card { + padding: 20px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-md); + text-align: center; + font-size: 0.9rem; + font-weight: 500; + transition: all var(--transition); + text-decoration: none; + color: inherit; +} + +.quick-card:hover { + background: var(--accent-3); + color: #0a0e17; + transform: translateY(-2px); +} + +/* ===== Not Found & Loading ===== */ +.not-found-page, +.page-loading { + min-height: 80vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + position: relative; + z-index: 1; + gap: 16px; +} + +.not-found-emoji { font-size: 5rem; } +.not-found-page h1 { + font-family: var(--font-display); + font-size: 4rem; + background: var(--gradient-text); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.not-found-page p { color: var(--text-muted); } + +.page-loading-spinner { + width: 48px; + height: 48px; + border: 3px solid var(--bg-glass); + border-top-color: var(--accent-3); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { to { transform: rotate(360deg); } } +.page-loading p { color: var(--text-muted); } + +@media (max-width: 768px) { + .profile-stats-grid { grid-template-columns: repeat(2, 1fr); } + .profile-fav-grid { grid-template-columns: 1fr; } + .profile-quick-grid { grid-template-columns: repeat(2, 1fr); } +} + +/* ===== Global Search ===== */ +.search-trigger { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); + color: var(--text-muted); + font-size: 0.8rem; + cursor: pointer; + transition: all var(--transition); + position: fixed; + top: calc(var(--nav-height) + 44px); + right: 24px; + z-index: 998; + backdrop-filter: blur(10px); +} + +.search-trigger:hover { + border-color: var(--accent-3); + color: var(--text-primary); +} + +.search-trigger kbd { + padding: 2px 6px; + border-radius: 4px; + background: var(--bg-glass); + border: var(--border-glass); + font-size: 0.7rem; + font-family: inherit; +} + +.search-overlay { + position: fixed; + inset: 0; + z-index: 5000; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(8px); + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 15vh; +} + +.search-modal { + width: 100%; + max-width: 560px; + background: var(--bg-secondary); + border: var(--border-glass); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-card); + animation: searchIn 0.2s ease; +} + +@keyframes searchIn { + from { opacity: 0; transform: scale(0.95) translateY(-10px); } + to { opacity: 1; transform: scale(1) translateY(0); } +} + +.search-input-wrap { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + border-bottom: var(--border-glass); +} + +.search-input-wrap input { + flex: 1; + background: none; + border: none; + outline: none; + color: var(--text-primary); + font-size: 1rem; + font-family: inherit; +} + +.search-input-wrap button { + padding: 4px 8px; + border-radius: 4px; + background: var(--bg-glass); + border: var(--border-glass); + color: var(--text-muted); + font-size: 0.7rem; + cursor: pointer; +} + +.search-results { + max-height: 360px; + overflow-y: auto; + padding: 8px; +} + +.search-result { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + padding: 12px 16px; + border: none; + background: none; + border-radius: var(--radius-md); + cursor: pointer; + text-align: left; + color: inherit; + font-family: inherit; + transition: background var(--transition); +} + +.search-result:hover, +.search-result.active { + background: var(--bg-glass); +} + +.search-result-emoji { font-size: 1.5rem; flex-shrink: 0; } + +.search-result-text { + flex: 1; + min-width: 0; +} + +.search-result-text strong { + display: block; + font-size: 0.9rem; + margin-bottom: 2px; +} + +.search-result-text span { + font-size: 0.75rem; + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} + +.search-result-type { + font-size: 0.7rem; + padding: 3px 8px; + border-radius: 100px; + background: rgba(78, 205, 196, 0.15); + color: var(--accent-3); + flex-shrink: 0; +} + +.search-empty { + padding: 32px; + text-align: center; + color: var(--text-muted); + font-size: 0.9rem; +} + +.search-hints { + padding: 24px; + text-align: center; + color: var(--text-muted); + font-size: 0.8rem; + line-height: 2; +} + +/* ===== Trip Planner ===== */ +.trip-section { + background: linear-gradient(180deg, transparent, rgba(255, 107, 107, 0.02), transparent); +} + +.trip-wrapper { + display: grid; + grid-template-columns: 320px 1fr; + gap: 32px; + align-items: start; +} + +.trip-form { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + padding: 28px; + backdrop-filter: blur(20px); + position: sticky; + top: calc(var(--nav-height) + 20px); +} + +.trip-form h3 { + font-family: var(--font-display); + margin-bottom: 20px; +} + +.trip-timeline { + min-height: 300px; +} + +.trip-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 300px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + color: var(--text-muted); + gap: 12px; +} + +.trip-route { + display: flex; + flex-direction: column; + gap: 0; + margin-bottom: 24px; +} + +.trip-stop { + display: flex; + align-items: flex-start; + gap: 16px; + position: relative; +} + +.trip-stop-dot { + font-size: 1.5rem; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: 50%; + flex-shrink: 0; + z-index: 1; +} + +.trip-stop-line { + position: absolute; + left: 23px; + top: 48px; + width: 2px; + height: calc(100% - 16px); + background: linear-gradient(to bottom, var(--accent-3), var(--accent-1)); + opacity: 0.3; +} + +.trip-stop-card { + flex: 1; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 16px 20px; + margin-bottom: 16px; + backdrop-filter: blur(20px); + transition: all var(--transition); +} + +.trip-stop-card:hover { + border-color: rgba(78, 205, 196, 0.3); +} + +.trip-stop-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.trip-remove { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.8rem; + padding: 4px; +} + +.trip-remove:hover { color: var(--accent-1); } + +.trip-stop-controls { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; + color: var(--text-muted); +} + +.trip-stop-controls input { + width: 50px; + padding: 4px 8px; + border-radius: var(--radius-sm); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + text-align: center; +} + +.trip-stop-cost { + margin-left: auto; + font-weight: 700; + color: var(--accent-3); +} + +.trip-summary { + display: flex; + align-items: center; + gap: 24px; + padding: 20px 24px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + flex-wrap: wrap; +} + +.trip-summary-stat { + text-align: center; +} + +.trip-summary-stat span { + display: block; + font-size: 0.75rem; + color: var(--text-muted); + margin-bottom: 4px; +} + +.trip-summary-stat strong { + font-family: var(--font-display); + font-size: 1.25rem; +} + +@media (max-width: 1024px) { + .trip-wrapper { grid-template-columns: 1fr; } + .trip-form { position: static; } + .search-trigger { top: auto; bottom: 80px; right: 16px; } +} + +@media (max-width: 768px) { + .search-trigger span, + .search-trigger kbd { display: none; } +} + +/* ===== Scroll Progress ===== */ +.scroll-progress { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 3px; + z-index: 9999; + background: rgba(255, 255, 255, 0.04); +} + +.scroll-progress-bar { + height: 100%; + background: linear-gradient(90deg, #FF6B6B, #FFE66D, #4ECDC4); + transition: width 0.1s linear; + box-shadow: 0 0 12px rgba(78, 205, 196, 0.5); +} + +/* ===== Toast Stack ===== */ +.toast-stack { + position: fixed; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + z-index: 10000; + display: flex; + flex-direction: column; + gap: 10px; + pointer-events: none; +} + +.toast-stack .toast { + position: relative; + bottom: auto; + left: auto; + transform: translateY(20px); + opacity: 0; + animation: toastIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + +.toast-stack .toast.show { opacity: 1; transform: translateY(0); } + +@keyframes toastIn { + from { opacity: 0; transform: translateY(20px) scale(0.95); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +.toast-info { border-color: rgba(78, 205, 196, 0.3); } +.toast-error { border-color: rgba(255, 107, 107, 0.3); } + +/* ===== Destination Matcher ===== */ +.matcher-fab { + position: fixed; + bottom: 30px; + left: 30px; + z-index: 1400; + display: flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, rgba(255, 107, 107, 0.9), rgba(78, 205, 196, 0.9)); + border: none; + border-radius: 50px; + color: #fff; + font-family: inherit; + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + box-shadow: 0 8px 32px rgba(255, 107, 107, 0.3); + transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + backdrop-filter: blur(10px); +} + +.matcher-fab:hover { + transform: translateY(-3px) scale(1.03); + box-shadow: 0 12px 40px rgba(255, 107, 107, 0.4); +} + +.matcher-fab-icon { font-size: 1.2rem; } + +.matcher-modal { + max-width: 560px; + width: 95%; + overflow: hidden; + padding: 0; +} + +.matcher-progress { + height: 4px; + background: rgba(255, 255, 255, 0.06); +} + +.matcher-progress-bar { + height: 100%; + background: linear-gradient(90deg, #FF6B6B, #FFE66D, #4ECDC4); + transition: width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.matcher-body { + padding: 36px 32px 28px; + min-height: 420px; +} + +.matcher-step-meta { + text-align: center; + margin-bottom: 28px; +} + +.matcher-step-meta h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin: 12px 0 8px; +} + +.matcher-step-meta p { + color: var(--text-muted); + font-size: 0.9rem; +} + +.matcher-options { + display: flex; + flex-direction: column; + gap: 10px; +} + +.matcher-option { + display: flex; + align-items: center; + gap: 16px; + padding: 16px 20px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all 0.25s ease; + font-family: inherit; + color: var(--text-primary); + text-align: left; +} + +.matcher-option:hover { + border-color: rgba(78, 205, 196, 0.4); + background: rgba(78, 205, 196, 0.08); + transform: translateX(6px); +} + +.matcher-option-emoji { font-size: 1.8rem; flex-shrink: 0; } + +.matcher-option strong { + display: block; + font-size: 1rem; + margin-bottom: 2px; +} + +.matcher-option span { + font-size: 0.8rem; + color: var(--text-muted); +} + +.matcher-option-arrow { + margin-left: auto; + opacity: 0.3; + transition: all 0.2s; + flex-shrink: 0; +} + +.matcher-option:hover .matcher-option-arrow { + opacity: 1; + transform: translateX(4px); +} + +.matcher-back { + margin-top: 20px; + background: none; + border: none; + color: var(--text-muted); + font-family: inherit; + cursor: pointer; + font-size: 0.85rem; + padding: 8px 0; +} + +.matcher-back:hover { color: var(--text-primary); } + +.matcher-step, +.matcher-results { + animation: matcherSlide 0.35s ease forwards; +} + +.matcher-forward { animation-name: matcherSlide; } +.matcher-back { animation-name: matcherSlideBack; } + +@keyframes matcherSlide { + from { opacity: 0; transform: translateX(30px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes matcherSlideBack { + from { opacity: 0; transform: translateX(-30px); } + to { opacity: 1; transform: translateX(0); } +} + +.matcher-result-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 20px; +} + +.matcher-result-card { + display: grid; + grid-template-columns: auto auto 1fr auto; + grid-template-rows: auto auto; + gap: 4px 12px; + padding: 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); + text-decoration: none; + color: inherit; + transition: all 0.25s ease; + align-items: center; +} + +.matcher-result-card:hover { + border-color: rgba(78, 205, 196, 0.4); + transform: translateY(-2px); + box-shadow: var(--shadow-card); +} + +.matcher-result-card.top { + border-color: rgba(255, 230, 109, 0.4); + background: linear-gradient(135deg, rgba(255, 107, 107, 0.06), rgba(78, 205, 196, 0.06)); +} + +.matcher-result-rank { font-size: 1.2rem; grid-row: span 2; } +.matcher-result-emoji { font-size: 2rem; grid-row: span 2; } + +.matcher-result-info strong { + display: block; + font-size: 1rem; +} + +.matcher-result-info span { + font-size: 0.75rem; + color: var(--text-muted); +} + +.matcher-result-score { + grid-column: 4; + grid-row: 1; + text-align: right; + font-size: 0.75rem; + color: var(--accent-3); + font-weight: 600; +} + +.matcher-score-bar { + width: 80px; + height: 4px; + background: rgba(255, 255, 255, 0.08); + border-radius: 2px; + margin-bottom: 4px; + overflow: hidden; +} + +.matcher-score-fill { + height: 100%; + background: linear-gradient(90deg, #FF6B6B, #4ECDC4); + border-radius: 2px; + transition: width 0.8s ease; +} + +.matcher-result-tags { + grid-column: 3 / -1; + display: flex; + gap: 12px; + font-size: 0.75rem; + color: var(--text-muted); +} + +.matcher-actions { + display: flex; + gap: 12px; + justify-content: center; +} + +.dest-matcher-btn { + margin-top: 16px; +} + +.hero-matcher-btn { + cursor: pointer; +} + +/* ===== Compare Modal Enhanced ===== */ +.compare-modal { + max-width: 900px; + width: 95%; +} + +.compare-modal-body { padding: 36px 32px; } + +.compare-modal-header { + text-align: center; + margin-bottom: 28px; +} + +.compare-modal-header h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin: 8px 0; +} + +.compare-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 16px; + margin-bottom: 32px; +} + +.compare-city-card { + text-align: center; + padding: 24px 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); + position: relative; + transition: all 0.3s ease; +} + +.compare-city-card.winner { + border-color: rgba(255, 230, 109, 0.5); + background: linear-gradient(180deg, rgba(255, 230, 109, 0.08), transparent); +} + +.compare-winner-badge { + position: absolute; + top: -10px; + left: 50%; + transform: translateX(-50%); + background: linear-gradient(135deg, #FF6B6B, #FFE66D); + color: #0a0e17; + font-size: 0.7rem; + font-weight: 700; + padding: 4px 12px; + border-radius: 20px; + white-space: nowrap; +} + +.compare-city-emoji { font-size: 2.5rem; display: block; margin-bottom: 8px; } + +.compare-city-card h3 { + font-family: var(--font-display); + font-size: 1.1rem; +} + +.compare-city-country { + font-size: 0.8rem; + color: var(--text-muted); +} + +.compare-score-ring { + position: relative; + width: 80px; + height: 80px; + margin: 16px auto; +} + +.compare-score-ring svg { width: 100%; height: 100%; } + +.compare-score-num { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-display); + font-weight: 700; + font-size: 1.2rem; +} + +.compare-city-link { + font-size: 0.8rem; + color: var(--accent-3); + text-decoration: none; +} + +.compare-city-link:hover { text-decoration: underline; } + +.compare-metrics { display: flex; flex-direction: column; gap: 20px; } + +.compare-metric-row { + display: grid; + grid-template-columns: 100px 1fr; + gap: 16px; + align-items: start; +} + +.compare-metric-label { + font-size: 0.85rem; + color: var(--text-muted); + padding-top: 4px; +} + +.compare-metric-bars { display: flex; flex-direction: column; gap: 8px; } + +.compare-bar-item { flex: 1; } + +.compare-bar-header { + display: flex; + justify-content: space-between; + font-size: 0.8rem; + margin-bottom: 4px; +} + +.compare-best { color: var(--accent-3); font-weight: 600; } + +.compare-bar-track { + height: 8px; + background: rgba(255, 255, 255, 0.06); + border-radius: 4px; + overflow: hidden; +} + +.compare-bar-fill { + height: 100%; + background: rgba(255, 255, 255, 0.15); + border-radius: 4px; + transition: width 0.8s cubic-bezier(0.34, 1.56, 0.64, 1); + animation: barGrow 0.8s ease forwards; +} + +.compare-bar-fill.best { + background: linear-gradient(90deg, #FF6B6B, #4ECDC4); +} + +@keyframes barGrow { + from { width: 0 !important; } +} + +/* ===== Timezone Board ===== */ +.timezone-section { + background: linear-gradient(180deg, transparent, rgba(78, 205, 196, 0.03), transparent); +} + +.tz-home-clock { + display: flex; + align-items: center; + justify-content: space-between; + gap: 32px; + padding: 28px 32px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); + margin-bottom: 28px; + flex-wrap: wrap; +} + +.tz-home-time { display: flex; align-items: center; gap: 16px; } + +.tz-live-dot { + width: 10px; + height: 10px; + background: #4ECDC4; + border-radius: 50%; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(78, 205, 196, 0.4); } + 50% { opacity: 0.8; box-shadow: 0 0 0 8px rgba(78, 205, 196, 0); } +} + +.tz-home-label { font-size: 0.9rem; color: var(--text-muted); } + +.tz-home-digits { + font-family: var(--font-display); + font-size: 2rem; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.tz-seconds { + font-size: 1rem; + color: var(--text-muted); + font-weight: 400; +} + +.tz-work-slider { + flex: 1; + min-width: 240px; +} + +.tz-work-slider label { + display: block; + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 12px; +} + +.tz-slider-row { + display: flex; + align-items: center; + gap: 12px; + font-size: 0.8rem; + color: var(--text-muted); + margin-bottom: 8px; +} + +.tz-slider-row input[type="range"] { + flex: 1; + accent-color: var(--accent-3); +} + +.tz-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 16px; +} + +.tz-card { + padding: 20px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + transition: all 0.3s ease; +} + +.tz-card:hover { + border-color: rgba(78, 205, 196, 0.3); + transform: translateY(-3px); +} + +.tz-card.working { + border-color: rgba(78, 205, 196, 0.4); + box-shadow: 0 0 20px rgba(78, 205, 196, 0.1); +} + +.tz-card-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 16px; +} + +.tz-card-emoji { font-size: 1.5rem; } + +.tz-card-header strong { + display: block; + font-size: 0.95rem; +} + +.tz-card-header span { + font-size: 0.7rem; + color: var(--text-muted); +} + +.tz-overlap-badge { + margin-left: auto; + font-size: 0.7rem; + font-weight: 600; + padding: 3px 10px; + border-radius: 20px; +} + +.tz-overlap-badge.excellent { background: rgba(78, 205, 196, 0.2); color: #4ECDC4; } +.tz-overlap-badge.good { background: rgba(255, 230, 109, 0.2); color: #FFE66D; } +.tz-overlap-badge.fair { background: rgba(255, 165, 0, 0.2); color: #ffa500; } +.tz-overlap-badge.poor { background: rgba(255, 107, 107, 0.2); color: #FF6B6B; } + +.tz-analog { + position: relative; + width: 100px; + height: 100px; + margin: 0 auto 12px; +} + +.tz-analog svg { width: 100%; height: 100%; } + +.tz-digital { + position: absolute; + bottom: -4px; + left: 50%; + transform: translateX(-50%); + font-family: var(--font-display); + font-size: 0.85rem; + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.tz-overlap-bar { + display: flex; + align-items: center; + gap: 10px; + font-size: 0.75rem; + color: var(--text-muted); + margin-bottom: 8px; +} + +.tz-overlap-track { + flex: 1; + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} + +.tz-overlap-fill { + height: 100%; + border-radius: 3px; + transition: width 0.6s ease; +} + +.tz-overlap-fill.excellent { background: #4ECDC4; } +.tz-overlap-fill.good { background: #FFE66D; } +.tz-overlap-fill.fair { background: #ffa500; } +.tz-overlap-fill.poor { background: #FF6B6B; } + +.tz-status { + text-align: center; + font-size: 0.75rem; + color: var(--text-muted); +} + +/* ===== Keyboard Shortcuts ===== */ +.shortcuts-modal { + max-width: 480px; + width: 95%; +} + +.shortcuts-body { + padding: 36px 32px; + text-align: center; +} + +.shortcuts-body h2 { + font-family: var(--font-display); + font-size: 1.5rem; + margin: 8px 0; +} + +.shortcuts-body p { + color: var(--text-muted); + font-size: 0.9rem; + margin-bottom: 24px; +} + +.shortcuts-grid { + display: flex; + flex-direction: column; + gap: 8px; + text-align: left; +} + +.shortcut-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); +} + +.shortcut-icon { font-size: 1.2rem; } +.shortcut-label { flex: 1; font-size: 0.9rem; } + +.shortcut-keys { + display: flex; + gap: 4px; +} + +.shortcut-keys kbd, +.shortcuts-body kbd { + display: inline-block; + padding: 3px 8px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 6px; + font-family: inherit; + font-size: 0.75rem; + color: var(--text-secondary); +} + +@media (max-width: 768px) { + .matcher-fab .matcher-fab-text { display: none; } + .matcher-fab { padding: 14px; border-radius: 50%; bottom: 90px; left: 16px; } + .compare-metric-row { grid-template-columns: 1fr; } + .tz-home-clock { flex-direction: column; text-align: center; } + .matcher-result-card { grid-template-columns: auto 1fr; } + .matcher-result-score { grid-column: 2; grid-row: 2; } + .matcher-result-tags { grid-column: 1 / -1; } +} + +/* ===== Visa Filters Enhancement ===== */ +.visa-filters { + display: flex; + gap: 8px; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 28px; +} + +.visa-card { + transition: all 0.3s ease; +} + +.visa-card:hover { + transform: translateY(-4px); + border-color: rgba(255, 230, 109, 0.3); +} + +.tool-card { + transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.tool-card:hover { + transform: translateY(-6px); + border-color: rgba(78, 205, 196, 0.3); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2); +} + +/* ===== Section Nav ===== */ +.section-nav { + position: fixed; + right: 20px; + top: 50%; + transform: translateY(-50%) translateX(60px); + z-index: 1300; + display: flex; + flex-direction: column; + gap: 6px; + opacity: 0; + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + pointer-events: none; +} + +.section-nav.visible { + opacity: 1; + transform: translateY(-50%) translateX(0); + pointer-events: auto; +} + +.section-nav-dot { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px 8px 10px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: 50px; + cursor: pointer; + transition: all 0.25s ease; + font-family: inherit; + color: var(--text-muted); + backdrop-filter: blur(16px); + justify-content: flex-end; +} + +.section-nav-dot:hover, +.section-nav-dot.active { + border-color: rgba(78, 205, 196, 0.4); + color: var(--text-primary); + background: rgba(78, 205, 196, 0.1); + transform: translateX(-4px); +} + +.section-nav-dot.active { + box-shadow: 0 0 16px rgba(78, 205, 196, 0.2); +} + +.section-nav-emoji { font-size: 1rem; } + +.section-nav-label { + font-size: 0.75rem; + font-weight: 500; + max-width: 0; + overflow: hidden; + white-space: nowrap; + transition: max-width 0.3s ease; +} + +.section-nav-dot:hover .section-nav-label, +.section-nav-dot.active .section-nav-label { + max-width: 60px; +} + +/* ===== Currency Converter ===== */ +.currency-section { + background: linear-gradient(180deg, transparent, rgba(255, 230, 109, 0.02), transparent); +} + +.currency-wrapper { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 24px; + align-items: start; +} + +.currency-card { + padding: 32px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); +} + +.currency-input-group label, +.currency-result label, +.currency-to-select label { + display: block; + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 10px; +} + +.currency-input-row { + display: flex; + gap: 12px; +} + +.currency-input-row input { + flex: 1; + padding: 14px 18px; + font-size: 1.5rem; + font-family: var(--font-display); + font-weight: 700; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); +} + +.currency-input-row select { + padding: 12px 16px; + border-radius: var(--radius-md); + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-primary); + font-family: inherit; + cursor: pointer; +} + +.currency-swap { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + margin: 20px auto; + border-radius: 50%; + border: var(--border-glass); + background: var(--bg-glass); + color: var(--accent-3); + cursor: pointer; + transition: all 0.3s ease; +} + +.currency-swap:hover { + background: rgba(78, 205, 196, 0.15); + transform: rotate(180deg); +} + +.currency-swap.swapping { + transform: rotate(360deg); +} + +.currency-result { + text-align: center; + padding: 24px; + background: rgba(78, 205, 196, 0.06); + border-radius: var(--radius-lg); + margin-bottom: 20px; +} + +.currency-result-display { + display: flex; + align-items: baseline; + justify-content: center; + gap: 10px; + margin: 12px 0 8px; +} + +.currency-result-flag { font-size: 2rem; } + +.currency-result-amount { + font-family: var(--font-display); + font-size: 2.5rem; + font-weight: 800; + background: linear-gradient(135deg, #FF6B6B, #4ECDC4); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.currency-result-code { + font-size: 1rem; + color: var(--text-muted); + font-weight: 600; +} + +.currency-rate-hint { + font-size: 0.8rem; + color: var(--text-muted); +} + +.currency-chips { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.currency-chip { + padding: 8px 14px; + border-radius: 50px; + border: var(--border-glass); + background: var(--bg-glass); + color: var(--text-secondary); + font-family: inherit; + font-size: 0.8rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.currency-chip:hover, +.currency-chip.active { + border-color: rgba(78, 205, 196, 0.4); + background: rgba(78, 205, 196, 0.12); + color: var(--accent-3); +} + +.currency-presets { + padding: 28px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); +} + +.currency-presets h3 { + font-family: var(--font-display); + margin-bottom: 4px; +} + +.currency-presets-desc { + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 20px; +} + +.currency-preset-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 20px; +} + +.currency-preset-card { + padding: 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); + cursor: pointer; + text-align: left; + font-family: inherit; + color: inherit; + transition: all 0.25s ease; +} + +.currency-preset-card:hover { + border-color: rgba(255, 230, 109, 0.4); + transform: translateY(-2px); +} + +.preset-emoji { + font-size: 1.5rem; + display: block; + margin-bottom: 8px; +} + +.currency-preset-card strong { + display: block; + font-size: 0.9rem; + margin-bottom: 4px; +} + +.currency-preset-card span:last-child { + font-size: 0.75rem; + color: var(--text-muted); +} + +.currency-tip { + display: flex; + gap: 10px; + padding: 14px; + background: rgba(255, 230, 109, 0.06); + border-radius: var(--radius-md); + font-size: 0.8rem; + color: var(--text-muted); +} + +/* ===== Nomad Score ===== */ +.nomad-score-section { + background: linear-gradient(180deg, transparent, rgba(167, 139, 250, 0.03), transparent); +} + +.nomad-score-card { + max-width: 560px; + margin: 0 auto; + padding: 36px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); +} + +.nomad-quiz-progress { + display: flex; + gap: 8px; + justify-content: center; + margin-bottom: 20px; +} + +.nomad-quiz-dot { + width: 40px; + height: 4px; + border-radius: 2px; + background: rgba(255, 255, 255, 0.08); + transition: all 0.3s ease; +} + +.nomad-quiz-dot.filled { background: var(--accent-3); } +.nomad-quiz-dot.current { box-shadow: 0 0 8px rgba(78, 205, 196, 0.5); } + +.nomad-quiz-step { + display: block; + text-align: center; + font-size: 0.8rem; + color: var(--text-muted); + margin-bottom: 12px; +} + +.nomad-quiz h3 { + text-align: center; + font-family: var(--font-display); + font-size: 1.25rem; + margin-bottom: 24px; +} + +.nomad-quiz-options { + display: flex; + flex-direction: column; + gap: 10px; +} + +.nomad-quiz-option { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 20px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); + cursor: pointer; + font-family: inherit; + color: var(--text-primary); + text-align: left; + transition: all 0.25s ease; +} + +.nomad-quiz-option:hover { + border-color: rgba(167, 139, 250, 0.4); + background: rgba(167, 139, 250, 0.08); + transform: translateX(6px); +} + +.nomad-quiz-option span:first-child { font-size: 1.5rem; } + +.nomad-quiz.animating { + opacity: 0; + transform: translateX(-20px); + transition: all 0.3s ease; +} + +.nomad-result { + text-align: center; +} + +.nomad-score-ring { + position: relative; + width: 140px; + height: 140px; + margin: 0 auto 20px; +} + +.nomad-score-ring svg { width: 100%; height: 100%; } + +.nomad-score-arc { + transition: stroke-dasharray 1s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.nomad-score-center { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.nomad-score-center strong { + font-family: var(--font-display); + font-size: 2.5rem; + font-weight: 800; + color: var(--score-color, var(--accent-3)); +} + +.nomad-score-center span { + font-size: 0.75rem; + color: var(--text-muted); +} + +.nomad-level-label { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: 8px; +} + +.nomad-level-desc { + color: var(--text-muted); + font-size: 0.9rem; + margin-bottom: 24px; + max-width: 360px; + margin-left: auto; + margin-right: auto; +} + +.nomad-result-actions { + display: flex; + gap: 12px; + justify-content: center; +} + +/* ===== Lifestyle Interactive ===== */ +.lifestyle-layout { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 40px; + align-items: start; +} + +.timeline-item { + cursor: pointer; + transition: all 0.3s ease; +} + +.timeline-item.active .timeline-card { + border-color: rgba(78, 205, 196, 0.5); + background: rgba(78, 205, 196, 0.08); + transform: scale(1.02); + box-shadow: 0 8px 32px rgba(78, 205, 196, 0.15); +} + +.timeline-item.active .timeline-time { + color: var(--accent-3); + font-weight: 700; +} + +.lifestyle-detail-card { + position: sticky; + top: 100px; + padding: 32px; + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-xl); + backdrop-filter: blur(20px); + text-align: center; +} + +.lifestyle-detail-icon { + font-size: 3rem; + display: block; + margin-bottom: 12px; +} + +.lifestyle-detail-time { + font-size: 0.85rem; + color: var(--text-muted); +} + +.lifestyle-detail-card h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin: 12px 0; +} + +.lifestyle-detail-card p { + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 20px; +} + +.lifestyle-tip { + display: flex; + gap: 10px; + padding: 16px; + background: rgba(255, 230, 109, 0.08); + border-radius: var(--radius-md); + text-align: left; + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 20px; +} + +.lifestyle-dots { + display: flex; + gap: 8px; + justify-content: center; +} + +.lifestyle-dot { + width: 10px; + height: 10px; + border-radius: 50%; + border: none; + background: rgba(255, 255, 255, 0.15); + cursor: pointer; + transition: all 0.2s ease; + padding: 0; +} + +.lifestyle-dot.active { + background: var(--accent-3); + transform: scale(1.3); +} + +/* ===== Destination Detail Enhanced ===== */ +.dest-detail-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 24px 0; +} + +.dest-detail-extras { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 20px; + margin: 28px 0; +} + +.dest-radar-card { + padding: 24px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-lg); +} + +.dest-radar-card h3 { + font-size: 1rem; + margin-bottom: 16px; +} + +.dest-radar-row { + display: grid; + grid-template-columns: 80px 1fr 36px; + gap: 10px; + align-items: center; + margin-bottom: 10px; +} + +.dest-radar-label { + font-size: 0.8rem; + color: var(--text-muted); +} + +.dest-radar-track { + height: 8px; + background: rgba(255, 255, 255, 0.06); + border-radius: 4px; + overflow: hidden; +} + +.dest-radar-fill { + height: 100%; + background: linear-gradient(90deg, #FF6B6B, #4ECDC4); + border-radius: 4px; + transition: width 0.8s ease; +} + +.dest-radar-val { + font-size: 0.8rem; + font-weight: 600; + text-align: right; + color: var(--accent-3); +} + +.dest-info-cards { + display: flex; + flex-direction: column; + gap: 10px; +} + +.dest-info-mini { + display: flex; + align-items: center; + gap: 14px; + padding: 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); +} + +.dest-info-mini span:first-child { font-size: 1.5rem; } + +.dest-info-mini strong { + display: block; + font-size: 0.85rem; +} + +.dest-info-mini span:last-child { + font-size: 0.75rem; + color: var(--text-muted); +} + +.dest-related { + margin-top: 32px; + padding-top: 28px; + border-top: 1px solid rgba(255, 255, 255, 0.06); +} + +.dest-related h3 { + margin-bottom: 16px; +} + +.dest-related-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; +} + +.dest-related-card { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + background: var(--bg-glass); + border: var(--border-glass); + border-radius: var(--radius-md); + text-decoration: none; + color: inherit; + transition: all 0.25s ease; +} + +.dest-related-card:hover { + border-color: rgba(78, 205, 196, 0.4); + transform: translateY(-2px); +} + +.dest-related-card span:first-child { font-size: 1.8rem; } + +.dest-related-card strong { + display: block; + font-size: 0.9rem; +} + +.dest-related-card span:last-child { + font-size: 0.75rem; + color: var(--text-muted); +} + +/* ===== Blog Reading Progress ===== */ +.blog-reading-progress { + position: fixed; + top: 3px; + left: 0; + right: 0; + height: 3px; + z-index: 9998; + background: rgba(255, 255, 255, 0.04); +} + +.blog-reading-bar { + height: 100%; + background: linear-gradient(90deg, #A78BFA, #4ECDC4); + transition: width 0.1s linear; +} + +.blog-reading-pct { + position: fixed; + top: 12px; + right: 20px; + z-index: 9998; + font-size: 0.7rem; + font-weight: 600; + color: var(--accent-3); + background: var(--bg-card); + padding: 4px 10px; + border-radius: 20px; + border: var(--border-glass); +} + +/* ===== Map Actions ===== */ +.map-info-actions { + display: flex; + gap: 10px; + margin-top: 16px; +} + +.btn-sm { + padding: 8px 16px !important; + font-size: 0.8rem !important; +} + +/* ===== Profile Trip ===== */ +.profile-trip { + margin: 40px 0; +} + +.profile-trip h2 { + font-family: var(--font-display); + margin-bottom: 20px; +} + +.profile-trip-list { + background: var(--bg-card); + border: var(--border-glass); + border-radius: var(--radius-lg); + padding: 20px; +} + +.profile-trip-item { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.profile-trip-item:last-of-type { border-bottom: none; } + +.trip-item-num { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(78, 205, 196, 0.15); + border-radius: 50%; + font-size: 0.75rem; + font-weight: 700; + color: var(--accent-3); +} + +.profile-trip-total { + padding: 16px 0 12px; + font-size: 0.9rem; + color: var(--text-muted); + text-align: center; +} + +@media (max-width: 1024px) { + .currency-wrapper { grid-template-columns: 1fr; } + .lifestyle-layout { grid-template-columns: 1fr; } + .lifestyle-detail-card { position: static; } + .dest-detail-extras { grid-template-columns: 1fr; } + .section-nav { display: none; } +} + +@media (max-width: 768px) { + .currency-preset-grid { grid-template-columns: 1fr; } + .currency-result-amount { font-size: 1.8rem; } +} + diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..d44b373 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import "./globals.css"; +import { AuthProvider } from "@/lib/auth"; +import { ToastProvider } from "@/lib/toast"; + +export const metadata: Metadata = { + title: "NomadFlow · 数字游民旅居指南", + description: "探索全球旅居生活,发现最适合数字游民的目的地、工具与社区。", + keywords: ["数字游民", "旅居", "远程工作", "nomad", "digital nomad"], + openGraph: { + title: "NomadFlow · 数字游民旅居指南", + description: "用一行代码环游世界 🌏", + type: "website", + locale: "zh_CN", + }, +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + ); +} diff --git a/frontend/src/app/loading.tsx b/frontend/src/app/loading.tsx new file mode 100644 index 0000000..68df03f --- /dev/null +++ b/frontend/src/app/loading.tsx @@ -0,0 +1,8 @@ +export default function Loading() { + return ( +
    +
    +

    加载中... 🌍

    +
    + ); +} diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx new file mode 100644 index 0000000..bf3283c --- /dev/null +++ b/frontend/src/app/login/page.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useState } from "react"; +import { useAuth } from "@/lib/auth"; +import Link from "next/link"; +import SiteShell from "@/components/SiteShell"; + +export default function LoginPage() { + const { login, register, demoLogin } = useAuth(); + const [mode, setMode] = useState<"login" | "register">("login"); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [name, setName] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + setLoading(true); + const ok = mode === "login" + ? await login(email, password) + : await register(email, password, name); + setLoading(false); + if (ok) window.location.href = "/"; + else setError(mode === "login" ? "邮箱或密码错误" : "注册失败,邮箱可能已存在"); + }; + + const handleDemo = async () => { + setLoading(true); + const ok = await demoLogin(); + setLoading(false); + if (ok) window.location.href = "/"; + }; + + return ( + +
    +
    + ← 返回首页 +
    + 🌍 +

    {mode === "login" ? "欢迎回来" : "加入 NomadFlow"}

    +

    {mode === "login" ? "登录你的游民账户" : "开始你的旅居之旅"}

    +
    + +
    + + +
    + +
    + {mode === "register" && ( +
    + + setName(e.target.value)} placeholder="你的游民昵称" required /> +
    + )} +
    + + setEmail(e.target.value)} placeholder="you@example.com" required /> +
    +
    + + setPassword(e.target.value)} placeholder="至少 6 位" required minLength={6} /> +
    + {error &&

    {error}

    } + +
    + +
    或
    + +

    演示账号:demo@nomadflow.io / demo123

    +
    +
    +
    + ); +} diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx new file mode 100644 index 0000000..d087ae0 --- /dev/null +++ b/frontend/src/app/not-found.tsx @@ -0,0 +1,15 @@ +import Link from "next/link"; +import SiteShell from "@/components/SiteShell"; + +export default function NotFound() { + return ( + +
    + 🧭 +

    404

    +

    哎呀,这片海域没有标记的岛屿

    + 返回首页 🏠 +
    +
    + ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..11bc286 --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,60 @@ +import { api } from "@/lib/api"; +import HomeClient from "@/components/HomeClient"; + +export const revalidate = 60; + +export default async function Home() { + try { + const [stats, ticker, destinations, visas, faqs, testimonials, tools, blog, cost, speed, growth, radar] = + await Promise.all([ + api.getStats(), + api.getTicker(), + api.getDestinations(), + api.getVisas(), + api.getFaqs(), + api.getTestimonials(), + api.getTools(), + api.getBlog(), + api.getChartCost(), + api.getChartSpeed(), + api.getChartGrowth(), + api.getChartRadar(), + ]); + + return ( + + ); + } catch { + // Minimal fallback if API completely unavailable + const stats = await api.getStats(); + const ticker = await api.getTicker(); + return ( + + ); + } +} diff --git a/frontend/src/app/profile/page.tsx b/frontend/src/app/profile/page.tsx new file mode 100644 index 0000000..cc9285b --- /dev/null +++ b/frontend/src/app/profile/page.tsx @@ -0,0 +1,149 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/lib/auth"; +import { api } from "@/lib/api"; +import type { Destination, ProfileStats, TripItem } from "@/lib/types"; +import SiteShell from "@/components/SiteShell"; +import FavoriteButton from "@/components/FavoriteButton"; + +const TRIP_KEY = "nomadflow-trip"; + +export default function ProfilePage() { + const { user, token, favorites, logout } = useAuth(); + const router = useRouter(); + const [stats, setStats] = useState(null); + const [favoriteDests, setFavoriteDests] = useState([]); + const [trip, setTrip] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!token) { + router.push("/login"); + return; + } + setLoading(true); + Promise.all([ + api.getProfileStats(token), + favorites.length > 0 ? api.getFavoriteDestinations(token) : Promise.resolve([]), + ]).then(([s, f]) => { + setStats(s); + setFavoriteDests(f); + }).catch(() => {}).finally(() => setLoading(false)); + + const saved = localStorage.getItem(TRIP_KEY); + if (saved) setTrip(JSON.parse(saved)); + }, [token, favorites, router]); + + if (!user) return null; + + return ( + +
    +
    +
    {user.avatar}
    +
    +

    {user.name}

    +

    {user.email}

    + {stats && {stats.nomad_level}} +
    + +
    + + {stats && ( +
    +
    + ❤️ + {stats.favorites_count} + 收藏目的地 +
    +
    + 🗺️ + {stats.destinations_explored} + 探索城市 +
    +
    + 📅 + {stats.member_since} + 加入年份 +
    +
    + 🧮 + 费用计算器 → +
    +
    + )} + +
    +

    ❤️ 我的收藏

    + {loading ? ( +

    加载中...

    + ) : favoriteDests.length === 0 ? ( +
    + 🌍 +

    还没有收藏目的地

    + 去探索 → +
    + ) : ( +
    + {favoriteDests.map((d) => ( +
    + + {d.emoji} +

    {d.name}, {d.country}

    +

    💰 ¥{d.cost.toLocaleString()}/月 · ⭐ {d.rating}

    + + +
    + ))} +
    + )} +
    + +
    +

    🗓️ 我的行程

    + {trip.length === 0 ? ( +
    + 🗺️ +

    还没有规划行程

    + 去规划 → +
    + ) : ( +
    + {trip.map((t, i) => ( +
    + {i + 1} + {t.emoji} +
    + {t.name}, {t.country} + {t.months} 个月 · ¥{(t.cost * t.months).toLocaleString()} +
    +
    + ))} +
    + 总计 {trip.reduce((s, t) => s + t.months, 0)} 个月 · + ¥{trip.reduce((s, t) => s + t.cost * t.months, 0).toLocaleString()} +
    + 编辑行程 → +
    + )} +
    + +
    +

    🚀 快捷入口

    +
    + 🌍 浏览目的地 + 📋 签证指南 + 📝 阅读博客 + 🗓️ 行程规划 + 📊 就绪度测评 +
    +
    +
    +
    + ); +} diff --git a/frontend/src/app/robots.ts b/frontend/src/app/robots.ts new file mode 100644 index 0000000..8233163 --- /dev/null +++ b/frontend/src/app/robots.ts @@ -0,0 +1,8 @@ +import type { MetadataRoute } from "next"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { userAgent: "*", allow: "/" }, + sitemap: "https://nomadflow.io/sitemap.xml", + }; +} diff --git a/frontend/src/app/sitemap.ts b/frontend/src/app/sitemap.ts new file mode 100644 index 0000000..e4c32f7 --- /dev/null +++ b/frontend/src/app/sitemap.ts @@ -0,0 +1,27 @@ +import type { MetadataRoute } from "next"; + +const DESTINATIONS = ["bali", "lisbon", "chiangmai", "mexico", "barcelona", "tokyo"]; +const BLOGS = ["chiangmai-guide-2026", "portugal-d7-visa", "nomad-tax-basics"]; + +export default function sitemap(): MetadataRoute.Sitemap { + const base = "https://nomadflow.io"; + const now = new Date(); + + return [ + { url: base, lastModified: now, changeFrequency: "weekly", priority: 1 }, + { url: `${base}/login`, lastModified: now, changeFrequency: "monthly", priority: 0.5 }, + { url: `${base}/profile`, lastModified: now, changeFrequency: "monthly", priority: 0.5 }, + ...DESTINATIONS.map((slug) => ({ + url: `${base}/destinations/${slug}`, + lastModified: now, + changeFrequency: "weekly" as const, + priority: 0.8, + })), + ...BLOGS.map((slug) => ({ + url: `${base}/blog/${slug}`, + lastModified: now, + changeFrequency: "monthly" as const, + priority: 0.7, + })), + ]; +} diff --git a/frontend/src/components/BackToTop.tsx b/frontend/src/components/BackToTop.tsx new file mode 100644 index 0000000..b6d38c5 --- /dev/null +++ b/frontend/src/components/BackToTop.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export default function BackToTop() { + const [visible, setVisible] = useState(false); + + useEffect(() => { + const onScroll = () => setVisible(window.scrollY > 600); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + return ( + + ); +} diff --git a/frontend/src/components/BlogReadingProgress.tsx b/frontend/src/components/BlogReadingProgress.tsx new file mode 100644 index 0000000..0ca63a2 --- /dev/null +++ b/frontend/src/components/BlogReadingProgress.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export default function BlogReadingProgress() { + const [progress, setProgress] = useState(0); + + useEffect(() => { + const onScroll = () => { + const article = document.querySelector(".blog-detail-content"); + if (!article) return; + const rect = article.getBoundingClientRect(); + const articleTop = rect.top + window.scrollY; + const articleHeight = article.scrollHeight; + const scrolled = window.scrollY - articleTop + window.innerHeight * 0.3; + setProgress(Math.min(Math.max((scrolled / articleHeight) * 100, 0), 100)); + }; + window.addEventListener("scroll", onScroll, { passive: true }); + onScroll(); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + return ( +