diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000..553cc7ddb3
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,58 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ backend:
+ name: Backend (typecheck + tests)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: actions/setup-node@v5
+ with:
+ node-version: 24
+ cache: npm
+ cache-dependency-path: backend/package-lock.json
+
+ - name: Install
+ run: npm ci
+ working-directory: backend
+
+ - name: Typecheck
+ run: npx tsc --noEmit
+ working-directory: backend
+
+ - name: Test
+ run: npm test
+ working-directory: backend
+
+ frontend:
+ name: Frontend (typecheck + build)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: actions/setup-node@v5
+ with:
+ node-version: 24
+ cache: npm
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: Install
+ run: npm ci
+ working-directory: frontend
+
+ - name: Typecheck
+ run: npx tsc --noEmit
+ working-directory: frontend
+
+ - name: Build
+ run: npx vite build
+ working-directory: frontend
diff --git a/.github/workflows/refresh-beaches.yml b/.github/workflows/refresh-beaches.yml
new file mode 100644
index 0000000000..1de6150af5
--- /dev/null
+++ b/.github/workflows/refresh-beaches.yml
@@ -0,0 +1,38 @@
+name: Refresh beaches snapshot
+
+# The beach list changes only a few times a year, so a daily refresh of the
+# static snapshot (frontend/public/beaches.json) is plenty. Runs on a schedule
+# and on demand; commits only when the data or sitemap actually changed.
+
+on:
+ schedule:
+ - cron: "0 4 * * *" # daily at 04:00 UTC
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ refresh:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: actions/setup-node@v5
+ with:
+ node-version: 24
+
+ - name: Build snapshot
+ run: node scripts/build-beaches.mjs
+
+ - name: Commit if changed
+ run: |
+ if [[ -n "$(git status --porcelain frontend/public/beaches.json frontend/public/sitemap.xml)" ]]; then
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add frontend/public/beaches.json frontend/public/sitemap.xml
+ git commit -m "chore: refresh beaches snapshot and sitemap"
+ git push
+ else
+ echo "Snapshot and sitemap unchanged β nothing to commit."
+ fi
diff --git a/.gitignore b/.gitignore
index 3d70248ba2..54949570c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,9 +7,18 @@ node_modules
.env.production.local
build
+dist
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-package-lock.json
\ No newline at end of file
+package-lock.json
+!frontend/package-lock.json
+!backend/package-lock.json
+
+CLAUDE.md
+.claude/
+.skills/
+# Local Netlify folder
+.netlify
diff --git a/Procfile b/Procfile
deleted file mode 100644
index dc14c0b63a..0000000000
--- a/Procfile
+++ /dev/null
@@ -1 +0,0 @@
-web: npm start --prefix backend
\ No newline at end of file
diff --git a/README.md b/README.md
index 31466b54c2..ab893d00a2 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,241 @@
-# Final Project
+# ποΈ Badlistan β Koll pΓ₯ badvattnet i hela Sverige
-Replace this readme with your own information about your project.
+
+
+
+
+
+
+
+
+
+
-Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
+**Badlistan** helps beachgoers and families in Sweden find safe, EU-classified bathing waters with real-time quality updates.
+It replaces outdated or clunky websites with a **clean, mobile-friendly experience** where you can browse nearby beaches on a map, check water quality, and save your favourites.
-## The problem
+[Try it out here](https://badaweb.netlify.app/) (deployed on Netlify)
-Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
+---
-## View it live
+## β¨ Features
-Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
\ No newline at end of file
+- πΊ **Map of all EU-classified beaches in Sweden** (MapLibre + OpenStreetMap)
+- π **Find the nearest beach** using your device's location
+- π¬ **View water quality, classification, and recent test results** (data from HaV)
+- π **Real-time beach search** with autocomplete dropdown
+- β€οΈ **Create an account and save favourite beaches** to your profile
+- π **Custom sorting for favorites** with pointer and keyboard controls
+- π **3D animated backgrounds** (WebGL water simulation in dark mode, sand texture in light mode)
+- π¨ **Glassmorphism UI** with backdrop blur effects
+- π€οΈ **Weather & sun times panel** on each beach β air temp, feels like, UV index, water temperature, and a visual sun arc with golden hour, sunrise/sunset, and twilight phases
+- π **Dark mode** and responsive design (mobile β desktop)
+- π **Multi-language support** (Swedish / English)
+- π **API Documentation** (Swagger UI at `/api/docs`)
+
+---
+
+## π Tech Stack
+
+**Frontend**
+
+- React 18 + Vite + TypeScript
+- React Router v7
+- Zustand (global state)
+- TanStack Query (server state & caching)
+- Tailwind CSS v4 (with CSS-based theme system)
+- i18next (translations)
+- MapLibre GL (maps)
+- Three.js ecosystem (@react-three/fiber, @react-three/drei) for 3D backgrounds
+- @dnd-kit (drag-and-drop)
+- react-hook-form + Zod (form validation)
+- react-hot-toast (notifications)
+
+**Custom React Hooks**
+
+- `useGeolocation` β Device location access
+- `useOutsideClose` β Close popovers on outside click/Escape key
+- `useToggleDarkMode` β Writes the `.dark` class (used in Header)
+- `useDarkModeObserver` β Reads/observes `.dark` class via MutationObserver (used in AmbientBackground)
+- `usePrefersReducedMotion` β Respect user's motion preferences
+- `useBeaches` β Beach data fetching
+- `useWeather` β Weather data with 30-min stale time
+- `useSunTimes` β Sun times keyed by date with 12-hr stale time
+
+**Backend**
+
+- Node.js + Express 5
+- MongoDB + Mongoose
+- Cookie-based JWT sessions
+- Zod (validation)
+- In-memory caching for HaV API responses
+- Swagger UI (API documentation)
+
+**External APIs**
+
+- [HaV Bathing Waters API](https://badplatsen.havochvatten.se/) (official Swedish Agency for Marine and Water Management)
+- [MapTiler](https://www.maptiler.com/) (map styles)
+- [Open-Meteo](https://open-meteo.com/) (weather forecast + marine/water temperature)
+- [sunrise-sunset.org](https://sunrise-sunset.org/api) (sunrise, sunset, golden hour, and twilight times)
+
+---
+
+## βΏ Accessibility
+
+Badlistan is built with accessibility in mind:
+
+- **Skip navigation link** β Jump directly to main content
+- **ARIA labels** β Proper labeling of interactive elements
+- **ARIA live regions** β Screen reader announcements for dynamic content
+- **ARIA busy states** β Loading indicators for assistive technology
+- **Keyboard navigation** β Search is an ARIA combobox (arrow keys / Enter / Escape); menus, dialogs, and favorite reordering are keyboard-operable with focus management
+- **Escape key handling** β Close menus and return focus to trigger
+- **Focus visible rings** β Clear focus indicators
+- **Reduced motion support** β Respects `prefers-reduced-motion` (both CSS and JS)
+- **Semantic HTML** β Proper document structure with roles
+- **Dynamic lang attribute** β HTML lang updates with language selection
+- **Accessible notifications** β Toast messages with proper ARIA live regions
+
+---
+
+## πΈ Screenshots
+
+_(Coming soon)_
+
+---
+
+## π Installation & Setup
+
+Clone the repo and install dependencies:
+
+```bash
+git clone https://github.com/govargas/bada.git
+cd bada
+```
+
+**Backend**
+
+```bash
+cd backend
+cp .env.example .env.local # then fill in your values
+npm install
+npm run dev
+```
+
+Backend runs on http://localhost:3000
+
+API Documentation available at http://localhost:3000/api/docs
+
+Raw OpenAPI JSON is available at http://localhost:3000/api/docs/openapi.json
+
+**Frontend**
+
+```bash
+cd frontend
+cp .env.example .env.local # then fill in your values
+npm install
+npm run dev
+```
+
+Frontend runs on http://localhost:5173
+
+---
+
+## π Environment Variables
+
+- See .env.example in both backend/ and frontend/.
+- Fill in with your own values (MongoDB Atlas, JWT secret, MapTiler key).
+
+---
+
+## π Deployment
+
+- Frontend: Deployed on Netlify: https://badaweb.netlify.app/
+- Backend: Deployed on Vercel β https://bada-backend.vercel.app/api/health
+
+---
+
+## β
Requirements Checklist
+
+### Technical Requirements (Grade G)
+
+- β
React frontend
+- β
Node.js + Express backend
+- β
MongoDB database
+- β
Authentication (httpOnly cookie session with JWT)
+- β
React Router navigation
+- β
Global state management (Zustand)
+- β
β₯2 external libraries (TanStack Query, MapLibre, Three.js, react-hook-form, i18next, react-hot-toast, @dnd-kit)
+- β
Custom React hooks (useGeolocation, useOutsideClose, useToggleDarkMode, useDarkModeObserver, usePrefersReducedMotion, useBeaches, useWeather, useSunTimes)
+- β
Responsive (320px β 1600px+)
+- β
Accessibility features (comprehensive a11y implementation)
+- β
Clean Code practices
+
+### Visual Requirements
+
+- β
Clear structure using box model with consistent margins/paddings
+- β
Consistent typography across views and breakpoints
+- β
Cohesive color scheme with CSS design tokens
+- β
Mobile-first responsive design
+- β
Dark mode support with 3D backgrounds
+- β
Multi-language support (Swedish/English)
+- β
Glassmorphism UI design system
+
+### Grade VG Enhancements
+
+- β
Error Boundaries
+- β
Toast notifications with a11y
+- β
Reduced motion support
+- β
Comprehensive documentation
+- β
Meta tags for SEO (Open Graph, Twitter Cards)
+- β
API Documentation (Swagger UI)
+- β
Performance optimizations (lazy-loaded 3D backgrounds)
+
+---
+
+## β οΈ Known Limitations
+
+These are conscious trade-offs for an MVP, documented rather than hidden:
+
+- **Backend cache is per-instance.** The HaV proxy cache is an in-memory `Map`. On Vercel serverless it does not survive cold starts or span instances, so it mainly de-dupes bursts within a warm instance. The beach _list_ sidesteps this entirely via a static CDN snapshot (`frontend/public/beaches.json`, refreshed daily by a GitHub Action); only per-beach detail still hits the proxy.
+- **Data is seasonal.** HaV samples bathing waters roughly JuneβAugust. Outside the season `latestSampleDate` and classification reflect the previous year.
+- **Golden hour is approximate.** It is computed as sunrise + 1h / sunset β 1h, and the sun arc degrades north of the Arctic Circle during the midnight sun (sunrise-sunset.org returns degenerate times there).
+- **Third-party APIs called from the client.** Open-Meteo and sunrise-sunset.org are called directly from the browser with no SLA β acceptable for an MVP, not for production traffic.
+- **Session cookie auth.** Login sets an httpOnly `bada_session` cookie through the Netlify `/api/*` proxy so the session stays first-party and unavailable to JavaScript.
+- **No SSR.** SEO is limited to per-route `
`, a generated `sitemap.xml`, and site-wide Open Graph tags. Per-beach Open Graph previews would require prerendering.
+
+---
+
+## π§ Roadmap
+
+### Completed
+
+- β
3D animated backgrounds (WebGL water/sand)
+- β
Enhanced glassmorphism UI
+- β
Header search with autocomplete
+- β
Comprehensive accessibility implementation
+- β
API documentation with Swagger
+- β
Performance optimization (deferred 3D loading)
+- β
Focus management and keyboard navigation
+- β
Weather & sun times panel (Open-Meteo + sunrise-sunset.org) with visual sun arc, golden hour zones, and water temperature
+
+### Planned
+
+- π Filter beaches by classification
+- π Add user beach photos
+- π Allow notes/tips per beach (e.g. "good for kids")
+- π PWA support with offline capability
+
+---
+
+## π‘ Inspiration & Credits
+
+- Data from the Swedish Agency for Marine and Water Management (HaV)
+- Maps powered by OpenStreetMap + MapTiler
+- Built during the Technigo Fullstack JavaScript Bootcamp (2025)
+
+---
+
+## π¨βπ» Author
+
+Created by Talo Vargas, 2025
diff --git a/backend/.babelrc b/backend/.babelrc
deleted file mode 100644
index cedf24f1a5..0000000000
--- a/backend/.babelrc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "presets": [
- "@babel/preset-env"
- ]
-}
\ No newline at end of file
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000000..3f24d328e2
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,28 @@
+# Server
+PORT=3000
+NODE_ENV=development
+
+# Database
+MONGODB_URI=mongodb+srv://:@/?retryWrites=true&w=majority
+
+# Auth
+JWT_SECRET=replace-with-a-random-secret
+
+# CORS
+ALLOWED_ORIGIN=http://localhost:5173
+
+# Netlify signed proxy. Set the same value in Netlify and the backend in
+# production so forwarded client IPs can be trusted for rate limiting.
+PROXY_SIGNING_KEY=
+
+# Rate limiting (optional locally; recommended in production).
+# A shared Upstash Redis store so auth rate limits hold across serverless
+# instances. Create a free database at https://console.upstash.com and copy
+# the REST URL + token. If unset, rate limiting falls back to in-memory.
+UPSTASH_REDIS_REST_URL=
+UPSTASH_REDIS_REST_TOKEN=
+
+# HaV API
+HAV_BASE_URL=https://badplatsen.havochvatten.se/badplatsen/api
+HAV_V2_BASE=https://api.havochvatten.se/bathingwaters/v2
+HAV_USER_AGENT=BADA-App/1.0 (contact: my@email)
diff --git a/backend/README.md b/backend/README.md
deleted file mode 100644
index d1438c9108..0000000000
--- a/backend/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Backend part of Final Project
-
-This project includes the packages and babel setup for an express server, and is just meant to make things a little simpler to get up and running with.
-
-## Getting Started
-
-1. Install the required dependencies using `npm install`.
-2. Start the development server using `npm run dev`.
\ No newline at end of file
diff --git a/backend/package-lock.json b/backend/package-lock.json
new file mode 100644
index 0000000000..24bca7e9c3
--- /dev/null
+++ b/backend/package-lock.json
@@ -0,0 +1,5370 @@
+{
+ "name": "bada-backend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "bada-backend",
+ "version": "1.0.0",
+ "dependencies": {
+ "@upstash/ratelimit": "^2.0.8",
+ "@upstash/redis": "^1.38.0",
+ "bcryptjs": "^3.0.2",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.5",
+ "dotenv": "^17.2.2",
+ "express": "^5.1.0",
+ "express-rate-limit": "^8.5.2",
+ "helmet": "^8.2.0",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.18.1",
+ "swagger-ui-express": "^5.0.1",
+ "zod": "^4.1.5"
+ },
+ "devDependencies": {
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.3",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/node": "^20.19.13",
+ "@types/supertest": "^7.2.0",
+ "@types/swagger-ui-express": "^4.1.8",
+ "mongodb-memory-server": "^11.2.0",
+ "supertest": "^7.2.2",
+ "ts-node-dev": "^2.0.0",
+ "tsx": "^4.20.5",
+ "typescript": "^5.9.2",
+ "vitest": "^4.1.9"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
+ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
+ "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
+ "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
+ "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
+ "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
+ "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
+ "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
+ "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
+ "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
+ "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
+ "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
+ "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
+ "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
+ "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
+ "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
+ "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
+ "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
+ "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
+ "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
+ "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
+ "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
+ "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@mongodb-js/saslprep": {
+ "version": "1.4.11",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz",
+ "integrity": "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==",
+ "license": "MIT",
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+ "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.137.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz",
+ "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@paralleldrive/cuid2": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
+ "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "^1.1.5"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz",
+ "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz",
+ "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz",
+ "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz",
+ "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz",
+ "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz",
+ "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz",
+ "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz",
+ "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz",
+ "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz",
+ "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz",
+ "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz",
+ "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz",
+ "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.1",
+ "@emnapi/runtime": "1.11.1",
+ "@napi-rs/wasm-runtime": "^1.1.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz",
+ "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz",
+ "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@scarf/scarf": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cookie-parser": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz",
+ "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/cookiejar": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
+ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.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/express": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
+ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz",
+ "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.10",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/methods": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
+ "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz",
+ "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "0.17.5",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
+ "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.8",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
+ "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/strip-json-comments": {
+ "version": "0.0.30",
+ "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz",
+ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/superagent": {
+ "version": "8.1.10",
+ "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.10.tgz",
+ "integrity": "sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookiejar": "^2.1.5",
+ "@types/methods": "^1.1.4",
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/@types/supertest": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz",
+ "integrity": "sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/methods": "^1.1.4",
+ "@types/superagent": "^8.1.0"
+ }
+ },
+ "node_modules/@types/swagger-ui-express": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
+ "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
+ "node_modules/@upstash/core-analytics": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@upstash/core-analytics/-/core-analytics-0.0.10.tgz",
+ "integrity": "sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@upstash/redis": "^1.28.3"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@upstash/ratelimit": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@upstash/ratelimit/-/ratelimit-2.0.8.tgz",
+ "integrity": "sha512-YSTMBJ1YIxsoPkUMX/P4DDks/xV5YYCswWMamU8ZIfK9ly6ppjRnVOyBhMDXBmzjODm4UQKcxsJPvaeFAijp5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@upstash/core-analytics": "^0.0.10"
+ },
+ "peerDependencies": {
+ "@upstash/redis": "^1.34.3"
+ }
+ },
+ "node_modules/@upstash/redis": {
+ "version": "1.38.0",
+ "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.38.0.tgz",
+ "integrity": "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg==",
+ "license": "MIT",
+ "dependencies": {
+ "uncrypto": "^0.1.3"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
+ "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
+ "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
+ "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
+ "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
+ "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
+ "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/async-mutex": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
+ "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/b4a": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
+ "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
+ "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/bare-events": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
+ "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-fs": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz",
+ "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4",
+ "bare-url": "^2.2.2",
+ "fast-fifo": "^1.3.2"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-os": {
+ "version": "3.9.2",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.2.tgz",
+ "integrity": "sha512-h530JsrkYi8518ZfR57GHaLoI5YzXkGGEV0Y+mf4KYPBn4OnNajiznwkDq7FgE+Vnmyss9Utnzi44y7sowiAXA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "bare": ">=1.14.0"
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz",
+ "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-os": "^3.0.1"
+ }
+ },
+ "node_modules/bare-stream": {
+ "version": "2.13.3",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz",
+ "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.8.1",
+ "streamx": "^2.25.0",
+ "teex": "^1.0.1"
+ },
+ "peerDependencies": {
+ "bare-abort-controller": "*",
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ },
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-url": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
+ "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
+ "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "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/bson": {
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
+ "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.20.1"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "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==",
+ "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/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/component-emitter": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
+ "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cookiejar": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
+ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.2.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
+ "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "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/dynamic-dedupe": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
+ "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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==",
+ "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/esbuild": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
+ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.9",
+ "@esbuild/android-arm": "0.25.9",
+ "@esbuild/android-arm64": "0.25.9",
+ "@esbuild/android-x64": "0.25.9",
+ "@esbuild/darwin-arm64": "0.25.9",
+ "@esbuild/darwin-x64": "0.25.9",
+ "@esbuild/freebsd-arm64": "0.25.9",
+ "@esbuild/freebsd-x64": "0.25.9",
+ "@esbuild/linux-arm": "0.25.9",
+ "@esbuild/linux-arm64": "0.25.9",
+ "@esbuild/linux-ia32": "0.25.9",
+ "@esbuild/linux-loong64": "0.25.9",
+ "@esbuild/linux-mips64el": "0.25.9",
+ "@esbuild/linux-ppc64": "0.25.9",
+ "@esbuild/linux-riscv64": "0.25.9",
+ "@esbuild/linux-s390x": "0.25.9",
+ "@esbuild/linux-x64": "0.25.9",
+ "@esbuild/netbsd-arm64": "0.25.9",
+ "@esbuild/netbsd-x64": "0.25.9",
+ "@esbuild/openbsd-arm64": "0.25.9",
+ "@esbuild/openbsd-x64": "0.25.9",
+ "@esbuild/openharmony-arm64": "0.25.9",
+ "@esbuild/sunos-x64": "0.25.9",
+ "@esbuild/win32-arm64": "0.25.9",
+ "@esbuild/win32-ia32": "0.25.9",
+ "@esbuild/win32-x64": "0.25.9"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.5.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
+ "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/form-data/node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/formidable": {
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
+ "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@paralleldrive/cuid2": "^2.2.2",
+ "dezalgo": "^1.0.4",
+ "once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
+ "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/helmet": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz",
+ "integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/EvanHahn"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-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-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-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-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
+ "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.2",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/kareem": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
+ "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/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/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "license": "MIT"
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.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/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mongodb": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.3.0",
+ "bson": "^6.10.4",
+ "mongodb-connection-string-url": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.188.0",
+ "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
+ "gcp-metadata": "^5.2.0",
+ "kerberos": "^2.0.1",
+ "mongodb-client-encryption": ">=6.0.0 <7",
+ "snappy": "^7.3.2",
+ "socks": "^2.7.1"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
+ "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/whatwg-url": "^11.0.2",
+ "whatwg-url": "^14.1.0 || ^13.0.0"
+ }
+ },
+ "node_modules/mongodb-memory-server": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-11.2.0.tgz",
+ "integrity": "sha512-506AD8qvClVx8Raw/WhAUUWBgIXPyi856iC01aa5vAzHmn6WOXC6ulvudkTF7oTMzJxkyA0A84VpD4BpyfqJ9w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "mongodb-memory-server-core": "11.2.0",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/mongodb-memory-server-core": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-11.2.0.tgz",
+ "integrity": "sha512-vOoDtn0JiLrHvZY81Rp/UtKXXK0rtJHZGZFVnccvJwYitPLNspO0Ty0grqFQOe7iAET8+GI4zAQcphg+R3vxQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-mutex": "^0.5.0",
+ "camelcase": "^6.3.0",
+ "debug": "^4.4.3",
+ "find-cache-dir": "^3.3.2",
+ "follow-redirects": "^1.16.0",
+ "https-proxy-agent": "^7.0.6",
+ "mongodb": "^7.2.0",
+ "new-find-package-json": "^2.0.0",
+ "semver": "^7.7.3",
+ "tar-stream": "^3.1.8",
+ "tslib": "^2.8.1",
+ "yauzl": "^3.3.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
+ "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/bson": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-7.3.1.tgz",
+ "integrity": "sha512-h/C0qe6857pQhcSJHLfsR1uYGj98Ge3wKAD3Ed9KqH3wcVh+BM4Jq4xISD7vs9OPuT07n+q3QQVjslJ286j6ag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/mongodb": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.4.0.tgz",
+ "integrity": "sha512-giySkkdYiwoBFo/oCc8nzov3xOYZ/sB8OpAYk5GINRLEjVw0LDsm8xgQL0XMTyU4extQlDZjhdUr1ZEwKFaazw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.3.0",
+ "bson": "^7.2.0",
+ "mongodb-connection-string-url": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.806.0",
+ "@mongodb-js/zstd": "^7.0.0",
+ "gcp-metadata": "^7.0.1",
+ "kerberos": "^7.0.0",
+ "mongodb-client-encryption": ">=7.0.0 <7.1.0",
+ "snappy": "^7.3.2",
+ "socks": "^2.8.6"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz",
+ "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/whatwg-url": "^13.0.0",
+ "whatwg-url": "^14.1.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/mongoose": {
+ "version": "8.24.1",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.24.1.tgz",
+ "integrity": "sha512-UpHBA0l5kHyKJQFjmBaFYQFo5sgz1DK0TRqDkOyBLYbqiIbKKhIvBpHWBXqeo0rgW4kGI1UhhAw+kTQZoj1BdA==",
+ "license": "MIT",
+ "dependencies": {
+ "bson": "^6.10.4",
+ "kareem": "2.6.3",
+ "mongodb": "~6.20.0",
+ "mpath": "0.9.0",
+ "mquery": "5.0.0",
+ "ms": "2.1.3",
+ "sift": "17.1.3"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mongoose"
+ }
+ },
+ "node_modules/mpath": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mquery": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
+ "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4.x"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/new-find-package-json": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz",
+ "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">=12.22.0"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
+ "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "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==",
+ "dev": true,
+ "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/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "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-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/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz",
+ "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.137.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.1.3",
+ "@rolldown/binding-darwin-arm64": "1.1.3",
+ "@rolldown/binding-darwin-x64": "1.1.3",
+ "@rolldown/binding-freebsd-x64": "1.1.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.1.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.1.3",
+ "@rolldown/binding-linux-arm64-musl": "1.1.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.1.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.1.3",
+ "@rolldown/binding-linux-x64-gnu": "1.1.3",
+ "@rolldown/binding-linux-x64-musl": "1.1.3",
+ "@rolldown/binding-openharmony-arm64": "1.1.3",
+ "@rolldown/binding-wasm32-wasi": "1.1.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.1.3",
+ "@rolldown/binding-win32-x64-msvc": "1.1.3"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "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",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "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==",
+ "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==",
+ "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==",
+ "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/sift": {
+ "version": "17.1.3",
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
+ "license": "MIT"
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/streamx": {
+ "version": "2.28.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz",
+ "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
+ "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": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/superagent": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
+ "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "component-emitter": "^1.3.1",
+ "cookiejar": "^2.1.4",
+ "debug": "^4.3.7",
+ "fast-safe-stringify": "^2.1.1",
+ "form-data": "^4.0.5",
+ "formidable": "^3.5.4",
+ "methods": "^1.1.2",
+ "mime": "2.6.0",
+ "qs": "^6.14.1"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/supertest": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
+ "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie-signature": "^1.2.2",
+ "methods": "^1.1.2",
+ "superagent": "^10.3.0"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "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/swagger-ui-dist": {
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.0.tgz",
+ "integrity": "sha512-gqs7Md3AxP4mbpXAq31o5QW+wGUZsUzVatg70yXpUR245dfIis5jAzufBd+UQM/w2xSfrhvA1eqsrgnl2PbezQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@scarf/scarf": "=1.4.0"
+ }
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
+ "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
+ "license": "MIT",
+ "dependencies": {
+ "swagger-ui-dist": ">=5.0.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0 || >=5.0.0-beta"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
+ "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "bare-fs": "^4.5.5",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "streamx": "^2.12.5"
+ }
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "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/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node-dev": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
+ "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.1",
+ "dynamic-dedupe": "^0.3.0",
+ "minimist": "^1.2.6",
+ "mkdirp": "^1.0.4",
+ "resolve": "^1.0.0",
+ "rimraf": "^2.6.1",
+ "source-map-support": "^0.5.12",
+ "tree-kill": "^1.2.2",
+ "ts-node": "^10.4.0",
+ "tsconfig": "^7.0.0"
+ },
+ "bin": {
+ "ts-node-dev": "lib/bin.js",
+ "tsnd": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "*",
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tsconfig": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
+ "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/strip-bom": "^3.0.0",
+ "@types/strip-json-comments": "0.0.30",
+ "strip-bom": "^3.0.0",
+ "strip-json-comments": "^2.0.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/tsx": {
+ "version": "4.20.5",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz",
+ "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.25.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uncrypto": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
+ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
+ "license": "MIT"
+ },
+ "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/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
+ "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.9",
+ "@vitest/mocker": "4.1.9",
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/runner": "4.1.9",
+ "@vitest/snapshot": "4.1.9",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.9",
+ "@vitest/browser-preview": "4.1.9",
+ "@vitest/browser-webdriverio": "4.1.9",
+ "@vitest/coverage-istanbul": "4.1.9",
+ "@vitest/coverage-v8": "4.1.9",
+ "@vitest/ui": "4.1.9",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
+ "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/esbuild": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest/node_modules/vite": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz",
+ "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.15",
+ "rolldown": "~1.1.2",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.3.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz",
+ "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz",
+ "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/backend/package.json b/backend/package.json
index 08f29f2448..36133699c1 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,20 +1,44 @@
{
- "name": "project-final-backend",
+ "name": "bada-backend",
"version": "1.0.0",
- "description": "Server part of final project",
+ "type": "module",
"scripts": {
- "start": "babel-node server.js",
- "dev": "nodemon server.js --exec babel-node"
+ "dev": "tsx watch src/server.ts",
+ "build": "tsc",
+ "start": "node dist/server.js",
+ "test": "vitest run"
},
- "author": "",
- "license": "ISC",
"dependencies": {
- "@babel/core": "^7.17.9",
- "@babel/node": "^7.16.8",
- "@babel/preset-env": "^7.16.11",
+ "@upstash/ratelimit": "^2.0.8",
+ "@upstash/redis": "^1.38.0",
+ "bcryptjs": "^3.0.2",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.5",
- "express": "^4.17.3",
- "mongoose": "^8.4.0",
- "nodemon": "^3.0.1"
+ "dotenv": "^17.2.2",
+ "express": "^5.1.0",
+ "express-rate-limit": "^8.5.2",
+ "helmet": "^8.2.0",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.18.1",
+ "swagger-ui-express": "^5.0.1",
+ "zod": "^4.1.5"
+ },
+ "devDependencies": {
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.3",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/node": "^20.19.13",
+ "@types/supertest": "^7.2.0",
+ "@types/swagger-ui-express": "^4.1.8",
+ "mongodb-memory-server": "^11.2.0",
+ "supertest": "^7.2.2",
+ "ts-node-dev": "^2.0.0",
+ "tsx": "^4.20.5",
+ "typescript": "^5.9.2",
+ "vitest": "^4.1.9"
+ },
+ "engines": {
+ "node": ">=18"
}
-}
\ No newline at end of file
+}
diff --git a/backend/scripts/test-favorites.sh b/backend/scripts/test-favorites.sh
new file mode 100755
index 0000000000..25b98271d4
--- /dev/null
+++ b/backend/scripts/test-favorites.sh
@@ -0,0 +1,150 @@
+#!/usr/bin/env bash
+#
+# Smoke-test the favorites API end-to-end against a running backend.
+# Usage:
+# 1. In one terminal: cd backend && npm run dev
+# 2. In another: ./backend/scripts/test-favorites.sh
+#
+# Optional env overrides:
+# BASE=http://localhost:3000 EMAIL=test@example.com PASSWORD=testpass123
+# BEACH=SE0110000000000001
+
+set -u
+
+BASE="${BASE:-http://localhost:3000}"
+EMAIL="${EMAIL:-test+favtest@example.com}"
+PASSWORD="${PASSWORD:-testpass123}"
+BEACH="${BEACH:-SE0110000000000001}"
+
+pass=0; fail=0
+green=$'\033[32m'; red=$'\033[31m'; dim=$'\033[2m'; reset=$'\033[0m'
+
+# check
+check() {
+ if [ "$2" = "$3" ]; then
+ printf " ${green}PASS${reset} %-42s (got %s)\n" "$1" "$2"; pass=$((pass+1))
+ else
+ printf " ${red}FAIL${reset} %-42s (got %s, expected %s)\n" "$1" "$2" "$3"; fail=$((fail+1))
+ fi
+}
+
+# Temp file for request bodies. Passing JSON via a file (--data-binary @file)
+# makes the payload completely opaque to shell brace-expansion / word-splitting
+# and to curl's {}[] URL globbing (also disabled with -g), which can otherwise
+# mangle bodies containing commas like {"a":1,"b":2} in some environments.
+BODY_FILE="$(mktemp -t bada-fav-body)"
+COOKIE_JAR="$(mktemp -t bada-fav-cookies)"
+trap 'rm -f "$BODY_FILE" "$COOKIE_JAR"' EXIT
+
+# build_args METHOD PATH [DATA] [COOKIE] -> populates global CURL_ARGS array
+build_args() {
+ local method="$1" path="$2" data="${3:-}" use_cookie="${4:-}"
+ CURL_ARGS=(-g -X "$method" "$BASE$path")
+ [ "$use_cookie" = "cookie" ] && CURL_ARGS+=(-b "$COOKIE_JAR" -c "$COOKIE_JAR")
+ if [ -n "$data" ]; then
+ printf '%s' "$data" > "$BODY_FILE"
+ CURL_ARGS+=(-H "Content-Type: application/json" --data-binary @"$BODY_FILE")
+ fi
+}
+
+# status METHOD PATH [DATA] [COOKIE] -> prints HTTP code
+status() {
+ build_args "$@"
+ curl -s -o /dev/null -w "%{http_code}" "${CURL_ARGS[@]}"
+}
+
+# body METHOD PATH [DATA] [COOKIE] -> prints response body
+body() {
+ build_args "$@"
+ curl -s "${CURL_ARGS[@]}"
+}
+
+echo "Testing favorites API at $BASE"
+echo "User: $EMAIL Beach: $BEACH"
+echo
+
+# 0. Server reachable?
+if ! curl -s -o /dev/null "$BASE/api/health"; then
+ echo "${red}Cannot reach $BASE β is the backend running? (cd backend && npm run dev)${reset}"
+ exit 1
+fi
+
+# JSON payloads are built into variables FIRST. Never inline JSON inside a
+# double-quoted "$(...)" β macOS bash 3.2 mis-parses nested quotes there and
+# brace-expands the literal { } (e.g. {"a":1,"b":2} -> two words split at the
+# comma), corrupting the body. Assigning to a variable first avoids this.
+cred_json="{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}"
+fav_json="{\"beachId\":\"$BEACH\",\"note\":\"smoke test\"}"
+fav_min_json="{\"beachId\":\"$BEACH\"}"
+reorder_json="{\"order\":[\"$BEACH\"]}"
+
+# 1. Register (201 first time, 409 if user already exists β both OK)
+code=$(status POST /api/auth/register "$cred_json")
+if [ "$code" = "201" ] || [ "$code" = "409" ]; then
+ printf " ${green}PASS${reset} %-42s (got %s)\n" "register (201 new / 409 existing)" "$code"; pass=$((pass+1))
+else
+ printf " ${red}FAIL${reset} %-42s (got %s)\n" "register" "$code"; fail=$((fail+1))
+fi
+
+# 2. Login -> httpOnly session cookie
+code=$(status POST /api/auth/login "$cred_json" cookie)
+if [ "$code" = "200" ] && grep -q "bada_session" "$COOKIE_JAR"; then
+ printf " ${green}PASS${reset} %-42s\n" "login sets session cookie"; pass=$((pass+1))
+else
+ printf " ${red}FAIL${reset} %-42s β cannot continue without a session\n" "login sets session cookie"; fail=$((fail+1))
+ echo; echo "${red}Aborting: login failed.${reset}"; exit 1
+fi
+
+# 3. Auth is enforced (no cookie -> 401)
+code=$(status GET /api/favorites)
+check "GET /favorites without cookie -> 401" "$code" "401"
+
+# 4. List favorites with cookie -> 200
+code=$(status GET /api/favorites '' cookie)
+check "GET /favorites with cookie -> 200" "$code" "200"
+
+# 5. Clean slate: remove this beach if a previous run left it
+status DELETE "/api/favorites/by-beach/$BEACH" '' cookie >/dev/null
+
+# 6. Add a favorite -> 201
+code=$(status POST /api/favorites "$fav_json" cookie)
+check "POST /favorites -> 201" "$code" "201"
+
+# 7. It shows up in the list
+if body GET /api/favorites '' cookie | grep -q "$BEACH"; then
+ printf " ${green}PASS${reset} %-42s\n" "favorite appears in list"; pass=$((pass+1))
+else
+ printf " ${red}FAIL${reset} %-42s\n" "favorite appears in list"; fail=$((fail+1))
+fi
+
+# 8. Duplicate add -> 409
+code=$(status POST /api/favorites "$fav_min_json" cookie)
+check "POST same favorite again -> 409" "$code" "409"
+
+# 9. Reorder -> 204
+code=$(status PATCH /api/favorites/reorder "$reorder_json" cookie)
+check "PATCH /favorites/reorder -> 204" "$code" "204"
+
+# 10. Delete by beach -> 200
+code=$(status DELETE "/api/favorites/by-beach/$BEACH" '' cookie)
+check "DELETE /favorites/by-beach -> 200" "$code" "200"
+
+# 11. Gone from the list
+if body GET /api/favorites '' cookie | grep -q "$BEACH"; then
+ printf " ${red}FAIL${reset} %-42s (still present)\n" "favorite removed from list"; fail=$((fail+1))
+else
+ printf " ${green}PASS${reset} %-42s\n" "favorite removed from list"; pass=$((pass+1))
+fi
+
+# 12. Decoupling check: health never touches the DB
+code=$(status GET /api/health)
+check "GET /api/health -> 200 (no DB)" "$code" "200"
+
+echo
+if [ "$fail" -eq 0 ]; then
+ echo "${green}All $pass checks passed.${reset}"
+ exit 0
+else
+ echo "${red}$fail failed${reset}, ${green}$pass passed.${reset}"
+ exit 1
+fi
diff --git a/backend/server.js b/backend/server.js
deleted file mode 100644
index 070c875189..0000000000
--- a/backend/server.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import express from "express";
-import cors from "cors";
-import mongoose from "mongoose";
-
-const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project";
-mongoose.connect(mongoUrl);
-mongoose.Promise = Promise;
-
-const port = process.env.PORT || 8080;
-const app = express();
-
-app.use(cors());
-app.use(express.json());
-
-app.get("/", (req, res) => {
- res.send("Hello Technigo!");
-});
-
-// Start the server
-app.listen(port, () => {
- console.log(`Server running on http://localhost:${port}`);
-});
diff --git a/backend/src/index.ts b/backend/src/index.ts
new file mode 100644
index 0000000000..ad80919e52
--- /dev/null
+++ b/backend/src/index.ts
@@ -0,0 +1,147 @@
+// backend/src/index.ts
+console.log("Server booted at", new Date().toISOString());
+
+import express from "express";
+import cors from "cors";
+import helmet from "helmet";
+import cookieParser from "cookie-parser";
+
+import { healthRouter } from "./routes/health.js";
+import { dbCheckRouter } from "./routes/dbCheck.js";
+import { authRouter } from "./routes/auth.js";
+import { requireAuth } from "./middleware/auth.js";
+import { favoritesRouter } from "./routes/favorites.js";
+import { beachesRouter } from "./routes/beaches.js";
+import { watchesRouter } from "./routes/watches.js";
+import { swaggerRouter } from "./swagger.js";
+
+const isProd = process.env.NODE_ENV === "production";
+
+/** ββ Required env validation at boot βββββββββββββββββββββββββββββββββββββββ */
+const requiredEnv = ["JWT_SECRET", "MONGODB_URI", "HAV_BASE_URL"];
+const missingEnv = requiredEnv.filter((k) => !process.env[k]);
+if (missingEnv.length > 0) {
+ const msg = `Missing required environment variables: ${missingEnv.join(", ")}`;
+ if (isProd) throw new Error(msg);
+ console.warn(`[WARN] ${msg}`);
+}
+
+const app = express();
+
+// Behind exactly one proxy in production (Vercel's edge). This makes req.ip
+// the real client IP from X-Forwarded-For, which the rate limiter keys on.
+app.set("trust proxy", 1);
+
+/** ββ Security headers ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+app.use(helmet());
+
+/** ββ CORS (allow comma-separated origins via CORS_ORIGIN or ALLOWED_ORIGIN) ββ */
+const allowedOrigins = (
+ process.env.CORS_ORIGIN ||
+ process.env.ALLOWED_ORIGIN ||
+ ""
+)
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean);
+
+// On Netlify the API runs as a function on the app's own origin, so every
+// browser request is same-origin and CORS never applies β no origin list needed.
+const isNetlify = process.env.NETLIFY === "true";
+
+if (isProd && !isNetlify && allowedOrigins.length === 0) {
+ throw new Error(
+ "No CORS origins configured. Set CORS_ORIGIN (or ALLOWED_ORIGIN) in production."
+ );
+}
+
+const corsOptions: cors.CorsOptions = {
+ origin(origin, cb) {
+ if (!origin) return cb(null, true); // non-browser clients
+ // Dev fallback: allow all only outside production. In production,
+ // allowedOrigins is guaranteed non-empty by the boot check above.
+ if (!isProd && allowedOrigins.length === 0) return cb(null, true);
+ if (allowedOrigins.includes(origin)) return cb(null, true);
+ return cb(null, false);
+ },
+ credentials: true,
+ methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
+};
+
+app.use(cors(corsOptions));
+app.use(express.json());
+app.use(cookieParser());
+
+if (process.env.NODE_ENV === "development") {
+ app.use((req, _res, next) => {
+ console.log(`[req] ${req.method} ${req.url}`);
+ next();
+ });
+}
+
+/** ββ API Documentation (Swagger UI) βββββββββββββββββββββββββββββββββββββββββ */
+// swagger-ui-express serves static assets from the swagger-ui-dist package
+// directory on disk; those files don't exist inside the esbuild function
+// bundle, so skip the docs UI when running as a Netlify Function.
+if (!isNetlify) app.use("/api", swaggerRouter);
+
+/** ββ Public routers under /api βββββββββββββββββββββββββββββββββββββββββββββ */
+app.use("/api", healthRouter);
+// Diagnostics endpoint leaks DB/service state; keep it out of production.
+if (!isProd) app.use("/api", dbCheckRouter);
+app.use("/api/favorites", favoritesRouter);
+app.use("/api/watches", watchesRouter);
+app.use("/api", beachesRouter);
+
+/** ββ Auth βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+app.use("/api/auth", authRouter);
+app.get("/api/protected/ping", requireAuth, (req, res) => {
+ res.json({ ok: true, user: (req as any).user });
+});
+app.get("/api/auth/me", requireAuth, (req, res) => {
+ res.json({ user: (req as any).user });
+});
+
+/** ββ Quick direct health ping ββββββββββββββββββββββββββββββββββββββββββββββ */
+app.get("/api/health-direct", (_req, res) => {
+ console.log("Hit /api/health-direct");
+ res.json({ ok: true, via: "direct" });
+});
+
+/** ββ 404 + error handler (MUST be last) ββββββββββββββββββββββββββββββββββββ */
+app.use((_req, res) => res.status(404).json({ error: "NotFound" }));
+app.use(
+ (
+ err: unknown,
+ _req: express.Request,
+ res: express.Response,
+ _next: express.NextFunction
+ ) => {
+ console.error("[ERROR]", err);
+ const maybeHttp = err as {
+ status?: number;
+ code?: string;
+ };
+ const status =
+ typeof maybeHttp.status === "number" &&
+ maybeHttp.status >= 400 &&
+ maybeHttp.status < 600
+ ? maybeHttp.status
+ : 500;
+ const error =
+ maybeHttp.code ??
+ (status === 404
+ ? "NotFound"
+ : status === 502
+ ? "BadGateway"
+ : "InternalServerError");
+ const message =
+ process.env.NODE_ENV !== "production" && err instanceof Error
+ ? err.message
+ : undefined;
+ res.status(status).json({ error, message });
+ }
+);
+
+export default app;
+export { swaggerDocument } from "./swagger.js";
diff --git a/backend/src/lib/cache.ts b/backend/src/lib/cache.ts
new file mode 100644
index 0000000000..adf5f140e6
--- /dev/null
+++ b/backend/src/lib/cache.ts
@@ -0,0 +1,23 @@
+type Entry = { value: T; expiry: number };
+
+export class SimpleCache {
+ private store = new Map>();
+
+ constructor(private defaultTtlMs = 60_000) {} // default 60s
+
+ get(key: string): T | undefined {
+ const hit = this.store.get(key);
+ if (!hit) return;
+ if (Date.now() > hit.expiry) {
+ this.store.delete(key);
+ return;
+ }
+ return hit.value as T;
+ }
+
+ set(key: string, value: T, ttlMs = this.defaultTtlMs) {
+ this.store.set(key, { value, expiry: Date.now() + ttlMs });
+ }
+}
+
+export const cache = new SimpleCache(60_000); // 60s default
diff --git a/backend/src/lib/db.ts b/backend/src/lib/db.ts
new file mode 100644
index 0000000000..b56d3e7942
--- /dev/null
+++ b/backend/src/lib/db.ts
@@ -0,0 +1,49 @@
+import mongoose from "mongoose";
+
+// Cache the connection across hot-reloads / serverless invocations
+type Cached = {
+ conn: typeof mongoose | null;
+ promise: Promise | null;
+};
+let cached: Cached = (global as any)._mongooseCached ?? {
+ conn: null,
+ promise: null,
+};
+(global as any)._mongooseCached = cached;
+
+export async function connectDB() {
+ if (cached.conn) return cached.conn;
+
+ if (!cached.promise) {
+ const uri = process.env.MONGODB_URI;
+ if (!uri) throw new Error("MONGODB_URI missing");
+
+ mongoose.set("strictQuery", true);
+ mongoose.set("bufferCommands", false);
+
+ cached.promise = mongoose
+ .connect(uri, { serverSelectionTimeoutMS: 5000 })
+ .then((m) => {
+ cached.conn = m;
+
+ // π΄ Optional debug hooks (remove later)
+ mongoose.connection.on("connected", () =>
+ console.log("[mongo] connected")
+ );
+ mongoose.connection.on("error", (e) =>
+ console.error("[mongo] error", e)
+ );
+ mongoose.connection.on("disconnected", () =>
+ console.warn("[mongo] disconnected")
+ );
+
+ return m;
+ })
+ .catch((err) => {
+ cached.promise = null;
+ throw err;
+ });
+ }
+
+ return cached.promise;
+}
diff --git a/backend/src/lib/hav.ts b/backend/src/lib/hav.ts
new file mode 100644
index 0000000000..2a69d96f21
--- /dev/null
+++ b/backend/src/lib/hav.ts
@@ -0,0 +1,117 @@
+import { cache } from "./cache.js";
+
+const HAV_BASE_URL = process.env.HAV_BASE_URL!;
+const HAV_USER_AGENT = process.env.HAV_USER_AGENT || "Badlistan/1.0";
+const HAV_V2_BASE =
+ process.env.HAV_V2_BASE ?? "https://api.havochvatten.se/bathingwaters/v2";
+
+/** Abort upstream HaV calls that hang, so a slow HaV can't pin a serverless
+ * function open until the platform timeout. */
+const HAV_TIMEOUT_MS = 5_000;
+
+export class HavHttpError extends Error {
+ status: number;
+ code: string;
+ upstreamStatus: number;
+
+ constructor(apiVersion: "v1" | "v2", status: number, statusText: string) {
+ super(`HaV ${apiVersion} error ${status} ${statusText}`);
+ this.name = "HavHttpError";
+ this.upstreamStatus = status;
+ this.status = status === 404 ? 404 : 502;
+ this.code = status === 404 ? "NotFound" : "UpstreamError";
+ }
+}
+
+function key(path: string) {
+ return `hav:${path}`;
+}
+
+/** v1 fetcher (old endpoints like /feature, /detail) */
+export async function havGet(path: string, ttlMs = 5 * 60 * 1000) {
+ const k = key(`v1:${path}`);
+ const cached = cache.get(k);
+ if (cached) return cached;
+
+ const url = `${HAV_BASE_URL}${path}`;
+ // TEMP: trace outgoing in dev
+ if (process.env.NODE_ENV !== "production") {
+ console.log(`[HaV v1] GET ${url}`);
+ }
+
+ const res = await fetch(url, {
+ headers: {
+ "User-Agent": HAV_USER_AGENT,
+ Accept: "application/json",
+ },
+ signal: AbortSignal.timeout(HAV_TIMEOUT_MS),
+ });
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => "");
+ console.warn(
+ `[HaV v1] ${res.status} ${res.statusText} for ${url}: ${text.slice(
+ 0,
+ 500
+ )}`
+ );
+ throw new HavHttpError("v1", res.status, res.statusText);
+ }
+
+ const data = await res.json();
+ cache.set(k, data, ttlMs);
+ return data;
+}
+
+/** Minimal v2 fetcher (used for monitoring results, etc.) */
+export async function havV2Get(
+ path: string,
+ init?: RequestInit
+): Promise {
+ const url = `${HAV_V2_BASE}${path}`;
+ if (process.env.NODE_ENV !== "production") {
+ console.log(`[HaV v2] GET ${url}`);
+ }
+
+ const res = await fetch(url, {
+ headers: { Accept: "application/json" },
+ signal: AbortSignal.timeout(HAV_TIMEOUT_MS),
+ ...init,
+ });
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => "");
+ console.warn(
+ `[HaV v2] ${res.status} ${res.statusText} for ${url}: ${text.slice(
+ 0,
+ 500
+ )}`
+ );
+ throw new HavHttpError("v2", res.status, res.statusText);
+ }
+
+ return res.json() as Promise;
+}
+
+/** Pull latest sample date (ISO string) from /bathing-waters/{id}/results */
+export async function getLatestSampleDate(id: string): Promise {
+ const k = key(`v2:results:${id}`);
+ const cached = cache.get(k);
+ if (cached !== undefined) return cached;
+
+ type MonitoringResult = { takenAt?: string | null };
+ type Results = { results?: MonitoringResult[] };
+
+ const data = await havV2Get(
+ `/bathing-waters/${encodeURIComponent(id)}/results`
+ );
+
+ const latest =
+ (data.results ?? [])
+ .map((r) => r.takenAt)
+ .filter((d): d is string => !!d)
+ .sort((a, b) => (a < b ? 1 : a > b ? -1 : 0))[0] ?? null;
+
+ cache.set(k, latest, 5 * 60 * 1000);
+ return latest;
+}
diff --git a/backend/src/lib/mailer.ts b/backend/src/lib/mailer.ts
new file mode 100644
index 0000000000..6d435af7a4
--- /dev/null
+++ b/backend/src/lib/mailer.ts
@@ -0,0 +1,41 @@
+// Thin Resend client via plain fetch β no SDK dependency for one endpoint.
+// Without RESEND_API_KEY the mailer logs instead of sending, so dev and CI
+// runs are safe by default.
+
+const RESEND_URL = "https://api.resend.com/emails";
+
+export interface Email {
+ to: string;
+ subject: string;
+ html: string;
+}
+
+export async function sendEmail(email: Email): Promise<{ sent: boolean }> {
+ const apiKey = process.env.RESEND_API_KEY;
+ // Resend's shared onboarding sender works for testing; set EMAIL_FROM to a
+ // verified domain sender before real users are on board.
+ const from = process.env.EMAIL_FROM ?? "Badlistan ";
+
+ if (!apiKey) {
+ console.log(
+ `[mailer] RESEND_API_KEY not set β would send to ${email.to}: ${email.subject}`
+ );
+ return { sent: false };
+ }
+
+ const res = await fetch(RESEND_URL, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ from, ...email }),
+ });
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => "");
+ console.error(`[mailer] Resend ${res.status}: ${text.slice(0, 300)}`);
+ return { sent: false };
+ }
+ return { sent: true };
+}
diff --git a/backend/src/lib/runWatchNotifications.ts b/backend/src/lib/runWatchNotifications.ts
new file mode 100644
index 0000000000..fa2e5171a9
--- /dev/null
+++ b/backend/src/lib/runWatchNotifications.ts
@@ -0,0 +1,114 @@
+// The daily watch notifier: fetch fresh HaV status for every watched beach,
+// diff against the stored snapshot, email the watchers about real changes,
+// then store the new snapshot. Invoked by the scheduled Netlify function
+// (see frontend/netlify/functions/notify-watchers.ts).
+
+import { connectDB } from "./db.js";
+import { havGet } from "./hav.js";
+import { Watch } from "../models/Watch.js";
+import { BeachState } from "../models/BeachState.js";
+import { User } from "../models/User.js";
+import { sendEmail } from "./mailer.js";
+import {
+ diffSnapshots,
+ snapshotFromDetail,
+ type BeachSnapshot,
+ type WatchEvent,
+} from "./watchEvents.js";
+
+function appOrigin(): string {
+ return (process.env.APP_ORIGIN ?? "http://localhost:5173").replace(/\/$/, "");
+}
+
+function eventEmailHtml(events: WatchEvent[]): string {
+ const [first] = events;
+ if (!first) throw new Error("eventEmailHtml requires at least one event");
+ const { name, beachId } = first;
+ const lines = events.map((e) => `${e.detail} `).join("");
+ const url = `${appOrigin()}/beach/${beachId}`;
+ return [
+ `Hej!
`,
+ `Det har hΓ€nt nΓ₯got vid ${name} , som du bevakar pΓ₯ Badlistan:
`,
+ ``,
+ `Se hela lΓ€get fΓΆr ${name}
`,
+ `Du fΓ₯r det hΓ€r mejlet fΓΆr att du bevakar badplatsen pΓ₯ Badlistan. ` +
+ `Avsluta bevakningen pΓ₯ badplatsens sida.
`,
+ ].join("\n");
+}
+
+export interface NotifierSummary {
+ beaches: number;
+ changed: number;
+ emailsSent: number;
+ errors: number;
+}
+
+export async function runWatchNotifications(): Promise {
+ await connectDB();
+ const beachIds: string[] = await Watch.distinct("beachId");
+ const summary: NotifierSummary = {
+ beaches: beachIds.length,
+ changed: 0,
+ emailsSent: 0,
+ errors: 0,
+ };
+
+ for (const beachId of beachIds) {
+ try {
+ // ttl 0: always fetch fresh β this runs once a day.
+ const detail = (await havGet(
+ `/detail/${encodeURIComponent(beachId)}`,
+ 0
+ )) as Record;
+ const next = snapshotFromDetail(beachId, detail);
+ const prevDoc = await BeachState.findOne({ beachId }).lean();
+ // The lean doc's optional fields must be normalized to the snapshot's
+ // explicit `| null` shape before diffing.
+ const prev: BeachSnapshot | null = prevDoc
+ ? {
+ beachId: prevDoc.beachId,
+ name: prevDoc.name,
+ advisory: prevDoc.advisory,
+ algalValue: prevDoc.algalValue ?? null,
+ algalText: prevDoc.algalText ?? null,
+ sampleDate: prevDoc.sampleDate ?? null,
+ classification: prevDoc.classification ?? null,
+ classificationText: prevDoc.classificationText ?? null,
+ }
+ : null;
+ const events = diffSnapshots(prev, next);
+
+ await BeachState.findOneAndUpdate({ beachId }, next, {
+ upsert: true,
+ setDefaultsOnInsert: true,
+ });
+
+ if (events.length === 0) continue;
+ summary.changed += 1;
+
+ const watches = await Watch.find({ beachId }).select("userId").lean();
+ const users = await User.find({
+ _id: { $in: watches.map((w) => w.userId) },
+ })
+ .select("email")
+ .lean();
+
+ const subject = `Badlistan: nytt lΓ€ge vid ${next.name}`;
+ const html = eventEmailHtml(events);
+ for (const user of users) {
+ const { sent } = await sendEmail({ to: user.email, subject, html });
+ if (sent) summary.emailsSent += 1;
+ }
+ } catch (err) {
+ // One broken beach must not stop the whole run.
+ summary.errors += 1;
+ console.error(`[notifier] failed for ${beachId}:`, err);
+ }
+ }
+
+ console.log(
+ `[notifier] beaches=${summary.beaches} changed=${summary.changed} ` +
+ `emails=${summary.emailsSent} errors=${summary.errors}`
+ );
+ return summary;
+}
diff --git a/backend/src/lib/sessionCookie.ts b/backend/src/lib/sessionCookie.ts
new file mode 100644
index 0000000000..60433c0608
--- /dev/null
+++ b/backend/src/lib/sessionCookie.ts
@@ -0,0 +1,32 @@
+import type { Response, CookieOptions } from "express";
+
+// Single source of truth for the auth session cookie. The cookie holds the
+// signed JWT; it is HttpOnly so JS (and therefore any XSS) can't read it.
+//
+// Topology A: the browser reaches the backend through the Netlify /api/* proxy,
+// so this is a first-party cookie on the app origin. No `domain` is set, which
+// keeps it host-only. SameSite=Lax blocks it on cross-site POSTs (CSRF defense)
+// while still allowing normal top-level navigations.
+export const SESSION_COOKIE = "bada_session";
+
+const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; // matches the JWT's 7d expiry
+
+function baseOptions(): CookieOptions {
+ const isProd = process.env.NODE_ENV === "production";
+ return {
+ httpOnly: true,
+ secure: isProd, // Secure in prod; relaxed so http://localhost dev works
+ sameSite: "lax",
+ path: "/",
+ };
+}
+
+export function setSessionCookie(res: Response, token: string): void {
+ res.cookie(SESSION_COOKIE, token, { ...baseOptions(), maxAge: SEVEN_DAYS_MS });
+}
+
+export function clearSessionCookie(res: Response): void {
+ // Must mirror the path/sameSite/secure used when setting, or the browser
+ // won't match and clear it.
+ res.clearCookie(SESSION_COOKIE, baseOptions());
+}
diff --git a/backend/src/lib/watchEvents.ts b/backend/src/lib/watchEvents.ts
new file mode 100644
index 0000000000..4d0247015c
--- /dev/null
+++ b/backend/src/lib/watchEvents.ts
@@ -0,0 +1,126 @@
+// Pure diff logic for the watch notifier: HaV detail β snapshot β events.
+// Kept free of I/O so it's trivially unit-testable.
+
+export interface BeachSnapshot {
+ beachId: string;
+ name: string;
+ algalValue: string | null;
+ algalText: string | null;
+ /** JSON-stringified dissuasion array ("[]" when none). */
+ advisory: string;
+ /** HaV v1 sampleDate (epoch ms) as string, null if never sampled. */
+ sampleDate: string | null;
+ classification: string | null;
+ classificationText: string | null;
+}
+
+export type WatchEventType =
+ | "algal"
+ | "advisory"
+ | "advisoryLifted"
+ | "newSample"
+ | "classification";
+
+export interface WatchEvent {
+ type: WatchEventType;
+ beachId: string;
+ name: string;
+ /** Human-readable Swedish detail line for the notification email. */
+ detail: string;
+}
+
+function asString(v: unknown): string | null {
+ if (v == null) return null;
+ return String(v);
+}
+
+/** Normalize a raw HaV v1 detail payload into a comparable snapshot. */
+export function snapshotFromDetail(
+ beachId: string,
+ detail: Record
+): BeachSnapshot {
+ const dissuasion = detail.dissuasion;
+ const advisory = Array.isArray(dissuasion)
+ ? JSON.stringify(dissuasion)
+ : asString(dissuasion) ?? "[]";
+ return {
+ beachId,
+ name: asString(detail.locationName) ?? beachId,
+ algalValue: asString(detail.algalValue),
+ algalText: asString(detail.algalText),
+ advisory,
+ sampleDate: asString(detail.sampleDate),
+ classification: asString(detail.classification),
+ classificationText: asString(detail.classificationText),
+ };
+}
+
+function advisoryActive(advisory: string): boolean {
+ return advisory !== "[]" && advisory !== "" && advisory !== "null";
+}
+
+/**
+ * Events worth emailing about between two snapshots.
+ * A null `prev` (first time the beach is seen) yields NO events β the first
+ * run establishes a baseline instead of spamming everyone with "changes".
+ */
+export function diffSnapshots(
+ prev: BeachSnapshot | null,
+ next: BeachSnapshot
+): WatchEvent[] {
+ if (!prev) return [];
+ const events: WatchEvent[] = [];
+ const base = { beachId: next.beachId, name: next.name };
+
+ const prevAdvisory = advisoryActive(prev.advisory);
+ const nextAdvisory = advisoryActive(next.advisory);
+ if (!prevAdvisory && nextAdvisory) {
+ events.push({
+ ...base,
+ type: "advisory",
+ detail: "Kommunen avrΓ₯der nu frΓ₯n bad hΓ€r.",
+ });
+ } else if (prevAdvisory && !nextAdvisory) {
+ events.push({
+ ...base,
+ type: "advisoryLifted",
+ detail: "AvrΓ₯dan frΓ₯n bad har hΓ€vts.",
+ });
+ }
+
+ if (prev.algalValue !== next.algalValue && next.algalText) {
+ events.push({
+ ...base,
+ type: "algal",
+ detail: `Nytt alglΓ€ge: ${next.algalText}.`,
+ });
+ }
+
+ if (prev.sampleDate !== next.sampleDate && next.sampleDate) {
+ const when = Number(next.sampleDate);
+ const dateStr = Number.isFinite(when)
+ ? new Intl.DateTimeFormat("sv-SE", {
+ dateStyle: "long",
+ timeZone: "Europe/Stockholm",
+ }).format(new Date(when))
+ : "nyligen";
+ events.push({
+ ...base,
+ type: "newSample",
+ detail: `Ett nytt vattenprov togs ${dateStr}.`,
+ });
+ }
+
+ if (
+ prev.classification !== next.classification &&
+ next.classificationText
+ ) {
+ events.push({
+ ...base,
+ type: "classification",
+ detail: `Ny klassificering: ${next.classificationText}.`,
+ });
+ }
+
+ return events;
+}
diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts
new file mode 100644
index 0000000000..8a1b4577a0
--- /dev/null
+++ b/backend/src/middleware/auth.ts
@@ -0,0 +1,53 @@
+import { Request, Response, NextFunction } from "express";
+import jwt from "jsonwebtoken";
+import { clearSessionCookie, SESSION_COOKIE } from "../lib/sessionCookie.js";
+import { connectDB } from "../lib/db.js";
+import { User } from "../models/User.js";
+
+export interface AuthedRequest extends Request {
+ user: { sub: string; email: string }; // β non-optional
+}
+
+/**
+ * Resolve the JWT from the httpOnly session cookie. The app keeps the session
+ * token out of JavaScript; protected routes should only accept this cookie.
+ */
+function readToken(req: Request): string | null {
+ const cookieToken = (req as Request & { cookies?: Record })
+ .cookies?.[SESSION_COOKIE];
+ if (cookieToken) return cookieToken;
+ return null;
+}
+
+export async function requireAuth(
+ req: Request,
+ res: Response,
+ next: NextFunction
+) {
+ try {
+ const token = readToken(req);
+ if (!token) return res.status(401).json({ error: "NoAuthToken" });
+
+ const payload = jwt.verify(token, process.env.JWT_SECRET!, {
+ algorithms: ["HS256"],
+ }) as {
+ sub: string;
+ email: string;
+ };
+ if (!payload.sub || !payload.email) {
+ return res.status(401).json({ error: "InvalidToken" });
+ }
+
+ await connectDB();
+ const exists = await User.exists({ _id: payload.sub });
+ if (!exists) {
+ clearSessionCookie(res);
+ return res.status(401).json({ error: "InvalidToken" });
+ }
+
+ (req as AuthedRequest).user = payload; // set strongly-typed user
+ next();
+ } catch {
+ return res.status(401).json({ error: "InvalidToken" });
+ }
+}
diff --git a/backend/src/middleware/rateLimit.ts b/backend/src/middleware/rateLimit.ts
new file mode 100644
index 0000000000..199f1a17eb
--- /dev/null
+++ b/backend/src/middleware/rateLimit.ts
@@ -0,0 +1,120 @@
+import type { Request, RequestHandler } from "express";
+import rateLimit from "express-rate-limit";
+import { Ratelimit } from "@upstash/ratelimit";
+import { Redis } from "@upstash/redis";
+import jwt from "jsonwebtoken";
+
+function header(req: Request, name: string): string | undefined {
+ const v = req.headers[name];
+ return Array.isArray(v) ? v[0] : v;
+}
+
+// Did this request genuinely come through our Netlify proxy? When PROXY_SIGNING_KEY
+// is set, Netlify signs every proxied request with it (JWS in the `x-nf-sign`
+// header, HS256, iss "netlify") β see netlify.toml's `signed`. A direct caller
+// hitting the Vercel backend can't produce a valid signature, so this is how we
+// tell trustworthy proxied traffic from spoofable direct traffic.
+function isSignedByNetlify(req: Request): boolean {
+ const secret = process.env.PROXY_SIGNING_KEY;
+ if (!secret) return false;
+ const token = header(req, "x-nf-sign");
+ if (!token) return false;
+ try {
+ jwt.verify(token, secret, { algorithms: ["HS256"], issuer: "netlify" });
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+// Resolve the real client IP to throttle on. Under topology A the browser
+// reaches us through the Netlify /api/* proxy, so req.ip / X-Forwarded-For
+// collapse to Netlify's egress IP β keying on that would throttle ALL users as
+// one. Netlify forwards the true client IP in x-nf-client-connection-ip.
+//
+// Hardened mode (PROXY_SIGNING_KEY set): trust that forwarded IP only when the
+// request carries a valid Netlify signature; otherwise fall back to req.ip (the
+// connecting IP, which can't be spoofed) so a direct caller can't forge a
+// per-request identity to evade throttling.
+//
+// Transitional mode (key unset): best-effort β trust the forwarded IP so real
+// proxied users aren't lumped under Netlify's egress IP. Forgeable; set
+// PROXY_SIGNING_KEY in Netlify + the backend to harden.
+export function clientKey(req: Request): string {
+ const fallback = req.ip || req.socket.remoteAddress || "unknown";
+ const fwd = header(req, "x-nf-client-connection-ip");
+ if (!fwd) return fallback;
+
+ // Running AS a Netlify Function: the platform itself sets
+ // x-nf-client-connection-ip on every request (client-supplied values are
+ // overwritten), so the header is trustworthy without a proxy signature.
+ if (process.env.NETLIFY === "true") return fwd;
+
+ if (process.env.PROXY_SIGNING_KEY) {
+ return isSignedByNetlify(req) ? fwd : fallback;
+ }
+ return fwd; // transitional best-effort
+}
+
+// Throttle credential endpoints to slow brute-force and signup abuse:
+// at most LIMIT attempts per WINDOW from a single client.
+const LIMIT = 10;
+const WINDOW_MS = 15 * 60 * 1000; // 15 minutes
+const WINDOW = "15 m";
+
+// In-memory limiter β used for local dev and tests. It works on a single
+// long-running process but NOT across serverless instances, which is why
+// production uses the shared Upstash store below.
+const memoryLimiter = rateLimit({
+ windowMs: WINDOW_MS,
+ limit: LIMIT,
+ standardHeaders: "draft-7",
+ legacyHeaders: false,
+ message: { error: "TooManyRequests" },
+ keyGenerator: (req) => clientKey(req),
+ // Don't throttle the automated test suite (many auth calls from one IP).
+ skip: () => process.env.NODE_ENV === "test",
+});
+
+function buildAuthRateLimiter(): RequestHandler {
+ const url = process.env.UPSTASH_REDIS_REST_URL;
+ const token = process.env.UPSTASH_REDIS_REST_TOKEN;
+
+ // No shared store configured β fall back to the in-memory limiter.
+ if (!url || !token) {
+ if (process.env.NODE_ENV === "production") {
+ console.warn(
+ "[WARN] UPSTASH_REDIS_REST_URL/TOKEN not set β auth rate limiting is " +
+ "in-memory only and not enforced across serverless instances."
+ );
+ }
+ return memoryLimiter;
+ }
+
+ // Shared store: the request count lives in Upstash Redis, so it stays
+ // consistent no matter which short-lived serverless instance handles a
+ // given request.
+ const ratelimit = new Ratelimit({
+ redis: new Redis({ url, token }),
+ limiter: Ratelimit.slidingWindow(LIMIT, WINDOW),
+ prefix: "bada-auth",
+ });
+
+ return async (req, res, next) => {
+ const key = clientKey(req);
+ try {
+ const { success } = await ratelimit.limit(key);
+ if (!success) {
+ return res.status(429).json({ error: "TooManyRequests" });
+ }
+ return next();
+ } catch (err) {
+ // If the store is unreachable, fail open so a Redis hiccup can't lock
+ // every user out of login. Protection is briefly degraded, not the app.
+ console.error("RATE_LIMIT_ERR", err);
+ return next();
+ }
+ };
+}
+
+export const authRateLimiter: RequestHandler = buildAuthRateLimiter();
diff --git a/backend/src/models/BeachState.ts b/backend/src/models/BeachState.ts
new file mode 100644
index 0000000000..801eab141d
--- /dev/null
+++ b/backend/src/models/BeachState.ts
@@ -0,0 +1,26 @@
+import mongoose, { Schema, model, InferSchemaType, Model } from "mongoose";
+
+// Last-seen official status per watched beach. The daily notifier diffs the
+// fresh HaV detail against this snapshot to detect changes worth emailing
+// about, then overwrites it. Only beaches somebody watches get a document.
+const beachStateSchema = new Schema(
+ {
+ beachId: { type: String, required: true, unique: true },
+ name: { type: String, default: "" },
+ algalValue: { type: String, default: null },
+ algalText: { type: String, default: null },
+ /** JSON-stringified dissuasion array from HaV ("[]" when none). */
+ advisory: { type: String, default: "[]" },
+ /** HaV v1 sampleDate as a string (epoch ms), null if never sampled. */
+ sampleDate: { type: String, default: null },
+ classification: { type: String, default: null },
+ classificationText: { type: String, default: null },
+ },
+ { timestamps: true }
+);
+
+export type BeachStateDoc = InferSchemaType;
+
+export const BeachState: Model =
+ (mongoose.models.BeachState as Model) ??
+ model("BeachState", beachStateSchema);
diff --git a/backend/src/models/Favorite.ts b/backend/src/models/Favorite.ts
new file mode 100644
index 0000000000..40b2162b6f
--- /dev/null
+++ b/backend/src/models/Favorite.ts
@@ -0,0 +1,28 @@
+import mongoose, { Schema, model, InferSchemaType, Model } from "mongoose";
+
+const favoriteSchema = new Schema(
+ {
+ userId: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ required: true,
+ index: true,
+ },
+ // HaV beach identifier (string)
+ beachId: { type: String, required: true, index: true },
+ // optional user note
+ note: { type: String, default: "" },
+ // for drag & drop ordering (lower = earlier)
+ order: { type: Number, default: 0 },
+ },
+ { timestamps: true }
+);
+
+// Prevent duplicates: one user can favorite each beach only once
+favoriteSchema.index({ userId: 1, beachId: 1 }, { unique: true });
+
+export type FavoriteDoc = InferSchemaType;
+
+export const Favorite: Model =
+ (mongoose.models.Favorite as Model) ??
+ model("Favorite", favoriteSchema);
diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts
new file mode 100644
index 0000000000..de83c6cade
--- /dev/null
+++ b/backend/src/models/User.ts
@@ -0,0 +1,24 @@
+import mongoose, { Schema, model, InferSchemaType, Model } from "mongoose";
+
+const userSchema = new Schema(
+ {
+ email: {
+ type: String,
+ unique: true,
+ required: true,
+ index: true,
+ lowercase: true,
+ trim: true,
+ },
+ passwordHash: { type: String, required: true },
+ },
+ { timestamps: true }
+);
+
+// Infer the document shape from the schema
+export type UserDoc = InferSchemaType;
+
+// Always return a single, strongly-typed Model (no union)
+export const User: Model =
+ (mongoose.models.User as Model) ??
+ model("User", userSchema);
diff --git a/backend/src/models/Watch.ts b/backend/src/models/Watch.ts
new file mode 100644
index 0000000000..d33371c96d
--- /dev/null
+++ b/backend/src/models/Watch.ts
@@ -0,0 +1,30 @@
+import mongoose, { Schema, model, InferSchemaType, Model } from "mongoose";
+
+// A user watching one beach: the daily notifier emails them when the beach's
+// official status changes. Capped at a generous abuse limit (enforced in the
+// route, not the schema).
+const watchSchema = new Schema(
+ {
+ userId: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ required: true,
+ index: true,
+ },
+ beachId: {
+ type: String,
+ required: true,
+ match: /^SE[A-Z0-9]{16}$/,
+ },
+ },
+ { timestamps: true }
+);
+
+watchSchema.index({ userId: 1, beachId: 1 }, { unique: true });
+watchSchema.index({ beachId: 1 });
+
+export type WatchDoc = InferSchemaType;
+
+export const Watch: Model =
+ (mongoose.models.Watch as Model) ??
+ model("Watch", watchSchema);
diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts
new file mode 100644
index 0000000000..2237ac3851
--- /dev/null
+++ b/backend/src/routes/auth.ts
@@ -0,0 +1,120 @@
+import { Router } from "express";
+import { z } from "zod";
+import bcrypt from "bcryptjs";
+import jwt from "jsonwebtoken";
+import { User } from "../models/User.js";
+import { Favorite } from "../models/Favorite.js";
+import { connectDB } from "../lib/db.js";
+import { requireAuth, AuthedRequest } from "../middleware/auth.js";
+import { authRateLimiter } from "../middleware/rateLimit.js";
+import {
+ setSessionCookie,
+ clearSessionCookie,
+} from "../lib/sessionCookie.js";
+
+export const authRouter = Router();
+
+authRouter.get("/ping", (_req, res) => res.json({ ok: true, from: "auth" }));
+
+const zCreds = z.object({
+ email: z.string().trim().toLowerCase().email(),
+ password: z.string().min(8, "Password must be at least 8 characters"),
+});
+
+const DUMMY_PASSWORD_HASH =
+ "$2b$12$aD18laJETvQ5XOlTjAVZfe.YbWSo4SM3MpRAZkJehutDZYo152ilu";
+
+// POST /api/auth/register
+authRouter.post("/register", authRateLimiter, async (req, res) => {
+ try {
+ await connectDB();
+
+ const parsed = zCreds.safeParse(req.body);
+ if (!parsed.success)
+ return res
+ .status(400)
+ .json({ error: "InvalidBody", details: parsed.error.flatten() });
+
+ const { email, password } = parsed.data;
+
+ const exists = await User.findOne({ email });
+ if (exists) return res.status(409).json({ error: "EmailInUse" });
+
+ const passwordHash = await bcrypt.hash(password, 12);
+ await User.create({ email, passwordHash });
+
+ return res.status(201).json({ ok: true });
+ } catch (err: any) {
+ if (err?.code === 11000) {
+ return res.status(409).json({ error: "EmailInUse" });
+ }
+ console.error("REGISTER_ERR", err);
+ return res.status(500).json({ error: "InternalServerError" });
+ }
+});
+
+// POST /api/auth/login
+authRouter.post("/login", authRateLimiter, async (req, res) => {
+ try {
+ await connectDB();
+
+ const parsed = zCreds.safeParse(req.body);
+ if (!parsed.success)
+ return res
+ .status(400)
+ .json({ error: "InvalidBody", details: parsed.error.flatten() });
+
+ const { email, password } = parsed.data;
+
+ const user = await User.findOne({ email });
+ if (!user) {
+ await bcrypt.compare(password, DUMMY_PASSWORD_HASH);
+ return res.status(401).json({ error: "InvalidCredentials" });
+ }
+
+ const ok = await bcrypt.compare(password, user.passwordHash);
+ if (!ok) return res.status(401).json({ error: "InvalidCredentials" });
+
+ const token = jwt.sign(
+ { sub: String(user._id), email },
+ process.env.JWT_SECRET!,
+ { expiresIn: "7d", algorithm: "HS256" }
+ );
+ setSessionCookie(res, token);
+ return res.json({ ok: true });
+ } catch (err) {
+ console.error("LOGIN_ERR", err);
+ return res.status(500).json({ error: "InternalServerError" });
+ }
+});
+
+// POST /api/auth/logout
+// Clear the session cookie. Intentionally does not require a valid token so a
+// user with an expired/garbled cookie can still log out cleanly.
+authRouter.post("/logout", (_req, res) => {
+ clearSessionCookie(res);
+ return res.json({ ok: true });
+});
+
+// DELETE /api/auth/me
+// Permanently delete the authenticated user and all of their data (GDPR:
+// right to erasure). Favorites are removed first so no orphaned rows remain.
+authRouter.delete("/me", requireAuth, async (req, res) => {
+ try {
+ await connectDB();
+ const userId = (req as AuthedRequest).user.sub;
+
+ const deleted = await User.findByIdAndDelete(userId);
+ if (!deleted) {
+ clearSessionCookie(res);
+ return res.status(404).json({ error: "NotFound" });
+ }
+
+ await Favorite.deleteMany({ userId });
+ clearSessionCookie(res);
+ return res.json({ ok: true });
+ } catch (err) {
+ console.error("DELETE_ACCOUNT_ERR", err);
+ return res.status(500).json({ error: "InternalServerError" });
+ }
+});
diff --git a/backend/src/routes/beaches.ts b/backend/src/routes/beaches.ts
new file mode 100644
index 0000000000..a6d811d5d1
--- /dev/null
+++ b/backend/src/routes/beaches.ts
@@ -0,0 +1,58 @@
+import { Router } from "express";
+import { havGet, getLatestSampleDate } from "../lib/hav.js";
+
+export const beachesRouter = Router();
+
+/**
+ * GET /api/beaches
+ * Proxies HaV "feature" endpoint and returns (likely) GeoJSON.
+ */
+beachesRouter.get("/beaches", async (_req, res, next) => {
+ try {
+ // The beach list (locations + names) changes a few times a year, so cache
+ // it for an hour rather than re-hitting HaV every 5 minutes.
+ const data = await havGet("/feature/?format=json", 60 * 60 * 1000);
+ res.json(data);
+ } catch (err) {
+ next(err);
+ }
+});
+
+/**
+ * GET /api/beaches/:id
+ * Proxies HaV v1 detail and augments with latestSampleDate.
+ * Prefers v1.sampleDate (epoch ms); falls back to v2 results when needed.
+ */
+beachesRouter.get("/beaches/:id", async (req, res, next) => {
+ try {
+ const { id } = req.params;
+
+ // v1 detail
+ const detail = await havGet(
+ `/detail/${encodeURIComponent(id)}`,
+ 5 * 60 * 1000
+ );
+
+ // prefer v1 sampleDate (epoch ms)
+ let latestSampleDate: string | null = null;
+ const v1Ms = (detail as any)?.sampleDate;
+ if (typeof v1Ms === "number" && isFinite(v1Ms)) {
+ latestSampleDate = new Date(v1Ms).toISOString();
+ } else {
+ // fallback to v2
+ try {
+ latestSampleDate = await getLatestSampleDate(id);
+ } catch (e) {
+ // don't fail the whole request if v2 errors; just leave null
+ console.warn(
+ "[/beaches/:id] v2 fallback failed:",
+ (e as Error)?.message
+ );
+ }
+ }
+
+ res.json({ ...detail, latestSampleDate });
+ } catch (err) {
+ next(err);
+ }
+});
diff --git a/backend/src/routes/dbCheck.ts b/backend/src/routes/dbCheck.ts
new file mode 100644
index 0000000000..4893c87614
--- /dev/null
+++ b/backend/src/routes/dbCheck.ts
@@ -0,0 +1,29 @@
+import { Router, Request, Response } from "express";
+import mongoose from "mongoose";
+import { connectDB } from "../lib/db.js";
+
+export const dbCheckRouter = Router();
+
+dbCheckRouter.get("/db-check", async (_req: Request, res: Response) => {
+ try {
+ // Ensure we attempt a connection before reporting
+ await connectDB();
+ const state = mongoose.connection.readyState; // 0=disconnected, 1=connected, 2=connecting, 3=disconnecting
+
+ res.json({
+ ok: state === 1,
+ state,
+ message:
+ state === 1
+ ? "MongoDB is connected"
+ : state === 2
+ ? "MongoDB is connecting..."
+ : state === 3
+ ? "MongoDB is disconnecting..."
+ : "MongoDB is disconnected",
+ });
+ } catch (err) {
+ console.error("DB check error:", err);
+ res.status(500).json({ ok: false, error: "DB check failed" });
+ }
+});
diff --git a/backend/src/routes/favorites.ts b/backend/src/routes/favorites.ts
new file mode 100644
index 0000000000..cefeee0ca9
--- /dev/null
+++ b/backend/src/routes/favorites.ts
@@ -0,0 +1,180 @@
+import { Router } from "express";
+import { z } from "zod";
+import { Favorite } from "../models/Favorite.js";
+import { requireAuth, AuthedRequest } from "../middleware/auth.js";
+import { connectDB } from "../lib/db.js";
+import mongoose from "mongoose";
+import type { AnyBulkWriteOperation } from "mongoose";
+
+export const favoritesRouter = Router();
+
+const zBeachId = z
+ .string()
+ .trim()
+ .regex(/^SE[A-Z0-9]{16}$/, "Invalid beach id");
+
+const zCreate = z.object({
+ beachId: zBeachId,
+ note: z.string().max(500).optional(),
+});
+
+// Ensure DB connection for every favorites op.
+// This router is mounted at /api/favorites, so this only runs for
+// favorites traffic β not every /api request.
+favoritesRouter.use(async (_req, _res, next) => {
+ try {
+ await connectDB();
+ next();
+ } catch (e) {
+ next(e);
+ }
+});
+
+/**
+ * GET /api/favorites
+ * List current user's favorites (stable order)
+ */
+favoritesRouter.get("/", requireAuth, async (req, res) => {
+ const userId = (req as AuthedRequest).user.sub;
+ const items = await Favorite.find({ userId })
+ .sort({ order: 1, updatedAt: 1, _id: 1 }) // stable sort
+ .lean();
+ res.json(items);
+});
+
+/**
+ * POST /api/favorites
+ */
+favoritesRouter.post("/", requireAuth, async (req, res) => {
+ const parsed = zCreate.safeParse(req.body);
+ if (!parsed.success) {
+ // keep the existing error style
+ // @ts-ignore - z.treeifyError is available in the codebase
+ return res
+ .status(400)
+ .json({ error: "InvalidBody", details: z.treeifyError(parsed.error) });
+ }
+
+ const { beachId, note } = parsed.data;
+ const userId = (req as AuthedRequest).user.sub;
+
+ try {
+ const last = await Favorite.findOne({ userId }, { order: 1 })
+ .sort({ order: -1 })
+ .lean();
+ const created = await Favorite.create({
+ userId,
+ beachId,
+ note: note ?? "",
+ order: (last?.order ?? -1) + 1,
+ });
+ return res.status(201).json(created);
+ } catch (err: any) {
+ if (err?.code === 11000) {
+ return res.status(409).json({ error: "AlreadyFavorited" });
+ }
+ throw err;
+ }
+});
+
+/**
+ * DELETE /api/favorites/:id
+ */
+favoritesRouter.delete("/:id", requireAuth, async (req, res) => {
+ const userId = (req as AuthedRequest).user.sub;
+
+ const id = req.params.id as string | undefined;
+ if (!id) return res.status(400).json({ error: "MissingId" });
+
+ if (!mongoose.Types.ObjectId.isValid(id)) {
+ return res.status(400).json({ error: "InvalidId" });
+ }
+
+ const deleted = await Favorite.findOneAndDelete({ _id: id, userId });
+ if (!deleted) return res.status(404).json({ error: "NotFound" });
+ res.json({ ok: true });
+});
+
+/**
+ * DELETE /api/favorites/by-beach/:beachId
+ */
+favoritesRouter.delete(
+ "/by-beach/:beachId",
+ requireAuth,
+ async (req, res) => {
+ const userId = (req as AuthedRequest).user.sub;
+ const { beachId } = req.params;
+
+ const deleted = await Favorite.findOneAndDelete({ beachId, userId });
+ if (!deleted) return res.status(404).json({ error: "NotFound" });
+ res.json({ ok: true });
+ }
+);
+
+/**
+ * PATCH /api/favorites/reorder
+ * Persist custom order. Accepts: { order: string[] } where each string is a beachId.
+ * Non-owned or unknown ids are ignored. Any favorites not included are appended after.
+ */
+favoritesRouter.patch("/reorder", requireAuth, async (req, res) => {
+ try {
+ const userId = (req as AuthedRequest).user.sub;
+
+ const schema = z.object({
+ order: z.array(zBeachId).min(0),
+ });
+
+ const parsed = schema.safeParse(req.body);
+ if (!parsed.success) {
+ return res
+ .status(400)
+ .json({ error: "InvalidBody", details: parsed.error.flatten() });
+ }
+
+ // Deduplicate ids while preserving first occurrence order
+ const seen = new Set();
+ const order = parsed.data.order.filter((id) => {
+ if (seen.has(id)) return false;
+ seen.add(id);
+ return true;
+ });
+
+ // Fetch user's current favorites
+ const favs = await Favorite.find({ userId }, { beachId: 1 }).lean();
+ const currentIds = new Set(favs.map((f) => f.beachId));
+
+ // Keep only valid (owned) ids, preserve payload order
+ const sanitized = order.filter((id) => currentIds.has(id));
+
+ // Bulk update for provided ids
+ const ops: AnyBulkWriteOperation[] = sanitized.map((beachId, idx) => ({
+ updateOne: {
+ filter: { userId, beachId },
+ update: { $set: { order: idx } },
+ },
+ }));
+
+ // Append any missing favorites not included in payload
+ const missing = favs
+ .map((f) => f.beachId)
+ .filter((id) => !sanitized.includes(id));
+
+ ops.push(
+ ...missing.map((beachId, addIdx) => ({
+ updateOne: {
+ filter: { userId, beachId },
+ update: { $set: { order: sanitized.length + addIdx } },
+ },
+ }))
+ );
+
+ if (ops.length > 0) {
+ await Favorite.bulkWrite(ops, { ordered: false });
+ }
+
+ return res.status(204).end();
+ } catch (err) {
+ console.error("REORDER_ERR", err);
+ return res.status(500).json({ error: "InternalServerError" });
+ }
+});
diff --git a/backend/src/routes/health.ts b/backend/src/routes/health.ts
new file mode 100644
index 0000000000..5455fb4897
--- /dev/null
+++ b/backend/src/routes/health.ts
@@ -0,0 +1,13 @@
+import { Router, Request, Response } from "express";
+
+const router = Router();
+
+router.get("/health", async (_req: Request, res: Response) => {
+ console.log("Hit /api/health");
+ res.json({
+ ok: true,
+ timestamp: new Date().toISOString(),
+ });
+});
+
+export { router as healthRouter };
diff --git a/backend/src/routes/watches.ts b/backend/src/routes/watches.ts
new file mode 100644
index 0000000000..2f89017d88
--- /dev/null
+++ b/backend/src/routes/watches.ts
@@ -0,0 +1,89 @@
+import { Router } from "express";
+import { z } from "zod";
+import { requireAuth, AuthedRequest } from "../middleware/auth.js";
+import { connectDB } from "../lib/db.js";
+import { Watch } from "../models/Watch.js";
+
+export const watchesRouter = Router();
+
+// Generous per-account cap β abuse guard for the mail notifier, not a product
+// tier. Everything in Badlistan is free.
+const WATCH_LIMIT = 20;
+
+const zBeachId = z
+ .string()
+ .trim()
+ .regex(/^SE[A-Z0-9]{16}$/, "Invalid beach id");
+
+watchesRouter.use(async (_req, _res, next) => {
+ try {
+ await connectDB();
+ next();
+ } catch (e) {
+ next(e);
+ }
+});
+
+/** GET /api/watches β the signed-in user's watched beaches. */
+watchesRouter.get("/", requireAuth, async (req, res, next) => {
+ try {
+ const userId = (req as AuthedRequest).user.sub;
+ const items = await Watch.find({ userId })
+ .sort({ createdAt: 1 })
+ .select("beachId createdAt")
+ .lean();
+ res.json({ items });
+ } catch (err) {
+ next(err);
+ }
+});
+
+/**
+ * POST /api/watches { beachId } β start watching a beach.
+ * 409 WatchLimitReached at the abuse cap; the client explains the limit.
+ */
+watchesRouter.post("/", requireAuth, async (req, res, next) => {
+ try {
+ const userId = (req as AuthedRequest).user.sub;
+ const parsed = z.object({ beachId: zBeachId }).safeParse(req.body);
+ if (!parsed.success) {
+ return res.status(400).json({ error: "InvalidBody" });
+ }
+ const { beachId } = parsed.data;
+
+ // Re-adding an already-watched beach is a no-op, never a quota question.
+ const already = await Watch.exists({ userId, beachId });
+ if (!already) {
+ const existing = await Watch.countDocuments({ userId });
+ if (existing >= WATCH_LIMIT) {
+ return res
+ .status(409)
+ .json({ error: "WatchLimitReached", limit: WATCH_LIMIT });
+ }
+ }
+
+ await Watch.updateOne(
+ { userId, beachId },
+ { $setOnInsert: { userId, beachId } },
+ { upsert: true }
+ );
+ return res.status(201).json({ beachId });
+ } catch (err) {
+ next(err);
+ }
+});
+
+/** DELETE /api/watches/:beachId β stop watching. Idempotent. */
+watchesRouter.delete("/:beachId", requireAuth, async (req, res, next) => {
+ try {
+ const userId = (req as AuthedRequest).user.sub;
+ const parsed = zBeachId.safeParse(req.params.beachId);
+ if (!parsed.success) {
+ return res.status(400).json({ error: "InvalidBody" });
+ }
+ await Watch.deleteOne({ userId, beachId: parsed.data });
+ return res.status(204).end();
+ } catch (err) {
+ next(err);
+ }
+});
diff --git a/backend/src/server.ts b/backend/src/server.ts
new file mode 100644
index 0000000000..ab9a44e7b6
--- /dev/null
+++ b/backend/src/server.ts
@@ -0,0 +1,25 @@
+// backend/src/server.ts
+import dotenv from "dotenv";
+
+dotenv.config({ path: ".env.local" });
+dotenv.config();
+
+// Dynamic import so env vars are loaded BEFORE app modules evaluate β a static
+// `import app from "./index.js"` is hoisted above dotenv.config(), which left
+// e.g. HAV_BASE_URL undefined for modules that read process.env at load time.
+const { default: app } = await import("./index.js");
+
+const port = Number(process.env.PORT) || 3000;
+
+app.listen(port, () => {
+ console.log(`API listening on http://localhost:${port}`);
+ const allowed = (process.env.CORS_ORIGIN || process.env.ALLOWED_ORIGIN || "")
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean);
+ console.log(
+ allowed.length
+ ? `CORS allowed origins: ${allowed.join(", ")}`
+ : "CORS: no origins configured β permissive (dev)"
+ );
+});
diff --git a/backend/src/swagger.ts b/backend/src/swagger.ts
new file mode 100644
index 0000000000..a86554f5b2
--- /dev/null
+++ b/backend/src/swagger.ts
@@ -0,0 +1,725 @@
+// backend/src/swagger.ts
+import swaggerUi from "swagger-ui-express";
+import { Router } from "express";
+
+export const swaggerDocument = {
+ openapi: "3.0.3",
+ info: {
+ title: "Badlistan API",
+ version: "1.0.0",
+ description: `
+API for Badlistan β EU-classified bathing waters in Sweden.
+
+This API provides access to EU-classified bathing water data from the Swedish Agency for Marine and Water Management (HaV),
+along with user authentication and favorites management.
+
+## Authentication
+ Protected endpoints use the \`bada_session\` httpOnly session cookie set by \`POST /auth/login\`.
+ `,
+ contact: {
+ name: "Badlistan Project",
+ url: "https://github.com/govargas/bada",
+ },
+ license: {
+ name: "MIT",
+ url: "https://opensource.org/licenses/MIT",
+ },
+ },
+ servers: [
+ {
+ url: "/api",
+ description: "API Base URL",
+ },
+ ],
+ tags: [
+ { name: "Health", description: "Health check endpoints" },
+ { name: "Auth", description: "Authentication endpoints" },
+ { name: "Beaches", description: "Beach data from HaV API" },
+ { name: "Favorites", description: "User favorites management (requires auth)" },
+ ],
+ paths: {
+ "/health": {
+ get: {
+ tags: ["Health"],
+ summary: "Health check",
+ description: "Returns the health status of the API",
+ responses: {
+ 200: {
+ description: "API is healthy",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ timestamp: { type: "string", format: "date-time" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/auth/register": {
+ post: {
+ tags: ["Auth"],
+ summary: "Register a new user",
+ description: "Create a new user account with email and password",
+ requestBody: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ required: ["email", "password"],
+ properties: {
+ email: { type: "string", format: "email", example: "user@example.com" },
+ password: { type: "string", minLength: 8, example: "password123" },
+ },
+ },
+ },
+ },
+ },
+ responses: {
+ 201: {
+ description: "User created successfully",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ },
+ },
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request body",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 409: {
+ description: "Email already in use",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/auth/login": {
+ post: {
+ tags: ["Auth"],
+ summary: "Login",
+ description: "Authenticate with email and password. Sets the httpOnly session cookie.",
+ requestBody: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ required: ["email", "password"],
+ properties: {
+ email: { type: "string", format: "email", example: "user@example.com" },
+ password: { type: "string", example: "password123" },
+ },
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ description: "Login successful",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ },
+ },
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request body",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 401: {
+ description: "Invalid credentials",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/auth/logout": {
+ post: {
+ tags: ["Auth"],
+ summary: "Logout",
+ description: "Clears the session cookie.",
+ responses: {
+ 200: {
+ description: "Logged out",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/auth/ping": {
+ get: {
+ tags: ["Auth"],
+ summary: "Auth router health check",
+ responses: {
+ 200: {
+ description: "Auth router is reachable",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ from: { type: "string", example: "auth" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/auth/me": {
+ get: {
+ tags: ["Auth"],
+ summary: "Get current user",
+ description: "Returns the authenticated user's information",
+ security: [{ cookieAuth: [] }],
+ responses: {
+ 200: {
+ description: "User information",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ user: {
+ type: "object",
+ properties: {
+ sub: { type: "string", description: "User ID" },
+ email: { type: "string", format: "email" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ delete: {
+ tags: ["Auth"],
+ summary: "Delete current user",
+ description: "Deletes the authenticated user and their saved favorites.",
+ security: [{ cookieAuth: [] }],
+ responses: {
+ 200: {
+ description: "Account deleted",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 404: {
+ description: "User not found",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/protected/ping": {
+ get: {
+ tags: ["Auth"],
+ summary: "Protected route health check",
+ security: [{ cookieAuth: [] }],
+ responses: {
+ 200: {
+ description: "Authenticated request accepted",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ user: {
+ type: "object",
+ properties: {
+ sub: { type: "string" },
+ email: { type: "string", format: "email" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/health-direct": {
+ get: {
+ tags: ["Health"],
+ summary: "Direct health check",
+ responses: {
+ 200: {
+ description: "API is reachable",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ via: { type: "string", example: "direct" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/beaches": {
+ get: {
+ tags: ["Beaches"],
+ summary: "List all beaches",
+ description: "Returns GeoJSON data for all EU-classified beaches in Sweden from HaV API",
+ responses: {
+ 200: {
+ description: "GeoJSON FeatureCollection of beaches",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ description: "GeoJSON FeatureCollection",
+ },
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/beaches/{id}": {
+ get: {
+ tags: ["Beaches"],
+ summary: "Get beach details",
+ description: "Returns detailed information about a specific beach including water quality data",
+ parameters: [
+ {
+ name: "id",
+ in: "path",
+ required: true,
+ description: "Beach ID from HaV",
+ schema: { type: "string" },
+ },
+ ],
+ responses: {
+ 200: {
+ description: "Beach details",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/BeachDetail" },
+ },
+ },
+ },
+ 404: {
+ description: "Beach not found",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 502: {
+ description: "HaV upstream error",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/favorites": {
+ get: {
+ tags: ["Favorites"],
+ summary: "List user favorites",
+ description: "Returns all favorite beaches for the authenticated user",
+ security: [{ cookieAuth: [] }],
+ responses: {
+ 200: {
+ description: "List of favorites",
+ content: {
+ "application/json": {
+ schema: {
+ type: "array",
+ items: { $ref: "#/components/schemas/Favorite" },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ post: {
+ tags: ["Favorites"],
+ summary: "Add a favorite",
+ description: "Add a beach to the user's favorites",
+ security: [{ cookieAuth: [] }],
+ requestBody: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ required: ["beachId"],
+ properties: {
+ beachId: { type: "string", description: "Beach ID from HaV" },
+ note: { type: "string", maxLength: 500, description: "Optional note about the beach" },
+ },
+ },
+ },
+ },
+ },
+ responses: {
+ 201: {
+ description: "Favorite created",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Favorite" },
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request body",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 409: {
+ description: "Beach already favorited",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/favorites/{id}": {
+ delete: {
+ tags: ["Favorites"],
+ summary: "Remove a favorite by ID",
+ description: "Remove a favorite using its MongoDB document ID",
+ security: [{ cookieAuth: [] }],
+ parameters: [
+ {
+ name: "id",
+ in: "path",
+ required: true,
+ description: "Favorite document ID",
+ schema: { type: "string" },
+ },
+ ],
+ responses: {
+ 200: {
+ description: "Favorite removed",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ },
+ },
+ },
+ },
+ },
+ 400: {
+ description: "Invalid ID",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 404: {
+ description: "Favorite not found",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/favorites/by-beach/{beachId}": {
+ delete: {
+ tags: ["Favorites"],
+ summary: "Remove a favorite by beach ID",
+ description: "Remove a favorite using the beach ID",
+ security: [{ cookieAuth: [] }],
+ parameters: [
+ {
+ name: "beachId",
+ in: "path",
+ required: true,
+ description: "Beach ID from HaV",
+ schema: { type: "string" },
+ },
+ ],
+ responses: {
+ 200: {
+ description: "Favorite removed",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ ok: { type: "boolean", example: true },
+ },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 404: {
+ description: "Favorite not found",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/favorites/reorder": {
+ patch: {
+ tags: ["Favorites"],
+ summary: "Reorder favorites",
+ description: "Update the display order of user's favorites",
+ security: [{ cookieAuth: [] }],
+ requestBody: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ required: ["order"],
+ properties: {
+ order: {
+ type: "array",
+ items: { type: "string" },
+ description: "Array of beach IDs in desired order",
+ },
+ },
+ },
+ },
+ },
+ },
+ responses: {
+ 204: {
+ description: "Order updated successfully",
+ },
+ 400: {
+ description: "Invalid request body",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Error" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ components: {
+ securitySchemes: {
+ cookieAuth: {
+ type: "apiKey",
+ in: "cookie",
+ name: "bada_session",
+ description: "HttpOnly session cookie set by /auth/login",
+ },
+ },
+ schemas: {
+ Error: {
+ type: "object",
+ properties: {
+ error: { type: "string", description: "Error code" },
+ message: { type: "string", description: "Error message (development only)" },
+ details: { type: "object", description: "Validation error details" },
+ },
+ },
+ Favorite: {
+ type: "object",
+ properties: {
+ _id: { type: "string", description: "MongoDB document ID" },
+ userId: { type: "string", description: "Owner user ID" },
+ beachId: { type: "string", description: "Beach ID from HaV" },
+ note: { type: "string", description: "User note about the beach" },
+ order: { type: "number", description: "Display order" },
+ createdAt: { type: "string", format: "date-time" },
+ updatedAt: { type: "string", format: "date-time" },
+ },
+ },
+ BeachDetail: {
+ type: "object",
+ description: "Beach detail from HaV API with additional fields",
+ properties: {
+ id: { type: "string" },
+ locationName: { type: "string", description: "Beach name" },
+ locationArea: { type: "string", description: "Municipality" },
+ classification: { type: "number", description: "Water quality classification (1-4)" },
+ classificationText: { type: "string", description: "Classification in Swedish" },
+ latestSampleDate: { type: "string", format: "date-time", nullable: true },
+ algalText: { type: "string", nullable: true },
+ algalValue: { type: "number", nullable: true },
+ bathInformation: { type: "string", nullable: true },
+ dissuasion: {
+ type: "array",
+ items: { type: "object" },
+ },
+ euMotive: { type: "string", nullable: true },
+ euType: { type: "boolean", nullable: true },
+ nutsCode: { type: "string" },
+ contactMail: { type: "string", nullable: true },
+ contactPhone: { type: "string", nullable: true },
+ contactUrl: { type: "string", nullable: true },
+ },
+ },
+ },
+ },
+};
+
+export const swaggerRouter = Router();
+
+// Expose the raw OpenAPI spec before the Swagger UI catch-all under /docs.
+swaggerRouter.get("/docs/openapi.json", (_req, res) => {
+ res.json(swaggerDocument);
+});
+
+swaggerRouter.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
+ customCss: `
+ .swagger-ui .topbar { display: none }
+ .swagger-ui .info .title { font-size: 2rem }
+ `,
+ customSiteTitle: "Badlistan API Documentation",
+}));
diff --git a/backend/test/auth.test.ts b/backend/test/auth.test.ts
new file mode 100644
index 0000000000..f004ebe02a
--- /dev/null
+++ b/backend/test/auth.test.ts
@@ -0,0 +1,154 @@
+import { describe, it, expect } from "vitest";
+import request from "supertest";
+import { app, registerAndLoginAgent } from "./helpers.js";
+
+describe("auth", () => {
+ it("registers a new user", async () => {
+ const res = await request(app)
+ .post("/api/auth/register")
+ .send({ email: "a@example.com", password: "testpass123" });
+ expect(res.status).toBe(201);
+ expect(res.body.ok).toBe(true);
+ });
+
+ it("normalizes duplicate email casing with 409", async () => {
+ await request(app)
+ .post("/api/auth/register")
+ .send({ email: "Dup@example.com", password: "testpass123" });
+ const res = await request(app)
+ .post("/api/auth/register")
+ .send({ email: "dup@example.com", password: "testpass123" });
+ expect(res.status).toBe(409);
+ expect(res.body.error).toBe("EmailInUse");
+ });
+
+ it("rejects a too-short password with 400", async () => {
+ const res = await request(app)
+ .post("/api/auth/register")
+ .send({ email: "short@example.com", password: "123" });
+ expect(res.status).toBe(400);
+ expect(res.body.error).toBe("InvalidBody");
+ });
+
+ it("logs in with valid credentials and sets a session cookie", async () => {
+ await request(app)
+ .post("/api/auth/register")
+ .send({ email: "Login@example.com", password: "testpass123" });
+ const res = await request(app)
+ .post("/api/auth/login")
+ .send({ email: "login@example.com", password: "testpass123" });
+ expect(res.status).toBe(200);
+ expect(res.body).toEqual({ ok: true });
+ expect(res.body.token).toBeUndefined();
+ const setCookie = res.headers["set-cookie"] as unknown as string[];
+ expect(setCookie.some((c) => c.startsWith("bada_session="))).toBe(true);
+ });
+
+ it("rejects a wrong password with 401", async () => {
+ await request(app)
+ .post("/api/auth/register")
+ .send({ email: "wrong@example.com", password: "testpass123" });
+ const res = await request(app)
+ .post("/api/auth/login")
+ .send({ email: "wrong@example.com", password: "nottherightone" });
+ expect(res.status).toBe(401);
+ expect(res.body.error).toBe("InvalidCredentials");
+ });
+
+ it("does not reveal whether an email exists (same 401 for unknown user)", async () => {
+ const res = await request(app)
+ .post("/api/auth/login")
+ .send({ email: "ghost@example.com", password: "testpass123" });
+ expect(res.status).toBe(401);
+ expect(res.body.error).toBe("InvalidCredentials");
+ });
+
+ describe("session cookie", () => {
+ it("sets an httpOnly session cookie on login", async () => {
+ await request(app)
+ .post("/api/auth/register")
+ .send({ email: "cookie@example.com", password: "testpass123" });
+ const res = await request(app)
+ .post("/api/auth/login")
+ .send({ email: "cookie@example.com", password: "testpass123" });
+
+ const setCookie = res.headers["set-cookie"] as unknown as string[];
+ expect(Array.isArray(setCookie)).toBe(true);
+ const session = setCookie.find((c) => c.startsWith("bada_session="));
+ expect(session).toBeDefined();
+ expect(session).toMatch(/HttpOnly/i);
+ expect(session).toMatch(/SameSite=Lax/i);
+ });
+
+ it("authenticates a protected route via the cookie (no Authorization header)", async () => {
+ const agent = request.agent(app);
+ await agent
+ .post("/api/auth/register")
+ .send({ email: "agent@example.com", password: "testpass123" });
+ await agent
+ .post("/api/auth/login")
+ .send({ email: "agent@example.com", password: "testpass123" });
+
+ const me = await agent.get("/api/auth/me"); // cookie sent automatically
+ expect(me.status).toBe(200);
+ expect(me.body.user.email).toBe("agent@example.com");
+ });
+
+ it("clears the session cookie on logout", async () => {
+ const agent = request.agent(app);
+ await agent
+ .post("/api/auth/register")
+ .send({ email: "logout@example.com", password: "testpass123" });
+ await agent
+ .post("/api/auth/login")
+ .send({ email: "logout@example.com", password: "testpass123" });
+
+ const before = await agent.get("/api/auth/me");
+ expect(before.status).toBe(200);
+
+ const logout = await agent.post("/api/auth/logout");
+ expect(logout.status).toBe(200);
+
+ const after = await agent.get("/api/auth/me");
+ expect(after.status).toBe(401);
+ });
+ });
+
+ describe("account deletion", () => {
+ it("requires auth", async () => {
+ const res = await request(app).delete("/api/auth/me");
+ expect(res.status).toBe(401);
+ });
+
+ it("deletes the user and prevents future login", async () => {
+ const agent = await registerAndLoginAgent("del@example.com");
+ const del = await agent.delete("/api/auth/me");
+ expect(del.status).toBe(200);
+
+ const login = await request(app)
+ .post("/api/auth/login")
+ .send({ email: "del@example.com", password: "testpass123" });
+ expect(login.status).toBe(401);
+ });
+
+ it("invalidates other active sessions after deletion", async () => {
+ const agentA = request.agent(app);
+ const agentB = request.agent(app);
+ await agentA
+ .post("/api/auth/register")
+ .send({ email: "del-sessions@example.com", password: "testpass123" });
+ await agentA
+ .post("/api/auth/login")
+ .send({ email: "del-sessions@example.com", password: "testpass123" });
+ await agentB
+ .post("/api/auth/login")
+ .send({ email: "del-sessions@example.com", password: "testpass123" });
+
+ const del = await agentA.delete("/api/auth/me");
+ expect(del.status).toBe(200);
+
+ const me = await agentB.get("/api/auth/me");
+ expect(me.status).toBe(401);
+ });
+ });
+});
diff --git a/backend/test/favorites.test.ts b/backend/test/favorites.test.ts
new file mode 100644
index 0000000000..016d6f0d95
--- /dev/null
+++ b/backend/test/favorites.test.ts
@@ -0,0 +1,95 @@
+import { describe, it, expect } from "vitest";
+import request from "supertest";
+import { app, registerAndLoginAgent } from "./helpers.js";
+
+const BEACH = "SE0441273000000001";
+const BEACH_2 = "SE0110000000000001";
+
+describe("favorites", () => {
+ it("requires auth to list", async () => {
+ const res = await request(app).get("/api/favorites");
+ expect(res.status).toBe(401);
+ });
+
+ it("creates a favorite and lists it", async () => {
+ const agent = await registerAndLoginAgent("fav1@example.com");
+
+ const create = await agent
+ .post("/api/favorites")
+ .send({ beachId: BEACH, note: "nice" });
+ expect(create.status).toBe(201);
+ expect(create.body.beachId).toBe(BEACH);
+
+ const list = await agent.get("/api/favorites");
+ expect(list.status).toBe(200);
+ expect(list.body).toHaveLength(1);
+ expect(list.body[0].beachId).toBe(BEACH);
+ });
+
+ it("rejects a duplicate favorite with 409", async () => {
+ const agent = await registerAndLoginAgent("fav2@example.com");
+ await agent
+ .post("/api/favorites")
+ .send({ beachId: BEACH });
+ const dup = await agent
+ .post("/api/favorites")
+ .send({ beachId: BEACH });
+ expect(dup.status).toBe(409);
+ expect(dup.body.error).toBe("AlreadyFavorited");
+ });
+
+ it("rejects an invalid body with 400", async () => {
+ const agent = await registerAndLoginAgent("fav3@example.com");
+ const res = await agent
+ .post("/api/favorites")
+ .send({ beachId: "" });
+ expect(res.status).toBe(400);
+ });
+
+ it("removes a favorite by beach id", async () => {
+ const agent = await registerAndLoginAgent("fav4@example.com");
+ await agent
+ .post("/api/favorites")
+ .send({ beachId: BEACH });
+
+ const del = await agent.delete(`/api/favorites/by-beach/${BEACH}`);
+ expect(del.status).toBe(200);
+
+ const list = await agent.get("/api/favorites");
+ expect(list.body).toHaveLength(0);
+ });
+
+ it("persists a custom order via reorder", async () => {
+ const agent = await registerAndLoginAgent("fav5@example.com");
+ await agent
+ .post("/api/favorites")
+ .send({ beachId: BEACH });
+ await agent
+ .post("/api/favorites")
+ .send({ beachId: BEACH_2 });
+
+ const reorder = await agent
+ .patch("/api/favorites/reorder")
+ .send({ order: [BEACH_2, BEACH] });
+ expect(reorder.status).toBe(204);
+
+ const list = await agent.get("/api/favorites");
+ expect(list.body.map((f: { beachId: string }) => f.beachId)).toEqual([
+ BEACH_2,
+ BEACH,
+ ]);
+ });
+
+ it("scopes favorites to the owner (one user cannot see another's)", async () => {
+ const agentA = await registerAndLoginAgent("ownerA@example.com");
+ const agentB = await registerAndLoginAgent("ownerB@example.com");
+
+ await agentA
+ .post("/api/favorites")
+ .send({ beachId: BEACH });
+
+ const listB = await agentB.get("/api/favorites");
+ expect(listB.status).toBe(200);
+ expect(listB.body).toHaveLength(0);
+ });
+});
diff --git a/backend/test/helpers.ts b/backend/test/helpers.ts
new file mode 100644
index 0000000000..e6606b81f8
--- /dev/null
+++ b/backend/test/helpers.ts
@@ -0,0 +1,15 @@
+import request from "supertest";
+import app from "../src/index.js";
+
+export { app };
+
+/** Register a user and return an agent carrying their session cookie. */
+export async function registerAndLoginAgent(
+ email: string,
+ password = "testpass123"
+): Promise> {
+ const agent = request.agent(app);
+ await agent.post("/api/auth/register").send({ email, password });
+ await agent.post("/api/auth/login").send({ email, password });
+ return agent;
+}
diff --git a/backend/test/httpBehavior.test.ts b/backend/test/httpBehavior.test.ts
new file mode 100644
index 0000000000..a78d00461e
--- /dev/null
+++ b/backend/test/httpBehavior.test.ts
@@ -0,0 +1,27 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import request from "supertest";
+import { app } from "./helpers.js";
+
+describe("http behavior", () => {
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
+ it("serves the raw OpenAPI document as JSON", async () => {
+ const res = await request(app).get("/api/docs/openapi.json");
+ expect(res.status).toBe(200);
+ expect(res.headers["content-type"]).toMatch(/application\/json/);
+ expect(res.body.openapi).toBe("3.0.3");
+ });
+
+ it("maps a HaV detail 404 to an API 404", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn(async () => new Response("missing", { status: 404 }))
+ );
+
+ const res = await request(app).get("/api/beaches/SE0000000000000000");
+ expect(res.status).toBe(404);
+ expect(res.body.error).toBe("NotFound");
+ });
+});
diff --git a/backend/test/rateLimitKey.test.ts b/backend/test/rateLimitKey.test.ts
new file mode 100644
index 0000000000..3667531c6e
--- /dev/null
+++ b/backend/test/rateLimitKey.test.ts
@@ -0,0 +1,63 @@
+import { describe, it, expect, afterEach } from "vitest";
+import type { Request } from "express";
+import jwt from "jsonwebtoken";
+import { clientKey } from "../src/middleware/rateLimit.js";
+
+// Build a minimal Express-like request for clientKey().
+function makeReq(opts: {
+ ip?: string;
+ forwarded?: string;
+ sign?: string;
+}): Request {
+ const headers: Record = {};
+ if (opts.forwarded) headers["x-nf-client-connection-ip"] = opts.forwarded;
+ if (opts.sign) headers["x-nf-sign"] = opts.sign;
+ return {
+ headers,
+ ip: opts.ip,
+ socket: { remoteAddress: opts.ip },
+ } as unknown as Request;
+}
+
+const SECRET = "test-proxy-signing-key";
+const validSign = () =>
+ jwt.sign({ iss: "netlify", site_url: "https://badaweb.netlify.app" }, SECRET, {
+ algorithm: "HS256",
+ });
+
+describe("clientKey (rate-limit identity)", () => {
+ afterEach(() => {
+ delete process.env.PROXY_SIGNING_KEY;
+ });
+
+ it("falls back to req.ip when no forwarded header is present", () => {
+ expect(clientKey(makeReq({ ip: "1.2.3.4" }))).toBe("1.2.3.4");
+ });
+
+ it("transitional mode (no signing key): trusts the forwarded client IP", () => {
+ const req = makeReq({ ip: "10.0.0.1", forwarded: "9.9.9.9" });
+ expect(clientKey(req)).toBe("9.9.9.9");
+ });
+
+ it("hardened mode: trusts the forwarded IP when the Netlify signature is valid", () => {
+ process.env.PROXY_SIGNING_KEY = SECRET;
+ const req = makeReq({ ip: "10.0.0.1", forwarded: "9.9.9.9", sign: validSign() });
+ expect(clientKey(req)).toBe("9.9.9.9");
+ });
+
+ it("hardened mode: ignores a forged forwarded IP without a valid signature", () => {
+ process.env.PROXY_SIGNING_KEY = SECRET;
+ // Attacker hits the backend directly, forging the forwarded IP, no signature.
+ const req = makeReq({ ip: "203.0.113.7", forwarded: "9.9.9.9" });
+ expect(clientKey(req)).toBe("203.0.113.7");
+ });
+
+ it("hardened mode: ignores the forwarded IP when the signature is invalid", () => {
+ process.env.PROXY_SIGNING_KEY = SECRET;
+ const badSign = jwt.sign({ iss: "netlify" }, "wrong-secret", {
+ algorithm: "HS256",
+ });
+ const req = makeReq({ ip: "203.0.113.7", forwarded: "9.9.9.9", sign: badSign });
+ expect(clientKey(req)).toBe("203.0.113.7");
+ });
+});
diff --git a/backend/test/setup.ts b/backend/test/setup.ts
new file mode 100644
index 0000000000..555677d232
--- /dev/null
+++ b/backend/test/setup.ts
@@ -0,0 +1,26 @@
+import { beforeAll, afterAll, afterEach } from "vitest";
+import mongoose from "mongoose";
+import { MongoMemoryServer } from "mongodb-memory-server";
+
+// Spin up an isolated in-memory MongoDB so tests never touch a real database.
+let mongod: MongoMemoryServer;
+
+beforeAll(async () => {
+ mongod = await MongoMemoryServer.create();
+ process.env.MONGODB_URI = mongod.getUri();
+ // The app's connectDB() reuses this same mongoose connection.
+ await mongoose.connect(process.env.MONGODB_URI);
+});
+
+afterEach(async () => {
+ // Clean slate between tests.
+ const { collections } = mongoose.connection;
+ for (const name of Object.keys(collections)) {
+ await collections[name].deleteMany({});
+ }
+});
+
+afterAll(async () => {
+ await mongoose.disconnect();
+ await mongod.stop();
+});
diff --git a/backend/test/watches.test.ts b/backend/test/watches.test.ts
new file mode 100644
index 0000000000..f757b28c21
--- /dev/null
+++ b/backend/test/watches.test.ts
@@ -0,0 +1,157 @@
+import { describe, it, expect } from "vitest";
+import request from "supertest";
+import { app, registerAndLoginAgent } from "./helpers.js";
+import {
+ diffSnapshots,
+ snapshotFromDetail,
+ type BeachSnapshot,
+} from "../src/lib/watchEvents.js";
+
+const BEACH_A = "SE0441284000000498";
+const BEACH_B = "SE0411060000000277";
+
+describe("watches routes", () => {
+ it("requires auth", async () => {
+ expect((await request(app).get("/api/watches")).status).toBe(401);
+ expect((await request(app).post("/api/watches")).status).toBe(401);
+ });
+
+ it("watching several beaches is free; re-adding one is idempotent", async () => {
+ const agent = await registerAndLoginAgent("watcher@example.com");
+
+ const first = await agent.post("/api/watches").send({ beachId: BEACH_A });
+ expect(first.status).toBe(201);
+
+ const second = await agent.post("/api/watches").send({ beachId: BEACH_B });
+ expect(second.status).toBe(201);
+
+ // Re-adding the same beach is idempotent, not a new row.
+ const again = await agent.post("/api/watches").send({ beachId: BEACH_A });
+ expect(again.status).toBe(201);
+
+ const list = await agent.get("/api/watches");
+ expect(list.body.items).toHaveLength(2);
+ });
+
+ it("caps watches at the abuse limit with 409 WatchLimitReached", async () => {
+ const agent = await registerAndLoginAgent("heavy-watcher@example.com");
+
+ for (let i = 0; i < 20; i++) {
+ const beachId = `SE${String(i).padStart(16, "0")}`;
+ expect(
+ (await agent.post("/api/watches").send({ beachId })).status
+ ).toBe(201);
+ }
+
+ const over = await agent.post("/api/watches").send({ beachId: BEACH_A });
+ expect(over.status).toBe(409);
+ expect(over.body.error).toBe("WatchLimitReached");
+ });
+
+ it("unwatching is idempotent and frees the slot", async () => {
+ const agent = await registerAndLoginAgent("unwatcher@example.com");
+ await agent.post("/api/watches").send({ beachId: BEACH_A });
+ expect((await agent.delete(`/api/watches/${BEACH_A}`)).status).toBe(204);
+ expect((await agent.delete(`/api/watches/${BEACH_A}`)).status).toBe(204);
+ expect(
+ (await agent.post("/api/watches").send({ beachId: BEACH_B })).status
+ ).toBe(201);
+ });
+
+ it("rejects malformed beach ids", async () => {
+ const agent = await registerAndLoginAgent("malformed@example.com");
+ const res = await agent
+ .post("/api/watches")
+ .send({ beachId: "DROP TABLE beaches" });
+ expect(res.status).toBe(400);
+ });
+});
+
+describe("diffSnapshots", () => {
+ const base: BeachSnapshot = {
+ beachId: BEACH_A,
+ name: "Arild, Kallbadhuset",
+ algalValue: "4",
+ algalText: "Ingen blomning",
+ advisory: "[]",
+ sampleDate: "1782770400000",
+ classification: "1",
+ classificationText: "UtmΓ€rkt kvalitet",
+ };
+
+ it("first sighting (no previous snapshot) produces NO events", () => {
+ expect(diffSnapshots(null, base)).toEqual([]);
+ });
+
+ it("no change produces no events", () => {
+ expect(diffSnapshots(base, { ...base })).toEqual([]);
+ });
+
+ it("detects a new algal bloom", () => {
+ const events = diffSnapshots(base, {
+ ...base,
+ algalValue: "1",
+ algalText: "Kraftig blomning",
+ });
+ expect(events).toHaveLength(1);
+ expect(events[0].type).toBe("algal");
+ expect(events[0].detail).toContain("Kraftig blomning");
+ });
+
+ it("detects a new advisory and its lifting", () => {
+ const withAdvisory = {
+ ...base,
+ advisory: JSON.stringify([{ dissuasionText: "AvrΓ₯dan" }]),
+ };
+ expect(diffSnapshots(base, withAdvisory)[0].type).toBe("advisory");
+ expect(diffSnapshots(withAdvisory, base)[0].type).toBe("advisoryLifted");
+ });
+
+ it("detects a new sample and a reclassification", () => {
+ const events = diffSnapshots(base, {
+ ...base,
+ sampleDate: "1783000000000",
+ classification: "2",
+ classificationText: "Bra kvalitet",
+ });
+ expect(events.map((e) => e.type).sort()).toEqual([
+ "classification",
+ "newSample",
+ ]);
+ });
+
+ it("multiple simultaneous changes yield one event each", () => {
+ const events = diffSnapshots(base, {
+ ...base,
+ algalValue: "2",
+ algalText: "Blomning",
+ advisory: JSON.stringify([{ dissuasionText: "AvrΓ₯dan" }]),
+ sampleDate: "1783000000000",
+ });
+ expect(events).toHaveLength(3);
+ });
+});
+
+describe("snapshotFromDetail", () => {
+ it("normalizes a HaV v1 detail payload", () => {
+ const snap = snapshotFromDetail(BEACH_A, {
+ locationName: "Arild, Kallbadhuset",
+ algalValue: 4,
+ algalText: "Ingen blomning",
+ dissuasion: [],
+ sampleDate: 1782770400000,
+ classification: 1,
+ classificationText: "UtmΓ€rkt kvalitet",
+ });
+ expect(snap).toEqual({
+ beachId: BEACH_A,
+ name: "Arild, Kallbadhuset",
+ algalValue: "4",
+ algalText: "Ingen blomning",
+ advisory: "[]",
+ sampleDate: "1782770400000",
+ classification: "1",
+ classificationText: "UtmΓ€rkt kvalitet",
+ });
+ });
+});
diff --git a/backend/tsconfig.json b/backend/tsconfig.json
new file mode 100644
index 0000000000..fafc27f38f
--- /dev/null
+++ b/backend/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ /* Output */
+ "outDir": "dist",
+ "rootDir": "src",
+
+ /* Language and Environment */
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "resolveJsonModule": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+ "skipLibCheck": true,
+ "lib": ["ES2022", "DOM"],
+
+ /* Type Checking */
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+
+ /* Source Maps & Declarations */
+ "sourceMap": true,
+ "declaration": true,
+ "declarationMap": true,
+
+ /* Types */
+ "types": ["node"]
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/backend/vercel.json b/backend/vercel.json
new file mode 100644
index 0000000000..0ecf1b1b1d
--- /dev/null
+++ b/backend/vercel.json
@@ -0,0 +1,4 @@
+{
+ "builds": [{ "src": "src/index.ts", "use": "@vercel/node" }],
+ "routes": [{ "src": "/api/(.*)", "dest": "src/index.ts" }]
+}
diff --git a/backend/vitest.config.ts b/backend/vitest.config.ts
new file mode 100644
index 0000000000..713ed9f6f7
--- /dev/null
+++ b/backend/vitest.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ setupFiles: ["./test/setup.ts"],
+ // One in-memory Mongo per file; run files serially to keep it simple.
+ fileParallelism: false,
+ env: { NODE_ENV: "test", JWT_SECRET: "test-secret" },
+ // First run downloads the mongodb-memory-server binary.
+ hookTimeout: 120000,
+ testTimeout: 30000,
+ },
+ resolve: {
+ // The backend uses NodeNext ESM, so source imports carry a `.js`
+ // extension that actually points at a `.ts` file. Map it back for Vitest.
+ extensionAlias: { ".js": [".ts", ".js"] },
+ },
+});
diff --git a/frontend/.env.example b/frontend/.env.example
new file mode 100644
index 0000000000..b786ebab3f
--- /dev/null
+++ b/frontend/.env.example
@@ -0,0 +1,10 @@
+# Backend API
+VITE_API_BASE=http://localhost:3000/api
+
+# MapTiler β required for the MapLibre styles.
+# This is a publishable client-side key; restrict it by allowed origin in the
+# MapTiler dashboard (Account β Keys) so a visible key cannot be abused.
+VITE_MAPTILER_KEY=your-maptiler-key
+
+# Local API debug logging. Only active in vite dev, never in production builds.
+VITE_DEBUG_API=false
diff --git a/frontend/README.md b/frontend/README.md
deleted file mode 100644
index 5cdb1d9cf3..0000000000
--- a/frontend/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Frontend part of Final Project
-
-This boilerplate is designed to give you a head start in your React projects, with a focus on understanding the structure and components. As a student of Technigo, you'll find this guide helpful in navigating and utilizing the repository.
-
-## Getting Started
-
-1. Install the required dependencies using `npm install`.
-2. Start the development server using `npm run dev`.
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
index 664410b5b9..912d9e3e1a 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,13 +1,62 @@
-
+
-
+
+
- Technigo React Vite Boiler Plate
+ Badlistan β Koll pΓ₯ badvattnet i hela Sverige
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/frontend/netlify.toml b/frontend/netlify.toml
new file mode 100644
index 0000000000..f855816b33
--- /dev/null
+++ b/frontend/netlify.toml
@@ -0,0 +1,40 @@
+# All routing lives here (not in _redirects): on this site _redirects rules are
+# evaluated first, and its /* catch-all shadowed every netlify.toml redirect β
+# so keep every rule in this file.
+
+[build]
+ # Netlify auto-installs this package's deps first; the backend's deps must be
+ # installed too so esbuild can bundle the Express app into the api function.
+ # Subshell instead of `npm ci --prefix`: npm 10 (Node 22 build image) rejects
+ # that flag combination with a usage error.
+ command = "(cd ../backend && npm ci) && npm run build"
+ publish = "dist"
+ # The api function bundles ../backend, but Netlify's default monorepo change
+ # detection only watches the base directory β backend-only commits were
+ # skipped as "no content change". Build when either directory changed.
+ ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- . ../backend"
+
+[functions]
+ node_bundler = "esbuild"
+
+# Daily watch notifier: 05:00 UTC β 07:00 svensk sommartid, after municipal
+# morning updates have usually reached HaV.
+[functions."notify-watchers"]
+ schedule = "0 5 * * *"
+
+# The Express backend runs as a Netlify Function on this same site (the old
+# Vercel backend is retired β Vercel's Hobby plan forbids commercial use).
+# A rewrite (not redirect) hands the function the original /api/* path, which
+# is what the Express router expects. Same-origin, so the session cookie stays
+# first-party with no signed proxy needed.
+[[redirects]]
+ from = "/api/*"
+ to = "/.netlify/functions/api"
+ status = 200
+
+# SPA fallback: serve index.html for client-side routes. No `force`, so real
+# static files (assets, robots.txt, og-image) are still served directly.
+[[redirects]]
+ from = "/*"
+ to = "/index.html"
+ status = 200
diff --git a/frontend/netlify/functions/api.ts b/frontend/netlify/functions/api.ts
new file mode 100644
index 0000000000..9a35357487
--- /dev/null
+++ b/frontend/netlify/functions/api.ts
@@ -0,0 +1,35 @@
+// The whole Express backend, wrapped as a single Netlify Function.
+//
+// Why: the backend must leave Vercel (the Hobby plan forbids commercial use)
+// and running it on the same Netlify site keeps topology A intact β the
+// session cookie stays first-party and /api/* needs no external proxy hop.
+//
+// Routing: netlify.toml rewrites /api/* to this function. On a rewrite the
+// function receives the ORIGINAL request path (e.g. /api/health), which is
+// exactly what the Express app expects, so no basePath stripping is needed.
+//
+// The app is serverless-ready as-is: Mongo connections are cached per
+// instance (lib/db.ts) and connectDB() is called lazily inside routes.
+// Env vars (MONGODB_URI, JWT_SECRET, HAV_BASE_URL, β¦) come from the Netlify
+// site configuration β no dotenv here.
+import serverless from "serverless-http";
+
+// The Lambda runtime sets neither NODE_ENV nor NETLIFY (the latter is a
+// build-time variable), but the app reads both at module load: NODE_ENV for
+// prod-only behavior (Secure cookies, hiding dev endpoints) and NETLIFY to
+// skip the CORS origin list, keep Swagger off, and trust Netlify's
+// x-nf-client-connection-ip in the rate limiter. This wrapper only ever runs
+// on Netlify, so default both before the app evaluates β which is why the
+// import below is dynamic.
+process.env.NODE_ENV ??= "production";
+process.env.NETLIFY ??= "true";
+
+let cached: ReturnType | undefined;
+
+export const handler = async (event: unknown, context: unknown) => {
+ if (!cached) {
+ const { default: app } = await import("../../../backend/src/index.js");
+ cached = serverless(app);
+ }
+ return cached(event, context);
+};
diff --git a/frontend/netlify/functions/notify-watchers.ts b/frontend/netlify/functions/notify-watchers.ts
new file mode 100644
index 0000000000..c85b5bf352
--- /dev/null
+++ b/frontend/netlify/functions/notify-watchers.ts
@@ -0,0 +1,10 @@
+// Scheduled daily (see netlify.toml): checks every watched beach against HaV
+// and emails watchers about real changes. All logic lives in the backend
+// module; this is just the scheduling shell.
+export const handler = async () => {
+ const { runWatchNotifications } = await import(
+ "../../../backend/src/lib/runWatchNotifications.js"
+ );
+ const summary = await runWatchNotifications();
+ return { statusCode: 200, body: JSON.stringify(summary) };
+};
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000000..d5986a3007
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,8017 @@
+{
+ "name": "bada-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "bada-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@fontsource-variable/zalando-sans": "^5.2.2",
+ "@fontsource/special-gothic-expanded-one": "^5.2.3",
+ "@hookform/resolvers": "^5.2.2",
+ "@phosphor-icons/react": "^2.1.10",
+ "@react-three/drei": "^9.117.3",
+ "@react-three/fiber": "^8.17.10",
+ "@tanstack/react-query": "^5.87.4",
+ "i18next": "^25.5.2",
+ "maplibre-gl": "^5.7.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-hook-form": "^7.63.0",
+ "react-hot-toast": "^2.6.0",
+ "react-i18next": "^15.7.3",
+ "react-router-dom": "^7.9.1",
+ "serverless-http": "^4.0.0",
+ "three": "^0.181.2",
+ "zod": "^4.1.11",
+ "zustand": "^5.0.8"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.13",
+ "@types/maplibre-gl": "^1.13.2",
+ "@types/react": "^18.3.24",
+ "@types/react-dom": "^18.3.7",
+ "@vitejs/plugin-react": "^4.0.3",
+ "autoprefixer": "^10.4.21",
+ "eslint-plugin-react": "^7.32.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.13",
+ "typescript": "^5.9.2",
+ "vite": "^6.3.6",
+ "vite-plugin-svgr": "^4.5.0",
+ "vitest": "^4.1.9"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@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.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "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-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.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.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.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.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@dimforge/rapier3d-compat": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
+ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
+ "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
+ "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.1",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/modifiers": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz",
+ "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
+ "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
+ "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
+ "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
+ "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
+ "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
+ "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
+ "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
+ "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
+ "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
+ "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
+ "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
+ "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
+ "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
+ "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
+ "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
+ "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
+ "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
+ "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
+ "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
+ "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
+ "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
+ "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@fontsource-variable/zalando-sans": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/zalando-sans/-/zalando-sans-5.2.2.tgz",
+ "integrity": "sha512-gD5gj2OJkBrBdck+y9hUy8O6pnPoFJeMkpx73g1CwGvAl4P5J4jiOOh8uQr2YTcfX01wIAMhuDysSdd85V5WzQ==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource/special-gothic-expanded-one": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@fontsource/special-gothic-expanded-one/-/special-gothic-expanded-one-5.2.3.tgz",
+ "integrity": "sha512-CZnpTXJ0EZUDMrqHQTpetgf3f7VXiUbePUG/oKB2Lz+jhe/EWu4qwpEmI+0Vni+MXQ7hCQlGiC1LVH/UbDQwVw==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
+ "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.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",
+ "peer": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mapbox/geojson-rewind": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
+ "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
+ "license": "ISC",
+ "dependencies": {
+ "get-stream": "^6.0.1",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "geojson-rewind": "geojson-rewind"
+ }
+ },
+ "node_modules/@mapbox/jsonlint-lines-primitives": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+ "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@mapbox/point-geometry": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz",
+ "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==",
+ "license": "ISC"
+ },
+ "node_modules/@mapbox/tiny-sdf": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz",
+ "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/@mapbox/unitbezier": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+ "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/@mapbox/vector-tile": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz",
+ "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@mapbox/point-geometry": "~1.1.0",
+ "@types/geojson": "^7946.0.16",
+ "pbf": "^4.0.1"
+ }
+ },
+ "node_modules/@mapbox/whoots-js": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
+ "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@maplibre/maplibre-gl-style-spec": {
+ "version": "23.3.0",
+ "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.3.0.tgz",
+ "integrity": "sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==",
+ "license": "ISC",
+ "dependencies": {
+ "@mapbox/jsonlint-lines-primitives": "~2.0.2",
+ "@mapbox/unitbezier": "^0.0.1",
+ "json-stringify-pretty-compact": "^4.0.0",
+ "minimist": "^1.2.8",
+ "quickselect": "^3.0.0",
+ "rw": "^1.3.3",
+ "tinyqueue": "^3.0.0"
+ },
+ "bin": {
+ "gl-style-format": "dist/gl-style-format.mjs",
+ "gl-style-migrate": "dist/gl-style-migrate.mjs",
+ "gl-style-validate": "dist/gl-style-validate.mjs"
+ }
+ },
+ "node_modules/@maplibre/vt-pbf": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.0.3.tgz",
+ "integrity": "sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==",
+ "license": "MIT",
+ "dependencies": {
+ "@mapbox/point-geometry": "^1.1.0",
+ "@mapbox/vector-tile": "^2.0.4",
+ "@types/geojson-vt": "3.2.5",
+ "@types/supercluster": "^7.1.3",
+ "geojson-vt": "^4.0.2",
+ "pbf": "^4.0.1",
+ "supercluster": "^8.0.1"
+ }
+ },
+ "node_modules/@mediapipe/tasks-vision": {
+ "version": "0.10.17",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
+ "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@monogrid/gainmap-js": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
+ "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
+ "license": "MIT",
+ "dependencies": {
+ "promise-worker-transferable": "^1.0.4"
+ },
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@phosphor-icons/react": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
+ "integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8",
+ "react-dom": ">= 16.8"
+ }
+ },
+ "node_modules/@react-spring/animated": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz",
+ "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/shared": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/core": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz",
+ "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/animated": "~9.7.5",
+ "@react-spring/shared": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-spring/donate"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/rafz": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz",
+ "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==",
+ "license": "MIT"
+ },
+ "node_modules/@react-spring/shared": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz",
+ "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/rafz": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/three": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.7.5.tgz",
+ "integrity": "sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/animated": "~9.7.5",
+ "@react-spring/core": "~9.7.5",
+ "@react-spring/shared": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": ">=6.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "three": ">=0.126"
+ }
+ },
+ "node_modules/@react-spring/types": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz",
+ "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==",
+ "license": "MIT"
+ },
+ "node_modules/@react-three/drei": {
+ "version": "9.122.0",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.122.0.tgz",
+ "integrity": "sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mediapipe/tasks-vision": "0.10.17",
+ "@monogrid/gainmap-js": "^3.0.6",
+ "@react-spring/three": "~9.7.5",
+ "@use-gesture/react": "^10.3.1",
+ "camera-controls": "^2.9.0",
+ "cross-env": "^7.0.3",
+ "detect-gpu": "^5.0.56",
+ "glsl-noise": "^0.0.0",
+ "hls.js": "^1.5.17",
+ "maath": "^0.10.8",
+ "meshline": "^3.3.1",
+ "react-composer": "^5.0.3",
+ "stats-gl": "^2.2.8",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.1.3",
+ "three-mesh-bvh": "^0.7.8",
+ "three-stdlib": "^2.35.6",
+ "troika-three-text": "^0.52.0",
+ "tunnel-rat": "^0.1.2",
+ "utility-types": "^3.11.0",
+ "zustand": "^5.0.1"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": "^8",
+ "react": "^18",
+ "react-dom": "^18",
+ "three": ">=0.137"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "8.17.10",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.17.10.tgz",
+ "integrity": "sha512-S6bqa4DqUooEkInYv/W+Jklv2zjSYCXAhm6qKpAQyOXhTEt5gBXnA7W6aoJ0bjmp9pAeaSj/AZUoz1HCSof/uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/debounce": "^1.2.1",
+ "@types/react-reconciler": "^0.26.7",
+ "@types/webxr": "*",
+ "base64-js": "^1.5.1",
+ "buffer": "^6.0.3",
+ "debounce": "^1.2.1",
+ "its-fine": "^1.0.6",
+ "react-reconciler": "^0.27.0",
+ "scheduler": "^0.21.0",
+ "suspend-react": "^0.1.3",
+ "zustand": "^3.7.1"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-file-system": ">=11.0",
+ "expo-gl": ">=11.0",
+ "react": ">=18.0",
+ "react-dom": ">=18.0",
+ "react-native": ">=0.64",
+ "three": ">=0.133"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-file-system": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber/node_modules/scheduler": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
+ "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/@react-three/fiber/node_modules/zustand": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
+ "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
+ "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
+ "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
+ "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
+ "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
+ "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-svg-component": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
+ "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-preset": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
+ "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0",
+ "@svgr/babel-plugin-svg-dynamic-title": "8.0.0",
+ "@svgr/babel-plugin-svg-em-dimensions": "8.0.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "8.1.0",
+ "@svgr/babel-plugin-transform-svg-component": "8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/core": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
+ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "camelcase": "^6.2.0",
+ "cosmiconfig": "^8.1.3",
+ "snake-case": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
+ "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.21.3",
+ "entities": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/plugin-jsx": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
+ "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "@svgr/hast-util-to-babel-ast": "8.0.0",
+ "svg-parser": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@svgr/core": "*"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz",
+ "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.5.1",
+ "lightningcss": "1.30.1",
+ "magic-string": "^0.30.18",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.13"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz",
+ "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.4",
+ "tar": "^7.4.3"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.13",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.13",
+ "@tailwindcss/oxide-darwin-x64": "4.1.13",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.13",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.13",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.13",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.13",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.13",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.13"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz",
+ "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz",
+ "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz",
+ "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz",
+ "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz",
+ "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz",
+ "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz",
+ "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz",
+ "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz",
+ "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz",
+ "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.5",
+ "@emnapi/runtime": "^1.4.5",
+ "@emnapi/wasi-threads": "^1.0.4",
+ "@napi-rs/wasm-runtime": "^0.2.12",
+ "@tybys/wasm-util": "^0.10.0",
+ "tslib": "^2.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz",
+ "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz",
+ "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz",
+ "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.13",
+ "@tailwindcss/oxide": "4.1.13",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.13"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.87.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.87.4.tgz",
+ "integrity": "sha512-uNsg6zMxraEPDVO2Bn+F3/ctHi+Zsk+MMpcN8h6P7ozqD088F6mFY5TfGM7zuyIrL7HKpDyu6QHfLWiDxh3cuw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.87.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.87.4.tgz",
+ "integrity": "sha512-T5GT/1ZaNsUXf5I3RhcYuT17I4CPlbZgyLxc/ZGv7ciS6esytlbjb3DgUFO6c8JWYMDpdjSWInyGZUErgzqhcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.87.4"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/debounce": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz",
+ "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/draco3d": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
+ "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
+ "license": "MIT"
+ },
+ "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/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/geojson-vt": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz",
+ "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/maplibre-gl": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@types/maplibre-gl/-/maplibre-gl-1.13.2.tgz",
+ "integrity": "sha512-IC1RBMhKXpGDpiFsEwt17c/hbff0GCS/VmzqmrY6G+kyy2wfv2e7BoSQRAfqrvhBQPCoO8yc0SNCi5HkmCcVqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/offscreencanvas": {
+ "version": "2019.7.3",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
+ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.24",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
+ "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/react-reconciler": {
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz",
+ "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/supercluster": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
+ "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/three": {
+ "version": "0.181.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.181.0.tgz",
+ "integrity": "sha512-MLF1ks8yRM2k71D7RprFpDb9DOX0p22DbdPqT/uAkc6AtQXjxWCVDjCy23G9t1o8HcQPk7woD2NIyiaWcWPYmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@dimforge/rapier3d-compat": "~0.12.0",
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": "*",
+ "@webgpu/types": "*",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~0.22.0"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/@use-gesture/core": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
+ "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
+ "license": "MIT"
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
+ "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@use-gesture/core": "10.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
+ "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
+ "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/mocker/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
+ "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
+ "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
+ "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
+ "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
+ "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@webgpu/types": {
+ "version": "0.1.67",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.67.tgz",
+ "integrity": "sha512-uk53+2ECGUkWoDFez/hymwpRfdgdIn6y1ref70fEecGMe5607f4sozNFgBk0oxlr7j2CRGWBEc3IBYMmFdGGTQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "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/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.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/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/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/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/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/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "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/baseline-browser-mapping": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.3.tgz",
+ "integrity": "sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.26.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz",
+ "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==",
+ "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.8.2",
+ "caniuse-lite": "^1.0.30001741",
+ "electron-to-chromium": "^1.5.218",
+ "node-releases": "^2.0.21",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "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": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/camera-controls": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.1.tgz",
+ "integrity": "sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.126.1"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001741",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
+ "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "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/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true
+ },
+ "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/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "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.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "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/debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
+ "license": "MIT"
+ },
+ "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",
+ "peer": true
+ },
+ "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-gpu": {
+ "version": "5.0.70",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
+ "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
+ "license": "MIT",
+ "dependencies": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz",
+ "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dot-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/draco3d": {
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
+ "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
+ "license": "Apache-2.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/earcut": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
+ "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
+ "license": "ISC"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.218",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz",
+ "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
+ "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
+ "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.3",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.6",
+ "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.4",
+ "safe-array-concat": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz",
+ "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
+ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.9",
+ "@esbuild/android-arm": "0.25.9",
+ "@esbuild/android-arm64": "0.25.9",
+ "@esbuild/android-x64": "0.25.9",
+ "@esbuild/darwin-arm64": "0.25.9",
+ "@esbuild/darwin-x64": "0.25.9",
+ "@esbuild/freebsd-arm64": "0.25.9",
+ "@esbuild/freebsd-x64": "0.25.9",
+ "@esbuild/linux-arm": "0.25.9",
+ "@esbuild/linux-arm64": "0.25.9",
+ "@esbuild/linux-ia32": "0.25.9",
+ "@esbuild/linux-loong64": "0.25.9",
+ "@esbuild/linux-mips64el": "0.25.9",
+ "@esbuild/linux-ppc64": "0.25.9",
+ "@esbuild/linux-riscv64": "0.25.9",
+ "@esbuild/linux-s390x": "0.25.9",
+ "@esbuild/linux-x64": "0.25.9",
+ "@esbuild/netbsd-arm64": "0.25.9",
+ "@esbuild/netbsd-x64": "0.25.9",
+ "@esbuild/openbsd-arm64": "0.25.9",
+ "@esbuild/openbsd-x64": "0.25.9",
+ "@esbuild/openharmony-arm64": "0.25.9",
+ "@esbuild/sunos-x64": "0.25.9",
+ "@esbuild/win32-arm64": "0.25.9",
+ "@esbuild/win32-ia32": "0.25.9",
+ "@esbuild/win32-x64": "0.25.9"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "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": "4.6.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-plugin-react/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/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "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",
+ "peer": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "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/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "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/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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/geojson-vt": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz",
+ "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==",
+ "license": "ISC"
+ },
+ "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-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/gl-matrix": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz",
+ "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==",
+ "license": "MIT"
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "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/glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
+ "license": "MIT"
+ },
+ "node_modules/goober": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
+ "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hls.js": {
+ "version": "1.6.15",
+ "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz",
+ "integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "25.5.2",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz",
+ "integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6"
+ },
+ "peerDependencies": {
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "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-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-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",
+ "peer": true,
+ "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.0",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "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",
+ "peer": true,
+ "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-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-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "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/its-fine": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz",
+ "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0"
+ }
+ },
+ "node_modules/its-fine/node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
+ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
+ "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
+ "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",
+ "peer": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "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",
+ "peer": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/json-stringify-pretty-compact": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
+ "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
+ "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/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==",
+ "license": "ISC"
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.30.1",
+ "lightningcss-darwin-x64": "1.30.1",
+ "lightningcss-freebsd-x64": "1.30.1",
+ "lightningcss-linux-arm-gnueabihf": "1.30.1",
+ "lightningcss-linux-arm64-gnu": "1.30.1",
+ "lightningcss-linux-arm64-musl": "1.30.1",
+ "lightningcss-linux-x64-gnu": "1.30.1",
+ "lightningcss-linux-x64-musl": "1.30.1",
+ "lightningcss-win32-arm64-msvc": "1.30.1",
+ "lightningcss-win32-x64-msvc": "1.30.1"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
+ "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/maath": {
+ "version": "0.10.8",
+ "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
+ "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/three": ">=0.134.0",
+ "three": ">=0.134.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/maplibre-gl": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.7.1.tgz",
+ "integrity": "sha512-iCOQB6W/EGgQx8aU4SyfU5a5/GR2E+ELF92NMsqYfs3x+vnY+8mARmz4gor6XZHCz3tv19mnotVDRlRTMNKyGw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@mapbox/geojson-rewind": "^0.5.2",
+ "@mapbox/jsonlint-lines-primitives": "^2.0.2",
+ "@mapbox/point-geometry": "^1.1.0",
+ "@mapbox/tiny-sdf": "^2.0.7",
+ "@mapbox/unitbezier": "^0.0.1",
+ "@mapbox/vector-tile": "^2.0.4",
+ "@mapbox/whoots-js": "^3.1.0",
+ "@maplibre/maplibre-gl-style-spec": "^23.3.0",
+ "@maplibre/vt-pbf": "^4.0.3",
+ "@types/geojson": "^7946.0.16",
+ "@types/geojson-vt": "3.2.5",
+ "@types/supercluster": "^7.1.3",
+ "earcut": "^3.0.2",
+ "geojson-vt": "^4.0.2",
+ "gl-matrix": "^3.4.4",
+ "kdbush": "^4.0.2",
+ "murmurhash-js": "^1.0.0",
+ "pbf": "^4.0.1",
+ "potpack": "^2.1.0",
+ "quickselect": "^3.0.0",
+ "supercluster": "^8.0.1",
+ "tinyqueue": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.14.0",
+ "npm": ">=8.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
+ }
+ },
+ "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/meshline": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
+ "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/meshoptimizer": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz",
+ "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==",
+ "license": "MIT"
+ },
+ "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==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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/murmurhash-js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
+ "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/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",
+ "peer": true
+ },
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.21",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
+ "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "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.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/obug": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
+ "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "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/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pbf": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
+ "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "resolve-protobuf-schema": "^2.1.0"
+ },
+ "bin": {
+ "pbf": "bin/pbf"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/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.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/potpack": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz",
+ "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==",
+ "license": "ISC"
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/promise-worker-transferable": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
+ "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "is-promise": "^2.1.0",
+ "lie": "^3.0.2"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/protocol-buffers-schema": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz",
+ "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==",
+ "license": "MIT"
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true
+ },
+ "node_modules/quickselect": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
+ "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
+ "license": "ISC"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-composer": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz",
+ "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==",
+ "license": "MIT",
+ "dependencies": {
+ "prop-types": "^15.6.0"
+ },
+ "peerDependencies": {
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.63.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
+ "integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-hot-toast": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
+ "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.1.3",
+ "goober": "^2.1.16"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
+ }
+ },
+ "node_modules/react-i18next": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.3.tgz",
+ "integrity": "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 25.4.1",
+ "react": ">= 16.8.0",
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz",
+ "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.21.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
+ "node_modules/react-reconciler/node_modules/scheduler": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
+ "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz",
+ "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz",
+ "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "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/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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-protobuf-schema": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+ "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "protocol-buffers-schema": "^3.3.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",
+ "peer": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
+ "@rollup/rollup-android-arm64": "4.62.2",
+ "@rollup/rollup-darwin-arm64": "4.62.2",
+ "@rollup/rollup-darwin-x64": "4.62.2",
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
+ "@rollup/rollup-freebsd-x64": "4.62.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
+ "@rollup/rollup-openbsd-x64": "4.62.2",
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "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/serverless-http": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serverless-http/-/serverless-http-4.0.0.tgz",
+ "integrity": "sha512-KKl9h1+VApX9y2vHsVMogf906UXBwO3FDYiWPzqS0mCjEEx4Sx91JrssbWA7RN43HBuxrtoh8MkocZPvjyGnLg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "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/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==",
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/snake-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
+ "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dot-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stats-gl": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
+ "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/three": "*",
+ "three": "^0.170.0"
+ },
+ "peerDependencies": {
+ "@types/three": "*",
+ "three": "*"
+ }
+ },
+ "node_modules/stats-gl/node_modules/three": {
+ "version": "0.170.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
+ "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
+ "license": "MIT"
+ },
+ "node_modules/stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "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.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "license": "ISC",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ },
+ "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",
+ "peer": true,
+ "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/suspend-react": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
+ "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
+ "node_modules/svg-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
+ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
+ "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
+ "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.5.19",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz",
+ "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/three": {
+ "version": "0.181.2",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.181.2.tgz",
+ "integrity": "sha512-k/CjiZ80bYss6Qs7/ex1TBlPD11whT9oKfT8oTGiHa34W4JRd1NiH/Tr1DbHWQ2/vMUypxksLnF2CfmlmM5XFQ==",
+ "license": "MIT"
+ },
+ "node_modules/three-mesh-bvh": {
+ "version": "0.7.8",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz",
+ "integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==",
+ "deprecated": "Deprecated due to three.js version incompatibility. Please use v0.8.0, instead.",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">= 0.151.0"
+ }
+ },
+ "node_modules/three-stdlib": {
+ "version": "2.36.1",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
+ "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/draco3d": "^1.4.0",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@types/webxr": "^0.5.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "potpack": "^1.0.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.128.0"
+ }
+ },
+ "node_modules/three-stdlib/node_modules/fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
+ "license": "MIT"
+ },
+ "node_modules/three-stdlib/node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
+ "license": "ISC"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyqueue": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
+ "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
+ "license": "ISC"
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/troika-three-text": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
+ "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
+ "license": "MIT",
+ "dependencies": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.52.4",
+ "troika-worker-utils": "^0.52.0",
+ "webgl-sdf-generator": "1.1.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-three-utils": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
+ "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-worker-utils": {
+ "version": "0.52.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
+ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
+ "license": "MIT"
+ },
+ "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/tunnel-rat": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
+ "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "zustand": "^4.3.2"
+ }
+ },
+ "node_modules/tunnel-rat/node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "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/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "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",
+ "peer": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/utility-types": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
+ "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-svgr": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz",
+ "integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.2.0",
+ "@svgr/core": "^8.1.0",
+ "@svgr/plugin-jsx": "^8.1.0"
+ },
+ "peerDependencies": {
+ "vite": ">=2.6.0"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
+ "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.9",
+ "@vitest/mocker": "4.1.9",
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/runner": "4.1.9",
+ "@vitest/snapshot": "4.1.9",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.9",
+ "@vitest/browser-preview": "4.1.9",
+ "@vitest/browser-webdriverio": "4.1.9",
+ "@vitest/coverage-istanbul": "4.1.9",
+ "@vitest/coverage-v8": "4.1.9",
+ "@vitest/ui": "4.1.9",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "node_modules/webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "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.19",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
+ "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz",
+ "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
+ "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 7b2747e949..0ee139aba7 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,26 +1,56 @@
{
- "name": "project-final-backend",
- "description": "Client part of final project",
+ "name": "bada-frontend",
+ "description": "Frontend for BADA β find safe beaches in Sweden",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
- "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+ "build:beaches": "node ../scripts/build-beaches.mjs",
+ "lint": "tsc --noEmit",
+ "test": "vitest run",
"preview": "vite preview"
},
"dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@fontsource-variable/zalando-sans": "^5.2.2",
+ "@fontsource/special-gothic-expanded-one": "^5.2.3",
+ "@hookform/resolvers": "^5.2.2",
+ "@phosphor-icons/react": "^2.1.10",
+ "@react-three/drei": "^9.117.3",
+ "@react-three/fiber": "^8.17.10",
+ "@tanstack/react-query": "^5.87.4",
+ "i18next": "^25.5.2",
+ "maplibre-gl": "^5.7.1",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-hook-form": "^7.63.0",
+ "react-hot-toast": "^2.6.0",
+ "react-i18next": "^15.7.3",
+ "react-router-dom": "^7.9.1",
+ "serverless-http": "^4.0.0",
+ "three": "^0.181.2",
+ "zod": "^4.1.11",
+ "zustand": "^5.0.8"
},
"devDependencies": {
- "@types/react": "^18.2.15",
- "@types/react-dom": "^18.2.7",
+ "@tailwindcss/postcss": "^4.1.13",
+ "@types/maplibre-gl": "^1.13.2",
+ "@types/react": "^18.3.24",
+ "@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^4.0.3",
- "eslint": "^8.45.0",
+ "autoprefixer": "^10.4.21",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
- "vite": "^6.3.5"
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.13",
+ "typescript": "^5.9.2",
+ "vite": "^6.3.6",
+ "vite-plugin-svgr": "^4.5.0",
+ "vitest": "^4.1.9"
}
}
diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs
new file mode 100644
index 0000000000..483f378543
--- /dev/null
+++ b/frontend/postcss.config.cjs
@@ -0,0 +1,5 @@
+module.exports = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
diff --git a/frontend/public/beaches.json b/frontend/public/beaches.json
new file mode 100644
index 0000000000..efee26bd2f
--- /dev/null
+++ b/frontend/public/beaches.json
@@ -0,0 +1 @@
+[{"id":"SE0411060000000277","name":"AbborrsjΓΆn","municipality":"OlofstrΓΆm","lat":56.32656,"lon":14.69136},{"id":"SE0611763000002279","name":"AbborrtjΓ€rn","municipality":"Forshaga","lat":59.5096,"lon":13.4325},{"id":"SE0812404000003683","name":"AbborrtjΓ€rn","municipality":"Vindeln","lat":64.23141,"lon":19.72489},{"id":"SE0812418000003416","name":"Adak","municipality":"MalΓ₯","lat":65.35422,"lon":18.57681},{"id":"SE0110125000005597","name":"AdelsΓΆ Badplats","municipality":"EkerΓΆ","lat":59.3636,"lon":17.53409},{"id":"SE0920764000001592","name":"AgnasjΓΆn, Agnaryds badplats","municipality":"Alvesta","lat":57.00669,"lon":14.39704},{"id":"SE0920781000001318","name":"AgunnarydsjΓΆn","municipality":"Ljungby","lat":56.74417,"lon":14.15529},{"id":"SE0910617000000928","name":"AlbosjΓΆn","municipality":"GnosjΓΆ","lat":57.25142,"lon":13.77977},{"id":"SE0110138000002104","name":"Albybadet","municipality":"TyresΓΆ","lat":59.22933,"lon":18.27781},{"id":"SE0110126000004055","name":"AlbysjΓΆn, Flottsbrobadet","municipality":"Huddinge","lat":59.2316,"lon":17.8803},{"id":"SE0822582000003209","name":"AldersjΓΆn","municipality":"Boden","lat":65.78506,"lon":21.63941},{"id":"SE0110182000001246","name":"Alfredsrobadet","municipality":"Nacka","lat":59.2664,"lon":18.3586},{"id":"SE0220486000001910","name":"AlgΓΆ, Ekebyviksbadet","municipality":"StrΓ€ngnΓ€s","lat":59.43597,"lon":17.13672},{"id":"SE0241883000002575","name":"Alkvettern, Lunnedet","municipality":"Karlskoga","lat":59.4001,"lon":14.415},{"id":"SE0920780000001531","name":"Allgunnen, BjΓΆrnΓΆ","municipality":"VΓ€xjΓΆ","lat":57.17674,"lon":14.60818},{"id":"SE0A21490000003594","name":"AlmenΓ€s badplats, ΓresjΓΆ","municipality":"BorΓ₯s","lat":57.76204,"lon":12.93997},{"id":"SE0930883000001614","name":"Almviks badplats","municipality":"VΓ€stervik","lat":57.8322,"lon":16.4521},{"id":"SE0411081000000150","name":"AlmΓΆ","municipality":"Ronneby","lat":56.14765,"lon":15.44084},{"id":"SE0A21419000000655","name":"AlmΓΆns badplats","municipality":"TjΓΆrn","lat":58.0679,"lon":11.7609},{"id":"SE0632181000002982","name":"AlsjΓΆn, Γ
shammar","municipality":"Sandviken","lat":60.63854,"lon":16.55509},{"id":"SE0210381000001372","name":"Alsta","municipality":"EnkΓΆping","lat":59.74192,"lon":17.27229},{"id":"SE0920760000007260","name":"AlsterΓ₯n, Alstermo","municipality":"Uppvidinge","lat":56.9726,"lon":15.6349},{"id":"SE0622029000002429","name":"Alvik","municipality":"Leksand","lat":60.751,"lon":14.8683},{"id":"SE0230580000001819","name":"AmundebosjΓΆn","municipality":"LinkΓΆping","lat":58.12124,"lon":15.44545},{"id":"SE0722380000005057","name":"AndersΓΆn, Oxmelsviken","municipality":"Γstersund","lat":63.1565,"lon":14.4359},{"id":"SE0910682000002062","name":"AnebysjΓΆn, Flisby badpl.","municipality":"NΓ€ssjΓΆ","lat":57.79145,"lon":14.8141},{"id":"SE0930884000001421","name":"Anen","municipality":"Vimmerby","lat":57.82019,"lon":16.04582},{"id":"SE0A21485000000605","name":"AnfasterΓΆds camping","municipality":"Uddevalla","lat":58.20631,"lon":11.8957},{"id":"SE0822581000002767","name":"Ankargrund, Ankarskatan","municipality":"PiteΓ₯","lat":65.3415,"lon":21.486},{"id":"SE0632184000002991","name":"Ankarmon, Iggesund","municipality":"Hudiksvall","lat":61.6588,"lon":17.0766},{"id":"SE0930883000001634","name":"Ankarsrums badplats (LΓ₯ngsjΓΆn)","municipality":"VΓ€stervik","lat":57.6969,"lon":16.3154},{"id":"SE0910683000001516","name":"AnnebergssjΓΆn","municipality":"VΓ€rnamo","lat":57.1885,"lon":13.69684},{"id":"SE0A21441000001038","name":"AnnesjΓΆn","municipality":"Lerum","lat":57.85595,"lon":12.27243},{"id":"SE0230562000000884","name":"AnnsjΓΆn, Ljusfallshammar","municipality":"FinspΓ₯ng","lat":58.79,"lon":15.4593},{"id":"SE0A21489000001706","name":"Anten, GrΓ€fsnΓ€s slottspark","municipality":"AlingsΓ₯s","lat":58.07721,"lon":12.49285},{"id":"SE0A11383000000633","name":"Apelviken 1","municipality":"Varberg","lat":57.08366,"lon":12.25558},{"id":"SE0A11383000000634","name":"Apelviken 2","municipality":"Varberg","lat":57.0788,"lon":12.2602},{"id":"SE0611766000002372","name":"Aplungen, HensgΓ₯rd","municipality":"Sunne","lat":59.73735,"lon":13.0718},{"id":"SE0251984000004049","name":"ArbogaΓ₯n, Villagatsbadet","municipality":"Arboga","lat":59.3923,"lon":15.8243},{"id":"SE0441284000000498","name":"Arild, Kallbadhuset","municipality":"HΓΆganΓ€s","lat":56.27318,"lon":12.58292},{"id":"SE0110188000002154","name":"Arkadien","municipality":"NorrtΓ€lje","lat":59.7366,"lon":18.3555},{"id":"SE0A21494000000915","name":"Arken, VΓ€nern","municipality":"LidkΓΆping","lat":58.4824,"lon":12.82851},{"id":"SE0230581000004446","name":"ArkΓΆsund, Badholmarna","municipality":"NorrkΓΆping","lat":58.49065,"lon":16.94269},{"id":"SE0230581000001747","name":"ArkΓΆsund, Nordanskogsbadet Camping","municipality":"NorrkΓΆping","lat":58.4914,"lon":16.921},{"id":"SE0230581000004447","name":"ArkΓΆsund, SkΓΆldvik","municipality":"NorrkΓΆping","lat":58.4757,"lon":16.9416},{"id":"SE0622021000002825","name":"ArvnilstjΓ€rns badplats","municipality":"Vansbro","lat":60.56386,"lon":14.30998},{"id":"SE0A21482000005697","name":"ArΓΆd","municipality":"KungΓ€lv","lat":57.95991,"lon":11.7555},{"id":"SE0920780000001533","name":"AsasjΓΆn, Asa","municipality":"VΓ€xjΓΆ","lat":57.1626,"lon":14.791},{"id":"SE0920780000001532","name":"AsasjΓΆn, GrΓΆnalund (Tolg)","municipality":"VΓ€xjΓΆ","lat":57.1106,"lon":14.8016},{"id":"SE0920781000001327","name":"Askaken, Lidhult","municipality":"Ljungby","lat":56.81992,"lon":13.44461},{"id":"SE0A21480000000532","name":"Askimsbadet","municipality":"GΓΆteborg","lat":57.62342,"lon":11.9241},{"id":"SE0A21490000001801","name":"Asklanda badplats, ΓresjΓΆ","municipality":"BorΓ₯s","lat":57.81325,"lon":12.9984},{"id":"SE0241882000002516","name":"Aspa badplats, VΓ€ttern","municipality":"Askersund","lat":58.76684,"lon":14.80279},{"id":"SE0241885000002496","name":"AspasjΓΆn","municipality":"Lindesberg","lat":59.65684,"lon":15.25616},{"id":"SE0A21441000001039","name":"Aspen","municipality":"Lerum","lat":57.7675,"lon":12.2527},{"id":"SE0220483000001120","name":"Aspen","municipality":"Katrineholm","lat":59.14289,"lon":16.1275},{"id":"SE0A21480000000521","name":"AsperΓΆ","municipality":"GΓΆteborg","lat":57.65213,"lon":11.80504},{"id":"SE0611782000002358","name":"Asphyttan","municipality":"Filipstad","lat":59.6177,"lon":14.18},{"id":"SE0910604000000972","name":"AssjΓΆn","municipality":"Aneby","lat":57.80724,"lon":14.95575},{"id":"SE0822505000004043","name":"Auktsjaur","municipality":"Arvidsjaur","lat":65.74771,"lon":19.40272},{"id":"SE0812401000003539","name":"Ava Havsbad","municipality":"Nordmaling","lat":63.47808,"lon":19.3013},{"id":"SE0812425000002817","name":"AvatrΓ€sk badplats","municipality":"Dorotea","lat":64.32908,"lon":16.2868},{"id":"SE0910680000003506","name":"Axamobadet","municipality":"JΓΆnkΓΆping","lat":57.77703,"lon":14.06569},{"id":"SE0712260000003023","name":"BacketjΓ€rn, Torpshammar","municipality":"Γ
nge","lat":62.47917,"lon":16.29463},{"id":"SE0A21435000004425","name":"Badberget","municipality":"Tanum","lat":58.59188,"lon":11.27822},{"id":"SE0930860000000998","name":"Baddammen, JΓ€rnforsen","municipality":"Hultsfred","lat":57.41264,"lon":15.62052},{"id":"SE0A21435000004172","name":"Badholmen","municipality":"Tanum","lat":58.5969,"lon":11.2764},{"id":"SE0722380000004714","name":"Badhusparken","municipality":"Γstersund","lat":63.1789,"lon":14.6302},{"id":"SE0A21492000000946","name":"Badplatsen FurusjΓΆn","municipality":"Γ
mΓ₯l","lat":58.89614,"lon":12.56317},{"id":"SE0920761000001398","name":"BadsjΓΆn, Skruv","municipality":"Lessebo","lat":56.66967,"lon":15.37398},{"id":"SE0812422000003146","name":"BadsjΓΆn, Sorsele camping","municipality":"Sorsele","lat":65.53547,"lon":17.52475},{"id":"SE0611760000002394","name":"Badsta","municipality":"Storfors","lat":59.54469,"lon":14.28646},{"id":"SE0930834000001944","name":"Badudden","municipality":"TorsΓ₯s","lat":56.37858,"lon":16.09077},{"id":"SE0632180000004280","name":"Badviken(TΓ€rnviken)-LimΓΆn","municipality":"GΓ€vle","lat":60.71664,"lon":17.34196},{"id":"SE0110182000001223","name":"BagarsjΓΆbadet","municipality":"Nacka","lat":59.30956,"lon":18.26167},{"id":"SE0110182000001233","name":"Baggensuddsbadet","municipality":"Nacka","lat":59.28803,"lon":18.30752},{"id":"SE0A21443000003853","name":"BallasjΓΆn","municipality":"Bollebygd","lat":57.6713,"lon":12.526},{"id":"SE0441293000000428","name":"BallingslΓΆvssjΓΆn","municipality":"HΓ€ssleholm","lat":56.2326,"lon":13.8799},{"id":"SE0441290000000673","name":"Balsby badplats","municipality":"Kristianstad","lat":56.079,"lon":14.21329},{"id":"SE0622080000004238","name":"Balungstrand, ΓvertΓ€nger","municipality":"Falun","lat":60.89379,"lon":15.76065},{"id":"SE0441276000000868","name":"BandsjΓΆn","municipality":"Klippan","lat":56.07893,"lon":13.30503},{"id":"SE0A11383000000636","name":"Barnens badstrand","municipality":"Varberg","lat":57.10858,"lon":12.24064},{"id":"SE0110138000002105","name":"BarnsjΓΆbadet","municipality":"TyresΓΆ","lat":59.227,"lon":18.23471},{"id":"SE0920780000001558","name":"BarnsjΓΆn","municipality":"VΓ€xjΓΆ","lat":56.8519,"lon":14.8572},{"id":"SE0632132000003028","name":"BarrtjΓ€rn, Γlgered","municipality":"Nordanstig","lat":62.0166,"lon":16.936},{"id":"SE0441261000000076","name":"BarsebΓ€ckshamn","municipality":"KΓ€vlinge","lat":55.75661,"lon":12.90459},{"id":"SE0441261000003868","name":"BarsebΓ€ckstrand","municipality":"KΓ€vlinge","lat":55.76991,"lon":12.9192},{"id":"SE0441291000000330","name":"BaskemΓΆlla","municipality":"Simrishamn","lat":55.59691,"lon":14.31226},{"id":"SE0822581000002736","name":"BastatrΓ€sket, Storsund","municipality":"PiteΓ₯","lat":65.57074,"lon":20.6064},{"id":"SE0A21415000000110","name":"Bastevik, HΓ€llungen","municipality":"Stenungsund","lat":58.0956,"lon":11.9534},{"id":"SE0110182000001167","name":"BastusjΓΆbadet","municipality":"Nacka","lat":59.3182,"lon":18.20783},{"id":"SE0812417000003845","name":"BastutrΓ€sket, RΓ€velns badplats","municipality":"NorsjΓΆ","lat":64.77785,"lon":20.01613},{"id":"SE0712281000003475","name":"BergafjΓ€rden, Njurunda","municipality":"Sundsvall","lat":62.2673,"lon":17.4557},{"id":"SE0110181000001363","name":"BergasjΓΆn, Bergabadet","municipality":"SΓΆdertΓ€lje","lat":59.05758,"lon":17.44087},{"id":"SE0930880000000791","name":"Bergavik","municipality":"Kalmar","lat":56.6965,"lon":16.3707},{"id":"SE0110188000005195","name":"Bergbybadet","municipality":"NorrtΓ€lje","lat":60.0563,"lon":18.763},{"id":"SE0822581000002766","name":"BerglundstjΓ€rn, Arnemark","municipality":"PiteΓ₯","lat":65.49288,"lon":21.20916},{"id":"SE0712284000003055","name":"Bergoms badplats","municipality":"ΓrnskΓΆldsvik","lat":63.23054,"lon":18.57253},{"id":"SE0110188000002164","name":"Bergshamra, Mora","municipality":"NorrtΓ€lje","lat":59.63366,"lon":18.66484},{"id":"SE0A21480000000388","name":"BergsjΓΆn","municipality":"GΓΆteborg","lat":57.74951,"lon":12.06439},{"id":"SE0110115000001995","name":"BergsjΓΆn","municipality":"Vallentuna","lat":59.64925,"lon":18.27316},{"id":"SE0812480000002805","name":"Bettnesands havsbad","municipality":"UmeΓ₯","lat":63.6623,"lon":20.09146},{"id":"SE0A21480000000519","name":"Billdal","municipality":"GΓΆteborg","lat":57.58102,"lon":11.93108},{"id":"SE0441281000000305","name":"BillebjΓ€r","municipality":"Lund","lat":55.68863,"lon":13.31761},{"id":"SE0441264000000308","name":"Bingsmarken","municipality":"Skurup","lat":55.38453,"lon":13.49858},{"id":"SE0812480000004418","name":"BjennsjΓΆns norra badplats","municipality":"UmeΓ₯","lat":63.8086,"lon":19.98853},{"id":"SE0822514000003187","name":"BjumistrΓ€sk","municipality":"Kalix","lat":65.86917,"lon":23.24936},{"id":"SE0622080000002933","name":"Bjursen, BjursΓ₯s","municipality":"Falun","lat":60.76745,"lon":15.46384},{"id":"SE0A21485000000582","name":"BjursjΓΆn","municipality":"Uddevalla","lat":58.38838,"lon":11.97257},{"id":"SE0930883000001624","name":"Bjursund","municipality":"VΓ€stervik","lat":57.9142,"lon":16.6008},{"id":"SE0812417000003745","name":"BjurtrΓ€skets badplats","municipality":"NorsjΓΆ","lat":64.96186,"lon":19.73658},{"id":"SE0910685000000825","name":"BjΓ€desjΓΆsjΓΆn, MyresjΓΆ","municipality":"Vetlanda","lat":57.35257,"lon":14.9703},{"id":"SE0812409000003122","name":"BjΓ€nnsjΓΆn","municipality":"Robertsfors","lat":64.23716,"lon":20.43638},{"id":"SE0441293000000422","name":"BjΓ€rlΓ₯ngens badplats","municipality":"HΓ€ssleholm","lat":56.2893,"lon":13.7277},{"id":"SE0632132000003029","name":"BjΓ€rtsjΓΆn, Ilsbo","municipality":"Nordanstig","lat":61.8709,"lon":16.9966},{"id":"SE0712281000003454","name":"BjΓ€ssjΓΆn Indals-Liden","municipality":"Sundsvall","lat":62.6311,"lon":17.0969},{"id":"SE0622085000002455","name":"BjΓΆrken","municipality":"Ludvika","lat":60.2628,"lon":14.99221},{"id":"SE0930880000000793","name":"BjΓΆrkenΓ€s","municipality":"Kalmar","lat":56.7123,"lon":16.3655},{"id":"SE0230513000001202","name":"BjΓΆrkern, ValΓΆ","municipality":"Kinda","lat":58.01475,"lon":15.92193},{"id":"SE0940980000000260","name":"BjΓΆrkhaga","municipality":"Gotland","lat":57.4038,"lon":18.1684},{"id":"SE0110136000002148","name":"BjΓΆrkholmen, MuskΓΆ","municipality":"Haninge","lat":58.9956,"lon":18.14529},{"id":"SE0930821000001933","name":"BjΓΆrkhultsbadet","municipality":"HΓΆgsby","lat":57.08542,"lon":15.6873},{"id":"SE0110139000001717","name":"BjΓΆrknΓ€s","municipality":"Upplands-Bro","lat":59.48538,"lon":17.60461},{"id":"SE0712283000003777","name":"BjΓΆrksjΓΆn","municipality":"SollefteΓ₯","lat":63.2261,"lon":17.58717},{"id":"SE0441293000000417","name":"BjΓΆrkvikens badplats, FinjasjΓΆn","municipality":"HΓ€ssleholm","lat":56.12111,"lon":13.73427},{"id":"SE0920760000001296","name":"BjΓΆrkΓ₯kragΓΆlen, Γ
seda","municipality":"Uppvidinge","lat":57.17213,"lon":15.33288},{"id":"SE0A11383000000627","name":"BjΓΆrkΓ€ng","municipality":"Varberg","lat":57.00866,"lon":12.34658},{"id":"SE0A21407000003876","name":"BjΓΆrkΓΆ, Klarvik","municipality":"ΓckerΓΆ","lat":57.74285,"lon":11.67078},{"id":"SE0110188000002181","name":"BjΓΆrkΓΆ, Marumsbadet","municipality":"NorrtΓ€lje","lat":59.83911,"lon":19.00637},{"id":"SE0930882000001951","name":"BjΓΆrnhult","municipality":"Oskarshamn","lat":57.3163,"lon":16.2725},{"id":"SE0110120000004475","name":"BjΓΆrnΓΆ Sandarna Lilla Sand","municipality":"VΓ€rmdΓΆ","lat":59.2154,"lon":18.5245},{"id":"SE0110120000004474","name":"BjΓΆrnΓΆ Sandarna Stora Sand","municipality":"VΓ€rmdΓΆ","lat":59.2174,"lon":18.5288},{"id":"SE0251980000002543","name":"BjΓΆrnΓΆbadet, Badviken","municipality":"VΓ€sterΓ₯s","lat":59.56621,"lon":16.6201},{"id":"SE0622061000002657","name":"BjΓΆrsjΓΆ, DammsjΓΆn","municipality":"Smedjebacken","lat":60.0397,"lon":15.3418},{"id":"SE0A11381000000186","name":"BjΓΆrsjΓΆn","municipality":"Laholm","lat":56.6081,"lon":13.2455},{"id":"SE0220461000001065","name":"Blacksta NyckelsjΓΆn","municipality":"Gnesta","lat":59.12822,"lon":17.17044},{"id":"SE0930883000001615","name":"Blankaholm","municipality":"VΓ€stervik","lat":57.5875,"lon":16.5169},{"id":"SE0A21419000000651","name":"Blekets badplats","municipality":"TjΓΆrn","lat":57.95282,"lon":11.56409},{"id":"SE0110188000002175","name":"BlidΓΆ, RΓ₯dmansholmen","municipality":"NorrtΓ€lje","lat":59.6219,"lon":18.944},{"id":"SE0611765000002312","name":"Blomma, LelΓ₯ng","municipality":"Γ
rjΓ€ng","lat":59.25481,"lon":11.96156},{"id":"SE0940980000004397","name":"BlΓ₯ Lagunen","municipality":"Gotland","lat":57.9188,"lon":18.9245},{"id":"SE0A21486000003648","name":"BlΓ₯hΓ€llorna, FΓ€ringen","municipality":"StrΓΆmstad","lat":58.9844,"lon":11.26416},{"id":"SE0920763000001126","name":"BlΓΆtan","municipality":"Tingsryd","lat":56.48759,"lon":15.07786},{"id":"SE0930881000000808","name":"BoasjΓΆ","municipality":"Nybro","lat":57.02709,"lon":15.87603},{"id":"SE0920781000001322","name":"BoasjΓΆns badplats","municipality":"Ljungby","lat":56.75129,"lon":13.59631},{"id":"SE0930882000001949","name":"Bockara","municipality":"Oskarshamn","lat":57.2656,"lon":16.052},{"id":"SE0632182000002708","name":"BocksjΓΆn","municipality":"SΓΆderhamn","lat":61.32642,"lon":16.8682},{"id":"SE0930862000003986","name":"BodasjΓΆn,","municipality":"Emmaboda","lat":56.75372,"lon":15.49137},{"id":"SE0A21462000000726","name":"BodasjΓΆn, PrΓ€ssebo","municipality":"Lilla Edet","lat":58.09933,"lon":12.26949},{"id":"SE0A21485000000599","name":"Bodele","municipality":"Uddevalla","lat":58.33133,"lon":11.90785},{"id":"SE0A21486000003649","name":"Bofors camping","municipality":"StrΓΆmstad","lat":58.8828,"lon":11.1425},{"id":"SE0110187000001353","name":"Bogesund, Fridhemsbadet","municipality":"Vaxholm","lat":59.3877,"lon":18.2867},{"id":"SE0611737000003325","name":"BograngstjΓ€rn","municipality":"Torsby","lat":60.70909,"lon":12.61894},{"id":"SE0A21490000001809","name":"BogrydssjΓΆns badplats, Svaneholm","municipality":"BorΓ₯s","lat":57.60551,"lon":12.7987},{"id":"SE0920764000001582","name":"Bohult","municipality":"Alvesta","lat":56.58325,"lon":14.4475},{"id":"SE0230581000001752","name":"Bolen, Bolenbadet","municipality":"NorrkΓΆping","lat":58.78105,"lon":16.15095},{"id":"SE0110114000001600","name":"BollstanΓ€sbadet","municipality":"Upplands-VΓ€sby","lat":59.4994,"lon":17.9638},{"id":"SE0920781000001304","name":"Bolmen, BolmsΓΆ camping","municipality":"Ljungby","lat":57.00762,"lon":13.71776},{"id":"SE0910662000001110","name":"Bolmen, KΓ₯rehamn","municipality":"Gislaved","lat":57.0163,"lon":13.70514},{"id":"SE0920781000001302","name":"Bolmen, MjΓ€len","municipality":"Ljungby","lat":56.90171,"lon":13.77899},{"id":"SE0920781000001307","name":"Bolmen, OdensjΓΆ badplats","municipality":"Ljungby","lat":56.86351,"lon":13.61526},{"id":"SE0920781000001308","name":"Bolmen, Skeens badplats","municipality":"Ljungby","lat":56.76214,"lon":13.67018},{"id":"SE0920781000001303","name":"Bolmen, TannΓ₯ker","municipality":"Ljungby","lat":56.95165,"lon":13.7659},{"id":"SE0920781000001305","name":"Bolmens samhΓ€lle badplats","municipality":"Ljungby","lat":56.81376,"lon":13.69003},{"id":"SE0110182000001189","name":"Boobadet","municipality":"Nacka","lat":59.30807,"lon":18.29318},{"id":"SE0241860000003272","name":"BorasjΓΆn","municipality":"LaxΓ₯","lat":58.9711,"lon":14.54825},{"id":"SE0930885000000473","name":"Borgholm, Kallbadhuset","municipality":"Borgholm","lat":56.87936,"lon":16.64468},{"id":"SE0930885000000471","name":"Borgholm, Mejeriviken","municipality":"Borgholm","lat":56.88413,"lon":16.66923},{"id":"SE0241882000004392","name":"BorgmΓ€starholmen","municipality":"Askersund","lat":58.87356,"lon":14.90979},{"id":"SE0220488000005595","name":"BorgmΓ€starholmen","municipality":"Trosa","lat":58.8848,"lon":17.57482},{"id":"SE0712260000003020","name":"BorgsjΓΆbyn","municipality":"Γ
nge","lat":62.52797,"lon":15.92156},{"id":"SE0611764000002874","name":"BorgvikssjΓΆn, SparnΓ€s","municipality":"Grums","lat":59.33833,"lon":12.95539},{"id":"SE0910662000001106","name":"BorlΓ₯ngsbadet","municipality":"Gislaved","lat":57.19123,"lon":13.39757},{"id":"SE0441282000000408","name":"Borstahusen, hamnen","municipality":"Landskrona","lat":55.8953,"lon":12.8002},{"id":"SE0441282000000409","name":"Borstahusen, Strandbyn","municipality":"Landskrona","lat":55.9007,"lon":12.8016},{"id":"SE0930884000001423","name":"Borstingen","municipality":"Vimmerby","lat":57.67087,"lon":15.92083},{"id":"SE0A21462000000729","name":"BorydsjΓΆn","municipality":"Lilla Edet","lat":58.1423,"lon":12.17448},{"id":"SE0441293000000424","name":"BosarpasjΓΆns badplats","municipality":"HΓ€ssleholm","lat":55.9688,"lon":13.7508},{"id":"SE0920760000001299","name":"BoskvarnasjΓΆn, Lindshammar","municipality":"Uppvidinge","lat":57.22998,"lon":15.14182},{"id":"SE0241880000002641","name":"Botaren, RΓΆda Sand","municipality":"Γrebro","lat":59.01226,"lon":15.57063},{"id":"SE0A21487000004223","name":"BoteredssjΓΆn","municipality":"VΓ€nersborg","lat":58.3684,"lon":12.2648},{"id":"SE0251960000003014","name":"Botten","municipality":"KungsΓΆr","lat":59.4527,"lon":16.07666},{"id":"SE0A21446000001386","name":"BottensjΓΆn campingen","municipality":"Karlsborg","lat":58.54235,"lon":14.49925},{"id":"SE0241885000002510","name":"BottenΓ₯n, Norsviken","municipality":"Lindesberg","lat":59.61612,"lon":15.22175},{"id":"SE0A21496000004517","name":"BoulognersjΓΆn","municipality":"SkΓΆvde","lat":58.39099,"lon":13.86073},{"id":"SE0A21427000000034","name":"Bovallstrand, Badholmarna","municipality":"SotenΓ€s","lat":58.4759,"lon":11.315},{"id":"SE0A21439000001361","name":"Bovattnet, Stigens badplats","municipality":"FΓ€rgelanda","lat":58.56563,"lon":12.07102},{"id":"SE0A21435000003904","name":"Boviken","municipality":"Tanum","lat":58.55554,"lon":11.24236},{"id":"SE0812482000003353","name":"Boviksbadet","municipality":"SkellefteΓ₯","lat":64.77859,"lon":21.1179},{"id":"SE0611782000002360","name":"Brattfors","municipality":"Filipstad","lat":59.67138,"lon":14.02337},{"id":"SE0251907000002865","name":"Brattheden, RamnΓ€s","municipality":"Surahammar","lat":59.80076,"lon":16.16677},{"id":"SE0441256000000289","name":"BreanΓ€s, Immeln","municipality":"Γstra GΓΆinge","lat":56.27746,"lon":14.30289},{"id":"SE0A11380000000381","name":"BrearedssjΓΆn","municipality":"Halmstad","lat":56.7176,"lon":13.1409},{"id":"SE0210381000001365","name":"Bredsand","municipality":"EnkΓΆping","lat":59.5905,"lon":17.07094},{"id":"SE0712281000003476","name":"Bredsand Njurunda","municipality":"Sundsvall","lat":62.3461,"lon":17.3672},{"id":"SE0241863000003428","name":"BredsjΓΆ badplats","municipality":"HΓ€llefors","lat":59.83412,"lon":14.73163},{"id":"SE0930882000001950","name":"Bredviken","municipality":"Oskarshamn","lat":57.3777,"lon":16.5744},{"id":"SE0812480000004412","name":"Bredviks havsbad, Obbola","municipality":"UmeΓ₯","lat":63.6832,"lon":20.30434},{"id":"SE0930883000004854","name":"Breviks badplats","municipality":"VΓ€stervik","lat":57.7692,"lon":16.6237},{"id":"SE0110138000002106","name":"Breviksbadet","municipality":"TyresΓΆ","lat":59.20913,"lon":18.39281},{"id":"SE0940980000000267","name":"Brissund","municipality":"Gotland","lat":57.7174,"lon":18.3771},{"id":"SE0910680000004316","name":"Brittebo lΓ€gergΓ₯rd","municipality":"JΓΆnkΓΆping","lat":57.80982,"lon":13.77546},{"id":"SE0110127000001394","name":"BrosjΓΆn, Brotorpsbadet","municipality":"Botkyrka","lat":59.17302,"lon":17.79094},{"id":"SE0230513000001206","name":"BrosjΓΆn, Ydrefors bad","municipality":"Kinda","lat":57.7941,"lon":15.53953},{"id":"SE0712281000003455","name":"BrudsjΓΆn Indals-Liden","municipality":"Sundsvall","lat":62.7592,"lon":16.7555},{"id":"SE0A21498000000899","name":"Bruksdammen","municipality":"Tidaholm","lat":58.21653,"lon":13.85419},{"id":"SE0722380000007380","name":"Brunflo badplats","municipality":"Γstersund","lat":63.0781,"lon":14.8168},{"id":"SE0712280000003736","name":"BrunnesjΓΆn, Stallarnas badplats","municipality":"HΓ€rnΓΆsand","lat":62.63989,"lon":17.67007},{"id":"SE0622039000004654","name":"Brunnsberg, KnΓ€rΓ₯n","municipality":"Γlvdalen","lat":61.29615,"lon":13.8963},{"id":"SE0110180000001864","name":"Brunnsvikens Strandbad","municipality":"Stockholm","lat":59.3618,"lon":18.0485},{"id":"SE0910680000004318","name":"Brunstorpsbadet","municipality":"JΓΆnkΓΆping","lat":57.82099,"lon":14.26698},{"id":"SE0230581000001737","name":"BrΓ₯viken, Kvarsebobadet","municipality":"NorrkΓΆping","lat":58.6386,"lon":16.59552},{"id":"SE0230581000001732","name":"BrΓ₯viken, LindΓΆbadet","municipality":"NorrkΓΆping","lat":58.6089,"lon":16.2644},{"id":"SE0220480000002086","name":"BrΓ₯viken, Parkbadet NΓ€vekvarn","municipality":"NykΓΆping","lat":58.6261,"lon":16.795},{"id":"SE0920781000001317","name":"BrΓ€kentorpsjΓΆn","municipality":"Ljungby","lat":56.80009,"lon":14.0318},{"id":"SE0210319000000779","name":"BrΓ€msand","municipality":"Γlvkarleby","lat":60.641,"lon":17.4611},{"id":"SE0822580000003230","name":"BrΓ€ndΓΆn","municipality":"LuleΓ₯","lat":65.68535,"lon":22.35426},{"id":"SE0110181000004288","name":"BrΓ€nningestrand","municipality":"SΓΆdertΓ€lje","lat":59.1601,"lon":17.6603},{"id":"SE0822581000002742","name":"BrΓ€nntrΓ€sket, Sjulsmark","municipality":"PiteΓ₯","lat":65.5269,"lon":21.41143},{"id":"SE0812480000004420","name":"BrΓ€nnΓ€set SΓΆrmjΓΆle","municipality":"UmeΓ₯","lat":63.66369,"lon":20.00371},{"id":"SE0A21480000000524","name":"BrΓ€nnΓΆ Ramsdal","municipality":"GΓΆteborg","lat":57.63371,"lon":11.77139},{"id":"SE0A21490000001810","name":"Bua badplats, Stora HΓ₯lsjΓΆn","municipality":"BorΓ₯s","lat":57.56888,"lon":12.6596},{"id":"SE0A11383000000624","name":"Bua BΓ₯le","municipality":"Varberg","lat":57.24492,"lon":12.11956},{"id":"SE0A11383000000625","name":"Bua Krokstad","municipality":"Varberg","lat":57.24048,"lon":12.10309},{"id":"SE0441293000004378","name":"Bubbarp/LursjΓΆn","municipality":"HΓ€ssleholm","lat":56.2633,"lon":13.8473},{"id":"SE0622023000002473","name":"BullsjΓΆn","municipality":"Malung","lat":60.68576,"lon":13.69657},{"id":"SE0230512000001919","name":"BulsjΓΆΓ₯n, Γsterby","municipality":"Ydre","lat":57.82419,"lon":15.26164},{"id":"SE0910680000003504","name":"BunnstrΓΆmsbadet","municipality":"JΓΆnkΓΆping","lat":58.00044,"lon":14.47598},{"id":"SE0822581000002755","name":"BunΓ€sviken, JΓ€vre","municipality":"PiteΓ₯","lat":65.14027,"lon":21.50052},{"id":"SE0940980000004500","name":"Burgsvik","municipality":"Gotland","lat":57.0341,"lon":18.2568},{"id":"SE0622085000002453","name":"BurtjΓ€rn","municipality":"Ludvika","lat":60.17845,"lon":15.16007},{"id":"SE0812482000003378","name":"BurtrΓ€sk LΓ₯ngnΓ€sbadet","municipality":"SkellefteΓ₯","lat":64.50849,"lon":20.7086},{"id":"SE0812482000003724","name":"BurtrΓ€sket, Kyrkholmen","municipality":"SkellefteΓ₯","lat":64.5164,"lon":20.66575},{"id":"SE0622084000002485","name":"BuskasjΓΆn","municipality":"Avesta","lat":60.2398,"lon":16.4594},{"id":"SE0712282000003435","name":"ButjΓ€rn","municipality":"Kramfors","lat":62.96848,"lon":17.92469},{"id":"SE0712260000003017","name":"Byberget","municipality":"Γ
nge","lat":62.49999,"lon":15.24719},{"id":"SE0722380000005056","name":"BynΓ€set/VΓ€stbyviken","municipality":"Γstersund","lat":63.1913,"lon":14.4548},{"id":"SE0930885000000483","name":"Byrum-Sandvik","municipality":"Borgholm","lat":57.23714,"lon":16.95899},{"id":"SE0712284000003061","name":"BysjΓΆbadet, SidensjΓΆ","municipality":"ΓrnskΓΆldsvik","lat":63.30078,"lon":18.3294},{"id":"SE0230561000001210","name":"BysjΓΆn","municipality":"Γ
tvidaberg","lat":58.1902,"lon":15.9936},{"id":"SE0611730000003520","name":"BysjΓΆn, HaganΓ€set","municipality":"Eda","lat":59.82079,"lon":12.33081},{"id":"SE0611730000003571","name":"BysjΓΆn, Lerot","municipality":"Eda","lat":59.8249,"lon":12.3626},{"id":"SE0812482000003357","name":"Byske Havsbad","municipality":"SkellefteΓ₯","lat":64.94544,"lon":21.2398},{"id":"SE0A21491000001018","name":"BystadssjΓΆn GΓ€llstad","municipality":"Ulricehamn","lat":57.63336,"lon":13.44126},{"id":"SE0A21490000001787","name":"ByttorpssjΓΆns badplats, BorΓ₯s","municipality":"BorΓ₯s","lat":57.7224,"lon":12.904},{"id":"SE0930885000000482","name":"Byxelkrok","municipality":"Borgholm","lat":57.32338,"lon":17.00376},{"id":"SE0611785000003990","name":"ByΓ€lven, KΓ€llarbacken","municipality":"SΓ€ffle","lat":59.15445,"lon":12.91666},{"id":"SE0611785000003410","name":"ByΓ€lven, Sandbackens badplats","municipality":"SΓ€ffle","lat":59.1613,"lon":12.9123},{"id":"SE0251962000002555","name":"BΓ₯gen","municipality":"Norberg","lat":60.04199,"lon":16.0707},{"id":"SE0220480000002085","name":"BΓ₯lsjΓΆn, BΓ₯lsjΓΆbadet","municipality":"NykΓΆping","lat":58.7181,"lon":16.599},{"id":"SE0230584000000723","name":"BΓ₯rstad","municipality":"Vadstena","lat":58.3968,"lon":14.712},{"id":"SE0622081000002416","name":"BΓ₯tstabadet","municipality":"BorlΓ€nge","lat":60.51052,"lon":15.37645},{"id":"SE0241861000002534","name":"BΓ€cksjΓΆn","municipality":"Hallsberg","lat":59.04493,"lon":14.97554},{"id":"SE0441282000000412","name":"BΓ€ckviken","municipality":"Landskrona","lat":55.9022,"lon":12.7215},{"id":"SE0441275000000043","name":"BΓ€lingesjΓΆn","municipality":"Perstorp","lat":56.25194,"lon":13.37573},{"id":"SE0712281000003471","name":"BΓ€nkΓ₯sviken AlnΓΆ","municipality":"Sundsvall","lat":62.366,"lon":17.4536},{"id":"SE0930885000000468","name":"BΓΆda hamn","municipality":"Borgholm","lat":57.23602,"lon":17.07573},{"id":"SE0930885000004480","name":"BΓΆda Riviera/ Kyrketorps familjecamping","municipality":"Borgholm","lat":57.25027,"lon":17.06518},{"id":"SE0930885000000469","name":"BΓΆda Sand","municipality":"Borgholm","lat":57.2751,"lon":17.05635},{"id":"SE0A21484000000553","name":"BΓΆkevik","municipality":"Lysekil","lat":58.24769,"lon":11.45299},{"id":"SE0230581000001748","name":"BΓΆksjΓΆn, BΓΆksjΓΆbadet","municipality":"NorrkΓΆping","lat":58.69689,"lon":16.34761},{"id":"SE0812480000002802","name":"BΓΆlesholmarna","municipality":"UmeΓ₯","lat":63.8258,"lon":20.22519},{"id":"SE0632180000004059","name":"BΓΆnaviken","municipality":"GΓ€vle","lat":60.73655,"lon":17.30443},{"id":"SE0230562000004191","name":"BΓΆnergΓ₯rden","municipality":"FinspΓ₯ng","lat":58.71639,"lon":15.71147},{"id":"SE0A21460000001407","name":"BΓΆnkasen Γrtingen, Γ
rbol","municipality":"Bengtsfors","lat":59.03554,"lon":12.14737},{"id":"SE0230562000000869","name":"BΓΆnnerbadet","municipality":"FinspΓ₯ng","lat":58.71283,"lon":15.75772},{"id":"SE0822505000004048","name":"Camp Geilas strandbad","municipality":"Arvidsjaur","lat":65.58271,"lon":19.19763},{"id":"SE0632180000004535","name":"Centrumbadet Boulognern","municipality":"GΓ€vle","lat":60.67362,"lon":17.11173},{"id":"SE0411080000000203","name":"Coldinubadet","municipality":"Karlskrona","lat":56.17869,"lon":15.62678},{"id":"SE0A21486000000105","name":"DaftΓΆ Camping","municipality":"StrΓΆmstad","lat":58.90007,"lon":11.19582},{"id":"SE0441267000000405","name":"DagstorpssjΓΆn VΓ€stra","municipality":"HΓΆΓΆr","lat":55.99139,"lon":13.4902},{"id":"SE0441267000004429","name":"DagstorpssjΓΆn Γstra","municipality":"HΓΆΓΆr","lat":55.988,"lon":13.4997},{"id":"SE0441287000000442","name":"Dalabadet","municipality":"Trelleborg","lat":55.3617,"lon":13.2096},{"id":"SE0812462000003696","name":"DalasjΓΆ","municipality":"Vilhelmina","lat":64.56473,"lon":16.90588},{"id":"SE0230581000005959","name":"Dalbystrand","municipality":"NorrkΓΆping","lat":58.4826,"lon":16.4755},{"id":"SE0812480000005498","name":"Dalesand, BΓ€cksjΓΆn","municipality":"UmeΓ₯","lat":63.947,"lon":20.378},{"id":"SE0A21490000001803","name":"DalsjΓΆns badplats, DalsjΓΆfors","municipality":"BorΓ₯s","lat":57.71689,"lon":13.11683},{"id":"SE0930834000001945","name":"DalskΓ€r","municipality":"TorsΓ₯s","lat":56.39124,"lon":16.09248},{"id":"SE0A21452000001877","name":"DalstorpasjΓΆn","municipality":"Tranemo","lat":57.60773,"lon":13.52738},{"id":"SE0930884000001440","name":"Dammen Gullringen","municipality":"Vimmerby","lat":57.80154,"lon":15.72616},{"id":"SE0632104000002788","name":"Dammen, DalgrΓ€nsen","municipality":"Hofors","lat":60.38801,"lon":16.45613},{"id":"SE0230562000000881","name":"Dammen, GrytgΓΆl","municipality":"FinspΓ₯ng","lat":58.80551,"lon":15.55863},{"id":"SE0622085000002450","name":"DammsjΓΆn","municipality":"Ludvika","lat":60.09965,"lon":15.10858},{"id":"SE0241885000002498","name":"DammsjΓΆn","municipality":"Lindesberg","lat":59.73508,"lon":15.20562},{"id":"SE0632104000002791","name":"DammsjΓΆn, Barkhyttan","municipality":"Hofors","lat":60.55394,"lon":16.3829},{"id":"SE0622081000004263","name":"Dammyran","municipality":"BorlΓ€nge","lat":60.47293,"lon":15.35422},{"id":"SE0812482000003358","name":"Danielstens havsbad","municipality":"SkellefteΓ₯","lat":64.60789,"lon":21.24398},{"id":"SE0920764000001598","name":"DansjΓΆn","municipality":"Alvesta","lat":56.94292,"lon":14.57494},{"id":"SE0241862000002254","name":"DegernΓ€s camping","municipality":"Degerfors","lat":59.25199,"lon":14.45995},{"id":"SE0241862000002253","name":"DegernΓ€sbadet","municipality":"Degerfors","lat":59.25699,"lon":14.451},{"id":"SE0812462000003705","name":"DikanΓ€s","municipality":"Vilhelmina","lat":65.22323,"lon":15.9448},{"id":"SE0220483000001141","name":"DjulΓΆsjΓΆn","municipality":"Katrineholm","lat":58.96895,"lon":16.21193},{"id":"SE0A11383000000635","name":"Djupa DrΓ€kt","municipality":"Varberg","lat":57.09829,"lon":12.24213},{"id":"SE0920763000001128","name":"DjupasjΓΆn, YxnanΓ€s","municipality":"Tingsryd","lat":56.49827,"lon":15.3093},{"id":"SE0930885000000479","name":"Djupvik","municipality":"Borgholm","lat":57.01794,"lon":16.82429},{"id":"SE0210360000000980","name":"DjupΓΆbadet","municipality":"Tierp","lat":60.54008,"lon":17.62715},{"id":"SE0A21481000003964","name":"DjursjΓΆn","municipality":"MΓΆlndal","lat":57.57011,"lon":12.18273},{"id":"SE0441283000000319","name":"Domsten","municipality":"Helsingborg","lat":56.11706,"lon":12.60276},{"id":"SE0A21480000000511","name":"DonsΓΆ LΓΆkholmen","municipality":"GΓΆteborg","lat":57.60116,"lon":11.81099},{"id":"SE0812425000002823","name":"Dorotea Camping","municipality":"Dorotea","lat":64.25607,"lon":16.38454},{"id":"SE0812480000002801","name":"Dragonudden, StΓΆcksjΓΆns ΓΆstra badplats","municipality":"UmeΓ₯","lat":63.7603,"lon":20.2006},{"id":"SE0930882000001952","name":"DragskΓ€r","municipality":"Oskarshamn","lat":57.3103,"lon":16.5668},{"id":"SE0411080000000198","name":"DragsΓΆ barnbadvik","municipality":"Karlskrona","lat":56.17333,"lon":15.56594},{"id":"SE0622039000004525","name":"Drevdagens badplats","municipality":"Γlvdalen","lat":61.76629,"lon":12.4044},{"id":"SE0110126000002122","name":"Drevviken, Badudden","municipality":"Huddinge","lat":59.2125,"lon":18.1748},{"id":"SE0110136000002139","name":"Drevviken, Dalen","municipality":"Haninge","lat":59.20465,"lon":18.18059},{"id":"SE0110126000002119","name":"Drevviken, LΓ€nnabadet","municipality":"Huddinge","lat":59.2018,"lon":18.1551},{"id":"SE0110126000002120","name":"Drevviken, MΓΆrtviksbadet","municipality":"Huddinge","lat":59.21833,"lon":18.16928},{"id":"SE0110136000004343","name":"Drevviken, Norrby holme","municipality":"Haninge","lat":59.20042,"lon":18.15649},{"id":"SE0110126000002121","name":"Drevviken, SjΓΆΓ€ngsbadet","municipality":"Huddinge","lat":59.2347,"lon":18.1585},{"id":"SE0110126000002117","name":"Drevviken, Stortorpsbadet","municipality":"Huddinge","lat":59.2393,"lon":18.1395},{"id":"SE0110136000002138","name":"Drevviken, VendelsΓΆ GΓ₯rd","municipality":"Haninge","lat":59.20803,"lon":18.19221},{"id":"SE0110117000002093","name":"DrΓ€ngsjΓΆn, Domarudden","municipality":"ΓsterΓ₯ker","lat":59.51636,"lon":18.34296},{"id":"SE0712284000003059","name":"DrΓΆmmebadet","municipality":"ΓrnskΓΆldsvik","lat":63.25085,"lon":18.27372},{"id":"SE0910682000002065","name":"DundersjΓΆn, Γngs badpl.","municipality":"NΓ€ssjΓΆ","lat":57.70061,"lon":14.55203},{"id":"SE0611785000003412","name":"Duse Camping","municipality":"SΓ€ffle","lat":59.0812,"lon":12.8881},{"id":"SE0611785000003413","name":"Duse klippbad","municipality":"SΓ€ffle","lat":59.079,"lon":12.8802},{"id":"SE0220483000001142","name":"DuveholmssjΓΆn","municipality":"Katrineholm","lat":58.98298,"lon":16.17384},{"id":"SE0712281000003477","name":"Dyket Njurunda","municipality":"Sundsvall","lat":62.2966,"lon":17.3953},{"id":"SE0712282000003437","name":"DΓ€mstasjΓΆn","municipality":"Kramfors","lat":63.05229,"lon":17.81386},{"id":"SE0712281000003467","name":"Edeforsen StΓΆde","municipality":"Sundsvall","lat":62.4182,"lon":16.4908},{"id":"SE0441272000000295","name":"Edenryds badplats","municipality":"BromΓΆlla","lat":56.03876,"lon":14.52173},{"id":"SE0930883000001641","name":"Edsbruks badplats (StorsjΓΆn)","municipality":"VΓ€stervik","lat":58.0167,"lon":16.4658},{"id":"SE0632104000002783","name":"Edsken","municipality":"Hofors","lat":60.54062,"lon":16.22415},{"id":"SE0A21492000000944","name":"Edslans badplats","municipality":"Γ
mΓ₯l","lat":59.07342,"lon":12.45135},{"id":"SE0622026000002621","name":"EdstjΓ€rn","municipality":"Gagnef","lat":60.5684,"lon":15.0906},{"id":"SE0A21435000003905","name":"Edsvik","municipality":"Tanum","lat":58.71106,"lon":11.22862},{"id":"SE0632181000002989","name":"Edsviken","municipality":"Sandviken","lat":60.28307,"lon":16.80654},{"id":"SE0110163000004528","name":"Edsviken Badberget","municipality":"Sollentuna","lat":59.43659,"lon":17.96748},{"id":"SE0110163000003635","name":"Edsviken SjΓΆberg","municipality":"Sollentuna","lat":59.42538,"lon":17.98509},{"id":"SE0110162000004210","name":"Edsviken, Kevinge Strand","municipality":"Danderyd","lat":59.39605,"lon":18.01863},{"id":"SE0110163000000818","name":"Edsviken, StrandvΓ€gen","municipality":"Sollentuna","lat":59.43144,"lon":17.96098},{"id":"SE0110162000001113","name":"Edsviken, SΓ€tra Γ€ng","municipality":"Danderyd","lat":59.4168,"lon":17.9984},{"id":"SE0110163000003636","name":"Edsviken, Tegelhagens badplats","municipality":"Sollentuna","lat":59.41183,"lon":17.98985},{"id":"SE0411081000000151","name":"EkenΓ€s","municipality":"Ronneby","lat":56.17249,"lon":15.28025},{"id":"SE0930880000000780","name":"EkenΓ€s","municipality":"Kalmar","lat":56.5126,"lon":16.1849},{"id":"SE0910685000000826","name":"EkenΓ€ssjΓΆn","municipality":"Vetlanda","lat":57.48495,"lon":15.0163},{"id":"SE0930885000000474","name":"Ekerums camping","municipality":"Borgholm","lat":56.79646,"lon":16.561},{"id":"SE0441290000000672","name":"Ekestads badplats","municipality":"Kristianstad","lat":56.1264,"lon":14.2518},{"id":"SE0940980000000231","name":"Ekeviken","municipality":"Gotland","lat":57.9729,"lon":19.252},{"id":"SE0110180000001863","name":"Ekhagens strandbad","municipality":"Stockholm","lat":59.37252,"lon":18.06911},{"id":"SE0210305000000759","name":"Ekillabadet","municipality":"HΓ₯bo","lat":59.6055,"lon":17.5104},{"id":"SE0910684000003789","name":"EksjΓΆhofgΓ₯rdssjΓΆn, nya badet","municipality":"SΓ€vsjΓΆ","lat":57.39183,"lon":14.68338},{"id":"SE0251960000003012","name":"Ekudden","municipality":"KungsΓΆr","lat":59.43333,"lon":16.10999},{"id":"SE0930880000000782","name":"EkΓΆ","municipality":"Kalmar","lat":56.6482,"lon":16.2816},{"id":"SE0230563000001315","name":"EkΓΆn, Gryt","municipality":"Valdemarsvik","lat":58.17304,"lon":16.85029},{"id":"SE0611785000003411","name":"Eldans badplats","municipality":"SΓ€ffle","lat":59.256,"lon":12.6399},{"id":"SE0411082000000164","name":"Elleholm","municipality":"Karlshamn","lat":56.1581,"lon":14.7396},{"id":"SE0A21471000007500","name":"Emils bad","municipality":"GΓΆtene","lat":58.6317,"lon":13.4457},{"id":"SE0910685000000827","name":"EmΓ₯n, Kyrkviken","municipality":"Vetlanda","lat":57.40196,"lon":15.07654},{"id":"SE0930880000000796","name":"Enerevet","municipality":"Kalmar","lat":56.7849,"lon":16.4311},{"id":"SE0632180000002890","name":"Engeltofta","municipality":"GΓ€vle","lat":60.70389,"lon":17.23892},{"id":"SE0632180000002891","name":"Engesberg, GΓ€vle camping","municipality":"GΓ€vle","lat":60.73016,"lon":17.28963},{"id":"SE0622031000002974","name":"Ensen","municipality":"RΓ€ttvik","lat":60.95505,"lon":15.21451},{"id":"SE0230581000001740","name":"EnsjΓΆn, EnsjΓΆbadet","municipality":"NorrkΓΆping","lat":58.5472,"lon":16.2157},{"id":"SE0210331000002565","name":"EnΓ₯ker, Venenbadet","municipality":"Heby","lat":60.04211,"lon":16.73823},{"id":"SE0110187000001354","name":"EriksΓΆbadet","municipality":"Vaxholm","lat":59.40441,"lon":18.30376},{"id":"SE0930840000000018","name":"EriksΓΆre camping, EriksΓΆre","municipality":"MΓΆrbylΓ₯nga","lat":56.61848,"lon":16.44295},{"id":"SE0110188000002159","name":"Erken, Svanberga","municipality":"NorrtΓ€lje","lat":59.8373,"lon":18.6546},{"id":"SE0110182000001231","name":"Erstaviksbadet","municipality":"Nacka","lat":59.26952,"lon":18.26581},{"id":"SE0251982000002536","name":"Eskiln","municipality":"Fagersta","lat":59.97897,"lon":15.7926},{"id":"SE0441278000000141","name":"Eskilstorps strand","municipality":"BΓ₯stad","lat":56.4419,"lon":12.8855},{"id":"SE0A11383000000644","name":"Espevik","municipality":"Varberg","lat":57.18976,"lon":12.18829},{"id":"SE0241882000002519","name":"EstabosjΓΆn","municipality":"Askersund","lat":58.9818,"lon":15.06089},{"id":"SE0622039000003669","name":"Evertsbergs badplats, SkepphussjΓΆn","municipality":"Γlvdalen","lat":61.1226,"lon":13.91434},{"id":"SE0920781000001325","name":"Exen, Bolmaryd","municipality":"Ljungby","lat":56.6884,"lon":13.75423},{"id":"SE0210360000000981","name":"Fagerviken","municipality":"Tierp","lat":60.54725,"lon":17.75515},{"id":"SE0220483000001155","name":"FagerΓΆn","municipality":"Katrineholm","lat":58.83289,"lon":16.34886},{"id":"SE0812482000003360","name":"FalktrΓ€sket","municipality":"SkellefteΓ₯","lat":64.7309,"lon":20.91649},{"id":"SE0622084000002478","name":"Falkudden","municipality":"Avesta","lat":60.18191,"lon":16.47908},{"id":"SE0441233000000024","name":"Falsterbo strandbad","municipality":"Vellinge","lat":55.38706,"lon":12.83986},{"id":"SE0441284000000501","name":"Farhultsbaden","municipality":"HΓΆganΓ€s","lat":56.22446,"lon":12.71267},{"id":"SE0110180000001866","name":"Farsta strandbad, Magelungen","municipality":"Stockholm","lat":59.2366,"lon":18.0843},{"id":"SE0110181000001355","name":"FarstanΓ€sbadet","municipality":"SΓΆdertΓ€lje","lat":59.0971,"lon":17.65426},{"id":"SE0441257000000430","name":"FedingesjΓΆns badplats","municipality":"Γrkelljunga","lat":56.37611,"lon":13.46951},{"id":"SE0910662000001112","name":"Fegens badplats","municipality":"Gislaved","lat":57.16942,"lon":13.14118},{"id":"SE0A11382000002853","name":"FegensjΓΆn ","municipality":"Falkenberg","lat":57.1071,"lon":13.0379},{"id":"SE0110117000007600","name":"Femsundsbadet","municipality":"ΓsterΓ₯ker","lat":59.4703,"lon":18.771},{"id":"SE0441293000000419","name":"FinjasjΓΆn, MjΓΆlkalΓ₯nga","municipality":"HΓ€ssleholm","lat":56.1386,"lon":13.6714},{"id":"SE0241883000007480","name":"FinnebΓ€cksbadet","municipality":"Karlskoga","lat":59.2939,"lon":14.5052},{"id":"SE0A21401000006720","name":"FinnsjΓΆn","municipality":"HΓ€rryda","lat":57.6359,"lon":12.1297},{"id":"SE0822580000003237","name":"First Camps strandbad","municipality":"LuleΓ₯","lat":65.59478,"lon":22.08972},{"id":"SE0220483000001118","name":"Fiskeboda","municipality":"Katrineholm","lat":59.1859,"lon":15.89088},{"id":"SE0A21480000000516","name":"FiskebΓ€ck","municipality":"GΓΆteborg","lat":57.64832,"lon":11.84786},{"id":"SE0A21484000000573","name":"FiskebΓ€cksvik","municipality":"Lysekil","lat":58.33775,"lon":11.40515},{"id":"SE0110182000001227","name":"FisksΓ€trabadet","municipality":"Nacka","lat":59.29704,"lon":18.25901},{"id":"SE0210380000001175","name":"FjΓ€llnora Naturistbad","municipality":"Uppsala","lat":59.82971,"lon":17.91371},{"id":"SE0210380000001180","name":"FjΓ€llnorabadet","municipality":"Uppsala","lat":59.83154,"lon":17.91204},{"id":"SE0611730000003572","name":"FjΓ€llsjΓΆn, Djupfors","municipality":"Eda","lat":59.8242,"lon":12.4393},{"id":"SE0110120000001665","name":"Fladen","municipality":"VΓ€rmdΓΆ","lat":59.287,"lon":18.4312},{"id":"SE0A21491000001019","name":"FlatasjΓΆn Dalum","municipality":"Ulricehamn","lat":57.89392,"lon":13.46534},{"id":"SE0910617000000929","name":"Flaten","municipality":"GnosjΓΆ","lat":57.35929,"lon":13.93005},{"id":"SE0910662000001109","name":"Flatenbadet","municipality":"Gislaved","lat":57.10001,"lon":13.62597},{"id":"SE0110180000001867","name":"Flatenbadet, allmΓ€nna","municipality":"Stockholm","lat":59.2525,"lon":18.1588},{"id":"SE0110180000001868","name":"Flatenbadet, barnbadet","municipality":"Stockholm","lat":59.2565,"lon":18.156},{"id":"SE0622081000002413","name":"Flatnanbadet","municipality":"BorlΓ€nge","lat":60.33374,"lon":15.16243},{"id":"SE0930881000004405","name":"Flerohopp","municipality":"Nybro","lat":56.82612,"lon":15.87156},{"id":"SE0910683000004255","name":"Flickornas Udde","municipality":"VΓ€rnamo","lat":57.19864,"lon":14.34088},{"id":"SE0622026000002614","name":"FlosjΓΆn, SjΓΆviken","municipality":"Gagnef","lat":60.5161,"lon":14.8086},{"id":"SE0920781000001320","name":"Flyxen, Vittaryd","municipality":"Ljungby","lat":56.9719,"lon":13.89327},{"id":"SE0A21495000000805","name":"FlΓ€mslΓ€tt","municipality":"Skara","lat":58.46043,"lon":13.67895},{"id":"SE0712284000003048","name":"FlΓ€rkenbadet, NorrflΓ€rke","municipality":"ΓrnskΓΆldsvik","lat":63.50669,"lon":18.08325},{"id":"SE0812480000004413","name":"FlΓ€se havsbad","municipality":"UmeΓ₯","lat":63.65964,"lon":20.2859},{"id":"SE0712281000003450","name":"FlΓ€sian NO","municipality":"Sundsvall","lat":62.3611,"lon":17.3715},{"id":"SE0712281000003480","name":"FlΓ€sian SΓ","municipality":"Sundsvall","lat":62.3595,"lon":17.3726},{"id":"SE0622039000004526","name":"FlΓΆtningens badplats","municipality":"Γlvdalen","lat":61.86579,"lon":12.19222},{"id":"SE0940980000005677","name":"Follingbo kalkbrott","municipality":"Gotland","lat":57.6078,"lon":18.3611},{"id":"SE0930882000001954","name":"Forshult","municipality":"Oskarshamn","lat":57.2558,"lon":16.3218},{"id":"SE0611782000002352","name":"Forshyttan","municipality":"Filipstad","lat":59.73206,"lon":13.88233},{"id":"SE0220483000001143","name":"ForssjΓΆsjΓΆn","municipality":"Katrineholm","lat":58.95205,"lon":16.29513},{"id":"SE0611765000002313","name":"ForstjΓ€rn, Lennartsfors","municipality":"Γ
rjΓ€ng","lat":59.32026,"lon":11.89615},{"id":"SE0A21485000004013","name":"Fossens Camping","municipality":"Uddevalla","lat":58.3242,"lon":11.56267},{"id":"SE0A21407000003873","name":"FotΓΆ, Vivik","municipality":"ΓckerΓΆ","lat":57.6708,"lon":11.66264},{"id":"SE0611765000002307","name":"Foxen, VΓ€stra FΓ₯gelvik","municipality":"Γ
rjΓ€ng","lat":59.44971,"lon":11.85295},{"id":"SE0251980000002541","name":"FramnΓ€s, Badviken","municipality":"VΓ€sterΓ₯s","lat":59.59408,"lon":16.59018},{"id":"SE0251961000002656","name":"Freden, BorgΓ₯sunds badplats","municipality":"Hallstahammar","lat":59.50873,"lon":16.26431},{"id":"SE0110180000004457","name":"FredhΓ€llsbadet, MΓ€laren","municipality":"Stockholm","lat":59.3304,"lon":17.9959},{"id":"SE0910682000002022","name":"FredriksdalssjΓΆn,Fr.d. badpl.","municipality":"NΓ€ssjΓΆ","lat":57.60856,"lon":14.60915},{"id":"SE0210330000001161","name":"Fredrikslund, Oxtorget","municipality":"Knivsta","lat":59.76525,"lon":17.63107},{"id":"SE0110187000004075","name":"Fredriksstrandsbadet","municipality":"Vaxholm","lat":59.40021,"lon":18.33392},{"id":"SE0822514000003262","name":"FrevisΓΆrens havsbad","municipality":"Kalix","lat":65.75819,"lon":23.40348},{"id":"SE0441283000003890","name":"Fria bad","municipality":"Helsingborg","lat":56.05795,"lon":12.68042},{"id":"SE0A21484000000561","name":"Fridhem","municipality":"Lysekil","lat":58.2764,"lon":11.4558},{"id":"SE0A21463000003945","name":"Fritsla, FuresjΓΆn ","municipality":"Mark","lat":57.5379,"lon":12.8055},{"id":"SE0A21490000003595","name":"FrufΓ€llans badplats, ΓresjΓΆ","municipality":"BorΓ₯s","lat":57.77556,"lon":12.97341},{"id":"SE0611766000002361","name":"Fryken, BjΓ€lverud","municipality":"Sunne","lat":60.01149,"lon":13.10479},{"id":"SE0611766000003826","name":"Fryken, KolsnΓ€sudden","municipality":"Sunne","lat":59.82364,"lon":13.14142},{"id":"SE0611766000002365","name":"Fryken, Lysvik strandbad","municipality":"Sunne","lat":60.0135,"lon":13.11538},{"id":"SE0611766000002364","name":"Fryken, Malviken","municipality":"Sunne","lat":59.77024,"lon":13.12075},{"id":"SE0611766000004293","name":"Fryken, SillegΓ₯rden","municipality":"Sunne","lat":59.7248,"lon":13.16103},{"id":"SE0622080000004383","name":"FrΓ€mby udde","municipality":"Falun","lat":60.58047,"lon":15.67622},{"id":"SE0110139000004442","name":"FrΓΆlunda badplats (klippbad)","municipality":"Upplands-Bro","lat":59.44269,"lon":17.69395},{"id":"SE0110139000004443","name":"FrΓΆlunda, vid grillhus (sandstrand)","municipality":"Upplands-Bro","lat":59.44157,"lon":17.69848},{"id":"SE0A11380000003940","name":"FrΓΆsakull","municipality":"Halmstad","lat":56.66979,"lon":12.72341},{"id":"SE0A11380000002975","name":"FrΓΆsakull, First camp","municipality":"Halmstad","lat":56.65996,"lon":12.72927},{"id":"SE0220461000005175","name":"FrΓΆsjΓΆstrand","municipality":"Gnesta","lat":59.0515,"lon":17.3066},{"id":"SE0722380000004184","name":"FrΓΆsΓΆ Surfbukten","municipality":"Γstersund","lat":63.1815,"lon":14.6241},{"id":"SE0930834000001947","name":"Fulvik","municipality":"TorsΓ₯s","lat":56.46012,"lon":16.13063},{"id":"SE0722361000004138","name":"FunΓ€sdalssjΓΆn","municipality":"HΓ€rjedalen","lat":62.53285,"lon":12.55974},{"id":"SE0910683000001483","name":"Furen, Hyltans badplats","municipality":"VΓ€rnamo","lat":57.04735,"lon":14.17765},{"id":"SE0441290000000671","name":"Furestads badplats","municipality":"Kristianstad","lat":56.1621,"lon":14.3813},{"id":"SE0910643000000719","name":"FurusjΓΆn","municipality":"Habo","lat":57.933,"lon":13.9579},{"id":"SE0A21490000004510","name":"FurusjΓΆns badplats, Rydboholm","municipality":"BorΓ₯s","lat":57.64947,"lon":12.86037},{"id":"SE0210380000003908","name":"FyrisΓ₯n Skyttorpsbadet","municipality":"Uppsala","lat":60.07775,"lon":17.75253},{"id":"SE0210380000001177","name":"FyrisΓ₯n, Storvadsbadet","municipality":"Uppsala","lat":59.90494,"lon":17.62533},{"id":"SE0210380000003866","name":"FyrisΓ₯n, Storvretabadet","municipality":"Uppsala","lat":59.96107,"lon":17.68838},{"id":"SE0210380000005939","name":"FyrisΓ₯n, Ulvabadet","municipality":"Uppsala","lat":59.914,"lon":17.5791},{"id":"SE0110114000001599","name":"Fysingen Naturbad","municipality":"Upplands-VΓ€sby","lat":59.5462,"lon":17.9186},{"id":"SE0110186000001466","name":"FΓ₯gelΓΆudde","municipality":"LidingΓΆ","lat":59.37614,"lon":18.23129},{"id":"SE0930882000001953","name":"FΓ₯rbosjΓΆn","municipality":"Oskarshamn","lat":57.3692,"lon":16.4746},{"id":"SE0812401000003541","name":"FΓ₯rskΓ€ret","municipality":"Nordmaling","lat":63.4516,"lon":19.6323},{"id":"SE0930840000000020","name":"FΓ€rjestaden, Granudden/Talludd","municipality":"MΓΆrbylΓ₯nga","lat":56.64564,"lon":16.46097},{"id":"SE0930840000005476","name":"FΓ€rjestadens hamn","municipality":"MΓΆrbylΓ₯nga","lat":56.64931,"lon":16.46007},{"id":"SE0632182000002706","name":"FΓ€rsjΓΆn","municipality":"SΓΆderhamn","lat":61.30175,"lon":17.0857},{"id":"SE0A21481000000398","name":"FΓ€rΓ₯s tjΓ€rns Badplats, BergsjΓΆn","municipality":"MΓΆlndal","lat":57.6113,"lon":12.0284},{"id":"SE0A11383000005575","name":"FΓ€stningsbadet","municipality":"Varberg","lat":57.10781,"lon":12.23684},{"id":"SE0632184000002996","name":"FΓΆnebo, Norra Dellen","municipality":"Hudiksvall","lat":61.9211,"lon":16.713},{"id":"SE0930884000004395","name":"FΓΆrsjΓΆn","municipality":"Vimmerby","lat":57.65724,"lon":15.70889},{"id":"SE0910686000001579","name":"FΓΆrsjΓΆn, MovΓ€ntabadet","municipality":"EksjΓΆ","lat":57.65651,"lon":15.12344},{"id":"SE0712284000003047","name":"Gafselebadet, Solberg","municipality":"ΓrnskΓΆldsvik","lat":63.79233,"lon":17.57531},{"id":"SE0241861000002535","name":"GallabergssjΓΆn","municipality":"Hallsberg","lat":58.98712,"lon":14.82196},{"id":"SE0411081000000158","name":"GaltsjΓΆn","municipality":"Ronneby","lat":56.37506,"lon":14.97788},{"id":"SE0632183000002903","name":"Galven","municipality":"BollnΓ€s","lat":61.4526,"lon":16.2325},{"id":"SE0632121000002714","name":"Galven, bygdegΓ₯rden","municipality":"OvanΓ₯ker","lat":61.4603,"lon":16.18776},{"id":"SE0441267000001340","name":"Gamla Boo","municipality":"HΓΆΓΆr","lat":55.873,"lon":13.4913},{"id":"SE0930861000004814","name":"Gamla fΓ€rjelΓ€get OknΓΆ","municipality":"MΓΆnsterΓ₯s","lat":57.0031,"lon":16.5297},{"id":"SE0A21472000000753","name":"Gamla Γrlenbadet","municipality":"Tibro","lat":58.46503,"lon":14.24936},{"id":"SE0930883000001629","name":"Gammalskeda badplats (Loftern)","municipality":"VΓ€stervik","lat":58.051,"lon":16.0583},{"id":"SE0632180000002892","name":"Gammel-Anna","municipality":"GΓ€vle","lat":60.7582,"lon":17.3343},{"id":"SE0A21480000004250","name":"Ganlet","municipality":"GΓΆteborg","lat":57.62991,"lon":11.87695},{"id":"SE0611780000002388","name":"Gapern ΓrnΓ€s","municipality":"Karlstad","lat":59.47693,"lon":13.73273},{"id":"SE0622039000004512","name":"Garbergs badplats","municipality":"Γlvdalen","lat":61.10817,"lon":14.26446},{"id":"SE0812422000004171","name":"GargnΓ€s Lagun","municipality":"Sorsele","lat":65.313,"lon":17.9695},{"id":"SE0110117000002092","name":"Garnsviksbadet","municipality":"ΓsterΓ₯ker","lat":59.514,"lon":18.2675},{"id":"SE0A11381000000188","name":"GatessjΓΆn","municipality":"Laholm","lat":56.5292,"lon":13.193},{"id":"SE0822582000003214","name":"GemtrΓ€sk","municipality":"Boden","lat":65.87951,"lon":22.01875},{"id":"SE0622021000002829","name":"Gensens badplats","municipality":"Vansbro","lat":60.573,"lon":14.40806},{"id":"SE0A21489000001709","name":"Gerdsken, GrΓ€sbadet","municipality":"AlingsΓ₯s","lat":57.92005,"lon":12.54784},{"id":"SE0A21489000003911","name":"Gerdsken, HΓ€stabadet","municipality":"AlingsΓ₯s","lat":57.92231,"lon":12.54159},{"id":"SE0A21489000007200","name":"Gerdsken, Sandbadet","municipality":"AlingsΓ₯s","lat":57.9182,"lon":12.5497},{"id":"SE0A21489000007180","name":"Gerdsken, Tarzanbadet","municipality":"AlingsΓ₯s","lat":57.9162,"lon":12.5447},{"id":"SE0A21489000007181","name":"Gerdsken, Γngabo","municipality":"AlingsΓ₯s","lat":57.911,"lon":12.5485},{"id":"SE0930884000001426","name":"GerssjΓΆn","municipality":"Vimmerby","lat":57.74522,"lon":16.04449},{"id":"SE0A21443000003856","name":"Gesebol, StockasjΓΆn","municipality":"Bollebygd","lat":57.72846,"lon":12.68186},{"id":"SE0110127000001391","name":"Getaren, Lidabadet","municipality":"Botkyrka","lat":59.16127,"lon":17.88206},{"id":"SE0611785000003582","name":"Getebol","municipality":"SΓ€ffle","lat":59.1043,"lon":12.8535},{"id":"SE0920767000000690","name":"GetesjΓΆn","municipality":"Markaryd","lat":56.4625,"lon":13.60521},{"id":"SE0A21415000000107","name":"GetskΓ€r","municipality":"Stenungsund","lat":58.0156,"lon":11.7989},{"id":"SE0A11383000000638","name":"GetterΓΆn 1:a vik","municipality":"Varberg","lat":57.11295,"lon":12.22371},{"id":"SE0A11383000000639","name":"GetterΓΆn 3:e vik","municipality":"Varberg","lat":57.11372,"lon":12.2162},{"id":"SE0A11383000000640","name":"GetterΓΆn 4:e vik","municipality":"Varberg","lat":57.11458,"lon":12.21165},{"id":"SE0A11383000000641","name":"GetterΓΆn 5:e vik","municipality":"Varberg","lat":57.1181,"lon":12.20256},{"id":"SE0712284000003070","name":"GideΓ₯badet","municipality":"ΓrnskΓΆldsvik","lat":63.48324,"lon":18.96346},{"id":"SE0110188000004894","name":"GillfjΓ€rden","municipality":"NorrtΓ€lje","lat":59.79806,"lon":18.73169},{"id":"SE0210382000001724","name":"Gimobadet","municipality":"Γsthammar","lat":60.1842,"lon":18.1707},{"id":"SE0A21401000000063","name":"GingsjΓΆn","municipality":"HΓ€rryda","lat":57.63181,"lon":12.4518},{"id":"SE0110188000002184","name":"Gisen","municipality":"NorrtΓ€lje","lat":59.8801,"lon":18.4975},{"id":"SE0441287000000443","name":"GislΓΆvs strandmark","municipality":"Trelleborg","lat":55.35236,"lon":13.24571},{"id":"SE0930884000001427","name":"Gissen","municipality":"Vimmerby","lat":57.5735,"lon":15.80354},{"id":"SE0910682000002032","name":"Gisshultsbadet","municipality":"NΓ€ssjΓΆ","lat":57.62155,"lon":14.78835},{"id":"SE0712260000003022","name":"GissjΓΆn, FrΓ€nsta","municipality":"Γ
nge","lat":62.5063,"lon":16.22812},{"id":"SE0441286000000353","name":"Gjuteriets strand","municipality":"Ystad","lat":55.42456,"lon":13.79867},{"id":"SE0611784000003303","name":"Glafsfjorden, Bergsviken","municipality":"Arvika","lat":59.55353,"lon":12.59127},{"id":"SE0611784000003871","name":"Glafsfjorden, Hungvik","municipality":"Arvika","lat":59.57765,"lon":12.69699},{"id":"SE0611784000003284","name":"Glafsfjorden, Ingestrand","municipality":"Arvika","lat":59.62419,"lon":12.59692},{"id":"SE0611784000003285","name":"Glafsfjorden, KlΓ€ssbol","municipality":"Arvika","lat":59.54198,"lon":12.74627},{"id":"SE0611784000003286","name":"Glafsfjorden, KΓΆrselviken","municipality":"Arvika","lat":59.6222,"lon":12.55278},{"id":"SE0230581000001730","name":"Glan, SkarsΓ€tter","municipality":"NorrkΓΆping","lat":58.64937,"lon":16.01961},{"id":"SE0110182000001222","name":"Glasbruksbadet","municipality":"Nacka","lat":59.32582,"lon":18.23297},{"id":"SE0622021000002827","name":"GlatjΓ€rns badplats","municipality":"Vansbro","lat":60.48938,"lon":14.3116},{"id":"SE0241885000002507","name":"Glien, LΓ₯nga Sand","municipality":"Lindesberg","lat":59.78974,"lon":15.3651},{"id":"SE0441278000000144","name":"Glimminge strand","municipality":"BΓ₯stad","lat":56.3748,"lon":12.6919},{"id":"SE0230513000001204","name":"Glimmingen","municipality":"Kinda","lat":57.94141,"lon":15.57052},{"id":"SE0622031000005258","name":"GlistjΓ€rn","municipality":"RΓ€ttvik","lat":60.9335,"lon":15.0987},{"id":"SE0A11382000000543","name":"Glommen","municipality":"Falkenberg","lat":56.9355,"lon":12.3571},{"id":"SE0220480000002217","name":"Glottran, Γ
nstabadet","municipality":"NykΓΆping","lat":58.9011,"lon":16.9772},{"id":"SE0A11381000000189","name":"GlΓ€nninge sjΓΆ","municipality":"Laholm","lat":56.5001,"lon":13.0524},{"id":"SE0910662000001102","name":"GlΓ€ntan","municipality":"Gislaved","lat":57.26771,"lon":13.65205},{"id":"SE0822581000002763","name":"GlΓ€ntan, BondΓΆfjΓ€rden","municipality":"PiteΓ₯","lat":65.24373,"lon":21.58244},{"id":"SE0722361000004142","name":"GlΓΆtesjΓΆn","municipality":"HΓ€rjedalen","lat":62.09743,"lon":13.50684},{"id":"SE0930860000000990","name":"GnΓΆtteln Kristinebergsbadet","municipality":"Hultsfred","lat":57.51242,"lon":15.88446},{"id":"SE0230583000001860","name":"GodegΓ₯rd, HΓΆkabadet","municipality":"Motala","lat":58.75106,"lon":15.13838},{"id":"SE0A11384000000454","name":"GottskΓ€r - Utholmen","municipality":"Kungsbacka","lat":57.38931,"lon":12.02766},{"id":"SE0A21484000000574","name":"Govik","municipality":"Lysekil","lat":58.3612,"lon":11.4483},{"id":"SE0712284000003071","name":"Grantobadet, GideΓ₯","municipality":"ΓrnskΓΆldsvik","lat":63.50321,"lon":18.9124},{"id":"SE0812425000002819","name":"GranΓ₯sens badplats","municipality":"Dorotea","lat":64.01618,"lon":16.78856},{"id":"SE0812404000003681","name":"GranΓΆ HΓΆgkammen","municipality":"Vindeln","lat":64.24739,"lon":19.30433},{"id":"SE0A21488000001013","name":"GravlΓ₯ngen","municipality":"TrollhΓ€ttan","lat":58.1575,"lon":12.2205},{"id":"SE0910642000001975","name":"GravsjΓΆn","municipality":"MullsjΓΆ","lat":57.973,"lon":13.8903},{"id":"SE0A21435000003645","name":"Grebbestad","municipality":"Tanum","lat":58.6836,"lon":11.25807},{"id":"SE0110120000001666","name":"Grills","municipality":"VΓ€rmdΓΆ","lat":59.3122,"lon":18.4577},{"id":"SE0110192000002133","name":"GrindsjΓΆn","municipality":"NynΓ€shamn","lat":59.08349,"lon":17.8887},{"id":"SE0220486000001905","name":"Gripsholmsviken, Mariefreds strandbad","municipality":"StrΓ€ngnΓ€s","lat":59.26126,"lon":17.25528},{"id":"SE0110188000002180","name":"Grisslehamn, Kvarnsandsbadet","municipality":"NorrtΓ€lje","lat":60.07777,"lon":18.82725},{"id":"SE0110120000001667","name":"Grisslinge havsbad","municipality":"VΓ€rmdΓΆ","lat":59.3122,"lon":18.4504},{"id":"SE0230562000000871","name":"Gronbadet","municipality":"FinspΓ₯ng","lat":58.71194,"lon":15.81898},{"id":"SE0910685000000828","name":"Grumlan, Sandlandet","municipality":"Vetlanda","lat":57.40053,"lon":15.06566},{"id":"SE0910685000000829","name":"Grumlan, ΓstanΓ₯","municipality":"Vetlanda","lat":57.40538,"lon":15.04793},{"id":"SE0611764000002881","name":"Grumsfjorden, MickelsΓΆn","municipality":"Grums","lat":59.32566,"lon":13.07684},{"id":"SE0611764000002882","name":"Grumsfjorden, Trulsen","municipality":"Grums","lat":59.3338,"lon":13.07971},{"id":"SE0712262000006340","name":"Grunna","municipality":"TimrΓ₯","lat":62.501,"lon":17.413},{"id":"SE0910684000001996","name":"Grunnen","municipality":"SΓ€vsjΓΆ","lat":57.16034,"lon":14.42869},{"id":"SE0A21435000000118","name":"Grunnevattnet","municipality":"Tanum","lat":58.6703,"lon":11.4257},{"id":"SE0632183000002904","name":"Gruvberget","municipality":"BollnΓ€s","lat":61.1098,"lon":16.1555},{"id":"SE0622083000002610","name":"Grycken, BjΓΆrknΓ€bbabadet","municipality":"Hedemora","lat":60.4321,"lon":16.22936},{"id":"SE0241862000002273","name":"Grytenbadet","municipality":"Degerfors","lat":59.2394,"lon":14.3745},{"id":"SE0A21452000001878","name":"GrytteredssjΓΆn","municipality":"Tranemo","lat":57.58589,"lon":13.43843},{"id":"SE0822581000002762","name":"GrΓ₯trΓ€sk","municipality":"PiteΓ₯","lat":65.48325,"lon":19.81379},{"id":"SE0110188000002166","name":"GrΓ€ddΓΆ, BjΓΆrkΓΆ Γrn","municipality":"NorrtΓ€lje","lat":59.76837,"lon":19.01562},{"id":"SE0632121000002720","name":"GrΓ€ngen, GrΓ€ngsbo","municipality":"OvanΓ₯ker","lat":61.33149,"lon":15.9517},{"id":"SE0910680000003509","name":"GrΓ€nna strandbad","municipality":"JΓΆnkΓΆping","lat":58.0274,"lon":14.45631},{"id":"SE0632132000003030","name":"GrΓ€nnsjΓΆn, BastΓ₯sen","municipality":"Nordanstig","lat":62.0459,"lon":17.0581},{"id":"SE0230563000001314","name":"GrΓ€nnΓ€s, Valdemarsvik","municipality":"Valdemarsvik","lat":58.19358,"lon":16.61964},{"id":"SE0930884000001428","name":"GrΓ€nssjΓΆn","municipality":"Vimmerby","lat":57.60952,"lon":16.10299},{"id":"SE0930883000001616","name":"GrΓ€nsΓΆ, Bondbackens badplats","municipality":"VΓ€stervik","lat":57.76528,"lon":16.68971},{"id":"SE0930883000001617","name":"GrΓ€nsΓΆ, Kanalen","municipality":"VΓ€stervik","lat":57.76984,"lon":16.67442},{"id":"SE0930883000001618","name":"GrΓ€nsΓΆ, Sandvikens badplats","municipality":"VΓ€stervik","lat":57.73749,"lon":16.70752},{"id":"SE0930883000001619","name":"GrΓ€nsΓΆ, Slottet","municipality":"VΓ€stervik","lat":57.7604,"lon":16.6812},{"id":"SE0632184000004346","name":"GrΓ€smaln, Hudiksvall","municipality":"Hudiksvall","lat":61.7195,"lon":17.1615},{"id":"SE0611766000002375","name":"GrΓ€ssjΓΆn, HedΓ₯s","municipality":"Sunne","lat":59.9969,"lon":12.88364},{"id":"SE0220428000001528","name":"GrΓ€vsjΓΆn","municipality":"VingΓ₯ker","lat":59.02221,"lon":15.66676},{"id":"SE0611783000002334","name":"GrΓΆcken","municipality":"Hagfors","lat":60.18654,"lon":13.33113},{"id":"SE0110139000003790","name":"GrΓΆna Udden","municipality":"Upplands-Bro","lat":59.47425,"lon":17.75099},{"id":"SE0241885000002499","name":"GrΓΆnbobadet","municipality":"Lindesberg","lat":59.60932,"lon":15.42167},{"id":"SE0A21435000006039","name":"GrΓΆnemad","municipality":"Tanum","lat":58.6982,"lon":11.21847},{"id":"SE0930840000006840","name":"GrΓΆnhΓΆgens strandbad","municipality":"MΓΆrbylΓ₯nga","lat":56.2666,"lon":16.3949},{"id":"SE0A11380000003938","name":"GrΓΆningen","municipality":"Halmstad","lat":56.6499,"lon":12.8743},{"id":"SE0930821000001934","name":"GrΓΆnskΓ₯rabadet","municipality":"HΓΆgsby","lat":57.07647,"lon":15.73267},{"id":"SE0110187000004296","name":"GrΓΆnviken","municipality":"Vaxholm","lat":59.39997,"lon":18.41631},{"id":"SE0712280000003739","name":"GrΓΆtsjΓΆns badplats","municipality":"HΓ€rnΓΆsand","lat":62.54294,"lon":17.78335},{"id":"SE0A21407000003872","name":"GrΓΆtΓΆ, KnΓΆten","municipality":"ΓckerΓΆ","lat":57.6988,"lon":11.67883},{"id":"SE0622039000004914","name":"GrΓΆvelsjΓΆn, nedre fallet","municipality":"Γlvdalen","lat":62.04261,"lon":12.33499},{"id":"SE0622039000004896","name":"GrΓΆvelsjΓΆn, sΓΆdra Γ€nden","municipality":"Γlvdalen","lat":62.10783,"lon":12.29825},{"id":"SE0930883000001620","name":"Gudingebadet","municipality":"VΓ€stervik","lat":57.89539,"lon":16.54232},{"id":"SE0A11380000003936","name":"Gullbranna","municipality":"Halmstad","lat":56.5855,"lon":12.9372},{"id":"SE0A21484000000560","name":"Gullmarsbaden","municipality":"Lysekil","lat":58.28005,"lon":11.4712},{"id":"SE0812482000004313","name":"Gullsands havsbad","municipality":"SkellefteΓ₯","lat":64.821,"lon":21.12363},{"id":"SE0712284000003038","name":"Gullviks havsbad","municipality":"ΓrnskΓΆldsvik","lat":63.2147,"lon":18.8276},{"id":"SE0812421000003847","name":"Gunnarns badplats","municipality":"Storuman","lat":65.01934,"lon":17.69625},{"id":"SE0930882000001956","name":"GunnarsΓΆ Grytan","municipality":"Oskarshamn","lat":57.2513,"lon":16.4958},{"id":"SE0930882000001948","name":"GunnarsΓΆ Hopptorn","municipality":"Oskarshamn","lat":57.2527,"lon":16.4908},{"id":"SE0930883000001639","name":"Gunnebo badplats","municipality":"VΓ€stervik","lat":57.7166,"lon":16.5687},{"id":"SE0A21419000000654","name":"Gunneby badplats","municipality":"TjΓΆrn","lat":58.00643,"lon":11.69371},{"id":"SE0611784000003281","name":"Gunnern GravΓ₯s","municipality":"Arvika","lat":59.79723,"lon":12.56515},{"id":"SE0611784000003287","name":"Gunnern Γ
rbotten","municipality":"Arvika","lat":59.7904,"lon":12.6166},{"id":"SE0110188000005094","name":"GunnsjΓΆn","municipality":"NorrtΓ€lje","lat":59.64493,"lon":18.61204},{"id":"SE0241885000004444","name":"Gusselby Annorlunda","municipality":"Lindesberg","lat":59.66088,"lon":15.19404},{"id":"SE0712280000003735","name":"GussjΓΆn, Starreds badplats","municipality":"HΓ€rnΓΆsand","lat":62.7071,"lon":17.5776},{"id":"SE0441291000000329","name":"GyllebosjΓΆn","municipality":"Simrishamn","lat":55.60079,"lon":14.20153},{"id":"SE0A11380000000382","name":"GyltigesjΓΆn","municipality":"Halmstad","lat":56.7652,"lon":13.1804},{"id":"SE0822580000003236","name":"Gyltzaudden","municipality":"LuleΓ₯","lat":65.58706,"lon":22.12375},{"id":"SE0110136000002142","name":"GΓ₯lΓΆ, SkΓ€lΓ₯ker","municipality":"Haninge","lat":59.09825,"lon":18.32248},{"id":"SE0A11384000000459","name":"GΓ₯rda brygga - Hamn","municipality":"Kungsbacka","lat":57.36428,"lon":12.08271},{"id":"SE0A11384000004754","name":"GΓ₯rda Brygga - Stora Vik","municipality":"Kungsbacka","lat":57.362,"lon":12.0807},{"id":"SE0632132000003037","name":"GΓ₯rdsjΓΆn, Gnarp","municipality":"Nordanstig","lat":62.1289,"lon":17.2392},{"id":"SE0210319000000777","name":"GΓ₯rdskΓ€r, Stigenbergsbadet","municipality":"Γlvkarleby","lat":60.58743,"lon":17.59039},{"id":"SE0A21430000000178","name":"GΓ₯rvik","municipality":"Munkedal","lat":58.4064,"lon":11.6166},{"id":"SE0822581000002757","name":"GΓ₯ssjΓΆn, Hemmingsmark","municipality":"PiteΓ₯","lat":65.2346,"lon":21.28705},{"id":"SE0622039000004513","name":"GΓ₯svarvs badplats","municipality":"Γlvdalen","lat":61.18065,"lon":14.0871},{"id":"SE0251980000004455","name":"GΓ€ddeholm","municipality":"VΓ€sterΓ₯s","lat":59.54834,"lon":16.66115},{"id":"SE0930861000004635","name":"GΓ€ddenΓ€s (gamla stugbyn) Timmernabben","municipality":"MΓΆnsterΓ₯s","lat":56.95668,"lon":16.4588},{"id":"SE0230562000000875","name":"GΓ€ddviken, Igelfors","municipality":"FinspΓ₯ng","lat":58.83581,"lon":15.7385},{"id":"SE0712282000003445","name":"GΓ€llstasjΓΆn","municipality":"Kramfors","lat":63.0646,"lon":18.3345},{"id":"SE0622085000003659","name":"GΓ€nsen","municipality":"Ludvika","lat":60.27439,"lon":14.72987},{"id":"SE0251962000002554","name":"GΓ€sen, Flikenbadet","municipality":"Norberg","lat":60.11671,"lon":15.90764},{"id":"SE0411081000000152","name":"GΓΆkalv","municipality":"Ronneby","lat":56.14306,"lon":15.28677},{"id":"SE0712262000002735","name":"GΓΆkbΓΆlesjΓΆn","municipality":"TimrΓ₯","lat":62.53269,"lon":17.57023},{"id":"SE0220486000001912","name":"GΓΆksjΓΆn, Sandviksbadet","municipality":"StrΓ€ngnΓ€s","lat":59.22729,"lon":17.09636},{"id":"SE0110126000002127","name":"GΓΆmmarens badplats","municipality":"Huddinge","lat":59.2518,"lon":17.9251},{"id":"SE0930821000001935","name":"GΓΆsjΓΆbadet","municipality":"HΓΆgsby","lat":57.23659,"lon":16.02712},{"id":"SE0910617000000930","name":"GΓΆtarpssjΓΆn","municipality":"GnosjΓΆ","lat":57.3976,"lon":13.76588},{"id":"SE0930882000001955","name":"GΓΆtemaren","municipality":"Oskarshamn","lat":57.4718,"lon":16.5853},{"id":"SE0441262000000325","name":"Habo Ljungs camping","municipality":"Lomma","lat":55.68879,"lon":13.05519},{"id":"SE0812480000004523","name":"Haddingen badplats, TavelsjΓΆ","municipality":"UmeΓ₯","lat":63.98064,"lon":20.11487},{"id":"SE0A21485000000597","name":"Hafstens camping","municipality":"Uddevalla","lat":58.31349,"lon":11.72212},{"id":"SE0930840000000013","name":"Haga Park","municipality":"MΓΆrbylΓ₯nga","lat":56.5798,"lon":16.40486},{"id":"SE0241882000002518","name":"Hagabadet, Alsen","municipality":"Askersund","lat":58.87992,"lon":14.9209},{"id":"SE0A21491000001020","name":"HaganΓ€sbadet, Yttre Γ
sunden","municipality":"Ulricehamn","lat":57.68566,"lon":13.30942},{"id":"SE0622061000002658","name":"Hagge, Haggen","municipality":"Smedjebacken","lat":60.1179,"lon":15.2624},{"id":"SE0622061000002664","name":"Hagudden, Norra Barken","municipality":"Smedjebacken","lat":60.0744,"lon":15.5609},{"id":"SE0A11380000002976","name":"HagΓΆns campingplats","municipality":"Halmstad","lat":56.63142,"lon":12.89837},{"id":"SE0411060000000276","name":"Halen","municipality":"OlofstrΓΆm","lat":56.26954,"lon":14.50755},{"id":"SE0A21490000005496","name":"Halla badplats, FrisjΓΆn","municipality":"BorΓ₯s","lat":57.5907,"lon":12.9865},{"id":"SE0411080000000212","name":"Hallarum","municipality":"Karlskrona","lat":56.16498,"lon":15.82641},{"id":"SE0220480000002081","name":"HallbosjΓΆn, NΓ€senbadet","municipality":"NykΓΆping","lat":58.8528,"lon":16.6959},{"id":"SE0930883000001621","name":"Hallmare havsbad","municipality":"VΓ€stervik","lat":57.87328,"lon":16.74551},{"id":"SE0230513000003557","name":"HallΓ₯ngen","municipality":"Kinda","lat":57.903,"lon":16.01},{"id":"SE0622084000002479","name":"HalsjΓΆn","municipality":"Avesta","lat":60.21941,"lon":16.32289},{"id":"SE0A21435000000124","name":"Hamburgsund","municipality":"Tanum","lat":58.568,"lon":11.2668},{"id":"SE0930883000001622","name":"Hammarsbadet","municipality":"VΓ€stervik","lat":57.88361,"lon":16.41529},{"id":"SE0210380000004254","name":"Hammarskogsbadet","municipality":"Uppsala","lat":59.76373,"lon":17.57601},{"id":"SE0611784000003288","name":"HammartjΓ€rn","municipality":"Arvika","lat":59.43476,"lon":12.73216},{"id":"SE0A21480000006680","name":"Hamnbadet","municipality":"GΓΆteborg","lat":57.7163,"lon":11.9574},{"id":"SE0441262000000321","name":"Hamnhusen, T-bryggan","municipality":"Lomma","lat":55.6725,"lon":13.0595},{"id":"SE0230584000007022","name":"Hamnparken","municipality":"Vadstena","lat":58.4473,"lon":14.8817},{"id":"SE0110192000002132","name":"Hamnviken","municipality":"NynΓ€shamn","lat":58.88891,"lon":17.92643},{"id":"SE0822581000002761","name":"Hamnviken, Γjebyn","municipality":"PiteΓ₯","lat":65.35096,"lon":21.35228},{"id":"SE0241880000002638","name":"Hampetorp, HjΓ€lmaren","municipality":"Γrebro","lat":59.14492,"lon":15.66413},{"id":"SE0712262000002727","name":"HamstasjΓΆn","municipality":"TimrΓ₯","lat":62.4665,"lon":17.2663},{"id":"SE0441233000004403","name":"Handikappbadet","municipality":"Vellinge","lat":55.39733,"lon":12.94428},{"id":"SE0910682000002010","name":"HandskerydssjΓΆn,AdelaUdde","municipality":"NΓ€ssjΓΆ","lat":57.64265,"lon":14.70115},{"id":"SE0A11384000000457","name":"Hanhals holme","municipality":"Kungsbacka","lat":57.4477,"lon":12.0682},{"id":"SE0920767000000692","name":"HannabadssjΓΆn","municipality":"Markaryd","lat":56.44029,"lon":13.5692},{"id":"SE0822583000003782","name":"Haparanda strand","municipality":"Haparanda","lat":65.8351,"lon":24.1429},{"id":"SE0722380000004491","name":"Hara SommarcafΓ©","municipality":"Γstersund","lat":63.0409,"lon":14.44942},{"id":"SE0822582000003216","name":"Harads","municipality":"Boden","lat":66.0908,"lon":20.93041},{"id":"SE0241882000002514","name":"Hargebaden, VΓ€ttern","municipality":"Askersund","lat":58.78237,"lon":14.96661},{"id":"SE0210382000001726","name":"Hargshamnsbadet","municipality":"Γsthammar","lat":60.17218,"lon":18.46639},{"id":"SE0230586000001276","name":"HargsjΓΆn","municipality":"MjΓΆlby","lat":58.26632,"lon":15.2472},{"id":"SE0632180000002895","name":"HarnΓ€s","municipality":"GΓ€vle","lat":60.64594,"lon":17.34737},{"id":"SE0822581000002740","name":"HarrtrΓ€sket, NorrfjΓ€rden","municipality":"PiteΓ₯","lat":65.44175,"lon":21.5229},{"id":"SE0712281000003472","name":"Hartungviken AlnΓΆ","municipality":"Sundsvall","lat":62.3911,"lon":17.519},{"id":"SE0632132000003837","name":"HasselsjΓΆn, Hasselabadet","municipality":"Nordanstig","lat":62.0975,"lon":16.7316},{"id":"SE0930883000007360","name":"HasselΓΆ sand","municipality":"VΓ€stervik","lat":57.8307,"lon":16.7324},{"id":"SE0712284000003073","name":"HattsjΓΆbadet, FlΓ€rke GideΓ₯","municipality":"ΓrnskΓΆldsvik","lat":63.5925,"lon":18.97658},{"id":"SE0A11380000000368","name":"Haverdal","municipality":"Halmstad","lat":56.71862,"lon":12.66728},{"id":"SE0110136000002140","name":"Havsbadet Schweizerbadet, DalarΓΆ","municipality":"Haninge","lat":59.13904,"lon":18.39451},{"id":"SE0930882000001957","name":"HavslΓ€tt","municipality":"Oskarshamn","lat":57.2786,"lon":16.4767},{"id":"SE0A21435000003646","name":"Havstenssund","municipality":"Tanum","lat":58.75169,"lon":11.17713},{"id":"SE0622085000002458","name":"HavstjΓ€rnen","municipality":"Ludvika","lat":60.29949,"lon":14.97502},{"id":"SE0A21415000000113","name":"Hawaii, StenungsΓΆn","municipality":"Stenungsund","lat":58.0581,"lon":11.7977},{"id":"SE0722361000004183","name":"Hede, Ljusnan","municipality":"HΓ€rjedalen","lat":62.42166,"lon":13.52304},{"id":"SE0722361000004310","name":"Hedevikens camping, Hedeviken","municipality":"HΓ€rjedalen","lat":62.40846,"lon":13.66957},{"id":"SE0622080000004239","name":"HedkarlssjΓΆn Sundborn","municipality":"Falun","lat":60.65462,"lon":15.8435},{"id":"SE0251982000002540","name":"HedtjΓ€rn","municipality":"Fagersta","lat":59.9664,"lon":15.74963},{"id":"SE0632181000002984","name":"HedΓ₯sbadet","municipality":"Sandviken","lat":60.58051,"lon":16.7572},{"id":"SE0920780000001571","name":"HelgasjΓΆn, Araby","municipality":"VΓ€xjΓΆ","lat":56.9073,"lon":14.7653},{"id":"SE0920780000004504","name":"HelgasjΓΆn, Dalbostrand","municipality":"VΓ€xjΓΆ","lat":56.91835,"lon":14.82878},{"id":"SE0920780000001573","name":"HelgasjΓΆn, Evedal","municipality":"VΓ€xjΓΆ","lat":56.921,"lon":14.8235},{"id":"SE0920780000003302","name":"HelgasjΓΆn, Gamla Evedal","municipality":"VΓ€xjΓΆ","lat":56.9236,"lon":14.81979},{"id":"SE0910683000001508","name":"HelgasjΓΆn, HjortsjΓΆbadet","municipality":"VΓ€rnamo","lat":56.96902,"lon":14.33062},{"id":"SE0920780000001565","name":"HelgasjΓΆn, Julebadet","municipality":"VΓ€xjΓΆ","lat":57.00307,"lon":14.77093},{"id":"SE0920780000001572","name":"HelgasjΓΆn, Pene badplats","municipality":"VΓ€xjΓΆ","lat":56.9251,"lon":14.7779},{"id":"SE0920780000001568","name":"HelgasjΓΆn, Ryttartorpet (Araby 3)","municipality":"VΓ€xjΓΆ","lat":56.91413,"lon":14.76683},{"id":"SE0920780000001562","name":"HelgasjΓΆn, Sandsbro","municipality":"VΓ€xjΓΆ","lat":56.9155,"lon":14.8538},{"id":"SE0920780000001566","name":"HelgasjΓΆn, SkΓ€lsnΓ€s","municipality":"VΓ€xjΓΆ","lat":56.9716,"lon":14.742},{"id":"SE0920780000001569","name":"HelgasjΓΆn, Stockekvarn","municipality":"VΓ€xjΓΆ","lat":56.99253,"lon":14.8576},{"id":"SE0920780000001564","name":"HelgasjΓΆn, Svanebro","municipality":"VΓ€xjΓΆ","lat":57.0054,"lon":14.685},{"id":"SE0920780000001567","name":"HelgasjΓΆn, Γ
by","municipality":"VΓ€xjΓΆ","lat":57.0229,"lon":14.7797},{"id":"SE0920780000001570","name":"HelgasjΓΆn, Γjaby","municipality":"VΓ€xjΓΆ","lat":56.9004,"lon":14.7404},{"id":"SE0622029000002438","name":"Helgbo","municipality":"Leksand","lat":60.6581,"lon":15.208},{"id":"SE0920765000001657","name":"Helge Γ₯, Delary badplats","municipality":"Γlmhult","lat":56.55368,"lon":13.96441},{"id":"SE0930883000006220","name":"HelgenΓ€s badplats","municipality":"VΓ€stervik","lat":57.9969,"lon":16.4986},{"id":"SE0441256000000290","name":"HelgeΓ₯, Njura","municipality":"Γstra GΓΆinge","lat":56.29057,"lon":14.05639},{"id":"SE0441256000000294","name":"HelgeΓ₯, ΓstanΓ₯","municipality":"Γstra GΓΆinge","lat":56.30676,"lon":14.02059},{"id":"SE0920780000001557","name":"Helige Γ₯, Gemla","municipality":"VΓ€xjΓΆ","lat":56.8727,"lon":14.6543},{"id":"SE0920780000004385","name":"Helige Γ₯, Mulevad","municipality":"VΓ€xjΓΆ","lat":56.8574,"lon":14.6268},{"id":"SE0812421000004321","name":"HembygdsomrΓ₯det Storuman","municipality":"Storuman","lat":65.11709,"lon":17.09072},{"id":"SE0712280000003730","name":"HemsΓΆn, SΓ₯gsands badplats","municipality":"HΓ€rnΓΆsand","lat":62.71049,"lon":18.11319},{"id":"SE0622026000002627","name":"HemtjΓ€rn","municipality":"Gagnef","lat":60.5448,"lon":15.2469},{"id":"SE0822581000002765","name":"HemtrΓ€sket, BlΓ₯smark","municipality":"PiteΓ₯","lat":65.26154,"lon":21.28836},{"id":"SE0910662000001115","name":"HensnΓ€sbadet","municipality":"Gislaved","lat":57.07735,"lon":13.13775},{"id":"SE0110188000002161","name":"HerrΓ€ng, Uddham","municipality":"NorrtΓ€lje","lat":60.13815,"lon":18.65631},{"id":"SE0110180000006740","name":"HerrΓ€ngens brygga","municipality":"Stockholm","lat":59.2724,"lon":17.9592},{"id":"SE0722361000004140","name":"HerrΓΆ","municipality":"HΓ€rjedalen","lat":62.01755,"lon":14.20722},{"id":"SE0940980000000248","name":"Herta","municipality":"Gotland","lat":57.2191,"lon":18.5975},{"id":"SE0930860000000999","name":"HesjΓΆn, MΓ₯lilla","municipality":"Hultsfred","lat":57.39616,"lon":15.80189},{"id":"SE0910617000000932","name":"High Chaparral","municipality":"GnosjΓΆ","lat":57.25762,"lon":13.8308},{"id":"SE0A21465000001068","name":"Hillared, HΓΆsjΓΆn","municipality":"Svenljunga","lat":57.61739,"lon":13.17878},{"id":"SE0632180000002886","name":"Hillevik","municipality":"GΓ€vle","lat":60.80985,"lon":17.24113},{"id":"SE0441290000000680","name":"HillevΓ€gens badplats","municipality":"Kristianstad","lat":55.8412,"lon":14.2274},{"id":"SE0251981000003256","name":"Hillingen","municipality":"Sala","lat":60.01655,"lon":16.66937},{"id":"SE0622085000002445","name":"HillΓ€ngsbadet","municipality":"Ludvika","lat":60.1527,"lon":15.21999},{"id":"SE0110127000001414","name":"HimmerfjΓ€rden, Sandviken","municipality":"Botkyrka","lat":59.05675,"lon":17.69968},{"id":"SE0910683000001500","name":"Hindsen, NΓ€sudden Γ","municipality":"VΓ€rnamo","lat":57.19637,"lon":14.13937},{"id":"SE0920767000000689","name":"HinnerydssjΓΆn","municipality":"Markaryd","lat":56.61347,"lon":13.60849},{"id":"SE0812480000004422","name":"HissjΓΆ badplats","municipality":"UmeΓ₯","lat":63.93792,"lon":20.14478},{"id":"SE0622029000002440","name":"HisstjΓ€rn","municipality":"Leksand","lat":60.6059,"lon":14.9658},{"id":"SE0441283000000318","name":"Hittarp","municipality":"Helsingborg","lat":56.09633,"lon":12.63519},{"id":"SE0930883000001623","name":"Hjorteds badplats (Hjorten)","municipality":"VΓ€stervik","lat":57.615,"lon":16.3045},{"id":"SE0920764000001590","name":"Hjortsberga","municipality":"Alvesta","lat":56.9195,"lon":14.44697},{"id":"SE0910665000001259","name":"HjortsjΓΆn","municipality":"Vaggeryd","lat":57.5091,"lon":14.1314},{"id":"SE0241863000003434","name":"HjulsjΓΆ badplats","municipality":"HΓ€llefors","lat":59.77752,"lon":14.78152},{"id":"SE0812460000003080","name":"HjΓ₯ggsjΓΆns badplats","municipality":"VΓ€nnΓ€s","lat":63.83236,"lon":19.74994},{"id":"SE0A21441000004440","name":"HjΓ€llsnΓ€sviken","municipality":"Lerum","lat":57.85002,"lon":12.32375},{"id":"SE0241880000004026","name":"HjΓ€lmarbaden","municipality":"Γrebro","lat":59.25435,"lon":15.41168},{"id":"SE0241880000002636","name":"HjΓ€lmaren, Dimbobaden","municipality":"Γrebro","lat":59.13772,"lon":15.73985},{"id":"SE0241880000002637","name":"HjΓ€lmaren, Katrinelund","municipality":"Γrebro","lat":59.23576,"lon":15.56813},{"id":"SE0241880000002635","name":"HjΓ€lmaren, Norra VinΓΆn","municipality":"Γrebro","lat":59.20479,"lon":15.71301},{"id":"SE0241880000002639","name":"HjΓ€lmaren, Γ
stabadet","municipality":"Γrebro","lat":59.31027,"lon":15.51459},{"id":"SE0441257000000434","name":"HjΓ€lmsjΓΆn,Mammas vik","municipality":"Γrkelljunga","lat":56.27974,"lon":13.30097},{"id":"SE0910685000000830","name":"HjΓ€lmΓ₯krasjΓΆn, FrΓΆderyd","municipality":"Vetlanda","lat":57.31537,"lon":14.816},{"id":"SE0910686000001581","name":"HjΓ€lten","municipality":"EksjΓΆ","lat":57.63237,"lon":15.34486},{"id":"SE0A11382000000612","name":"HjΓ€rtaredssjΓΆn","municipality":"Falkenberg","lat":57.1684,"lon":12.6871},{"id":"SE0441273000000003","name":"HjΓ€rtasjΓΆbadet","municipality":"Osby","lat":56.4002,"lon":14.282},{"id":"SE0A11381000000191","name":"HjΓΆrneredssjΓΆn","municipality":"Laholm","lat":56.5082,"lon":13.1947},{"id":"SE0722361000003976","name":"Hoan, Ytterhogdal","municipality":"HΓ€rjedalen","lat":62.17272,"lon":14.94326},{"id":"SE0A21452000004193","name":"HofsnΓ€s","municipality":"Tranemo","lat":57.63578,"lon":13.29182},{"id":"SE0910685000000831","name":"HolmasjΓΆn, Holsby","municipality":"Vetlanda","lat":57.46141,"lon":15.23618},{"id":"SE0611765000002315","name":"HolmedalssjΓΆn","municipality":"Γ
rjΓ€ng","lat":59.46549,"lon":11.9597},{"id":"SE0622084000002494","name":"Holmen","municipality":"Avesta","lat":60.15083,"lon":16.16159},{"id":"SE0940980000000251","name":"HolmhΓ€llar","municipality":"Gotland","lat":56.9289,"lon":18.2733},{"id":"SE0712260000003018","name":"HolmsjΓΆn, Storsand","municipality":"Γ
nge","lat":62.39336,"lon":15.42415},{"id":"SE0632182000002704","name":"Holmsveden, HΓ€rnebobadet","municipality":"SΓΆderhamn","lat":61.11999,"lon":16.72268},{"id":"SE0A21465000001060","name":"Holsljunga, HolsjΓΆn","municipality":"Svenljunga","lat":57.41746,"lon":12.9561},{"id":"SE0220480000004481","name":"Hopen, Husby Oppunda","municipality":"NykΓΆping","lat":58.9147,"lon":16.7765},{"id":"SE0930861000004185","name":"Hopptornet, OknΓΆ","municipality":"MΓΆnsterΓ₯s","lat":57.0158,"lon":16.49579},{"id":"SE0A21401000000060","name":"HornasjΓΆn","municipality":"HΓ€rryda","lat":57.70479,"lon":12.27719},{"id":"SE0712281000003478","name":"HornsjΓΆn Njurunda","municipality":"Sundsvall","lat":62.181,"lon":17.2102},{"id":"SE0930821000001936","name":"HornsΓΆbadet","municipality":"HΓΆgsby","lat":57.00131,"lon":16.21168},{"id":"SE0A21463000000948","name":"Horred, L:a HorredsjΓΆn","municipality":"Mark","lat":57.3648,"lon":12.4665},{"id":"SE0A11382000000616","name":"HorsasjΓΆn Γlvsered","municipality":"Falkenberg","lat":57.2384,"lon":12.8632},{"id":"SE0A21481000000396","name":"Horsikas badplats","municipality":"MΓΆlndal","lat":57.63896,"lon":12.05384},{"id":"SE0220482000000744","name":"HosjΓΆbadet","municipality":"Flen","lat":59.1408,"lon":16.73127},{"id":"SE0210380000004356","name":"HosjΓΆbadet","municipality":"Uppsala","lat":59.91338,"lon":18.30508},{"id":"SE0622031000002952","name":"HosjΓΆn, Backa","municipality":"RΓ€ttvik","lat":60.92391,"lon":15.08602},{"id":"SE0A21480000000533","name":"HovΓ₯sbadet","municipality":"GΓΆteborg","lat":57.61774,"lon":11.92219},{"id":"SE0210331000002566","name":"Huddunge, MyrsjΓΆbadet","municipality":"Heby","lat":60.03201,"lon":16.9909},{"id":"SE0611730000003518","name":"Hugn, KΓΆla kyrka","municipality":"Eda","lat":59.8007,"lon":12.24852},{"id":"SE0611730000003577","name":"Hugn, Sandviken","municipality":"Eda","lat":59.8186,"lon":12.2615},{"id":"SE0920764000001585","name":"Hulevik","municipality":"Alvesta","lat":56.62496,"lon":14.61757},{"id":"SE0930860000000994","name":"Hulingen, Hultsfred","municipality":"Hultsfred","lat":57.4904,"lon":15.8619},{"id":"SE0A21440000001827","name":"HultasjΓΆn","municipality":"Ale","lat":57.93669,"lon":12.24275},{"id":"SE0441257000000431","name":"HultasjΓΆns badplats","municipality":"Γrkelljunga","lat":56.33079,"lon":13.38334},{"id":"SE0930881000000811","name":"HultebrΓ€an","municipality":"Nybro","lat":56.64545,"lon":15.77264},{"id":"SE0230580000001816","name":"HultsjΓΆn","municipality":"LinkΓΆping","lat":58.57411,"lon":15.51013},{"id":"SE0A21402000000093","name":"Humlebadet, Bryggan","municipality":"Partille","lat":57.7542,"lon":12.1891},{"id":"SE0441293000000429","name":"HumlesjΓΆn","municipality":"HΓ€ssleholm","lat":56.2519,"lon":13.5812},{"id":"SE0411082000000165","name":"HundsjΓΆn","municipality":"Karlshamn","lat":56.2558,"lon":14.7854},{"id":"SE0230562000000879","name":"Hunn Rejmyre badplats","municipality":"FinspΓ₯ng","lat":58.8326,"lon":15.95148},{"id":"SE0910680000003501","name":"Hunnerydsbadet","municipality":"JΓΆnkΓΆping","lat":57.8597,"lon":14.3084},{"id":"SE0910686000001603","name":"HunsnΓ€sen, PrΓ€stΓ€ngsbadet","municipality":"EksjΓΆ","lat":57.6692,"lon":14.9891},{"id":"SE0241882000002524","name":"Husabergsudde, Alsen","municipality":"Askersund","lat":58.86864,"lon":14.91252},{"id":"SE0230582000001082","name":"Husbyvik","municipality":"SΓΆderkΓΆping","lat":58.46649,"lon":16.46829},{"id":"SE0622023000003660","name":"HustjΓ€rn","municipality":"Malung","lat":60.96437,"lon":13.34385},{"id":"SE0110184000006560","name":"Huvudsta strandbad","municipality":"Solna","lat":59.345,"lon":17.9903},{"id":"SE0A21441000004073","name":"Hyltorna, SΓ€velΓ₯ngen","municipality":"Lerum","lat":57.81294,"lon":12.37014},{"id":"SE0A21407000003880","name":"Hyppeln, SandtΓ₯ngen","municipality":"ΓckerΓΆ","lat":57.76541,"lon":11.61589},{"id":"SE0A21463000000956","name":"Hyssna Lilla HΓ₯lsjΓΆn","municipality":"Mark","lat":57.56331,"lon":12.5815},{"id":"SE0632132000003036","name":"HΓ₯ckstabadet, JΓ€ttendal","municipality":"Nordanstig","lat":61.9783,"lon":17.2836},{"id":"SE0A21465000001076","name":"HΓ₯cksvik, SkΓ€remosjΓΆn","municipality":"Svenljunga","lat":57.33777,"lon":13.18399},{"id":"SE0A21486000000102","name":"HΓ₯lkedalen","municipality":"StrΓΆmstad","lat":58.92847,"lon":11.18157},{"id":"SE0632181000002978","name":"HΓ₯lldammen","municipality":"Sandviken","lat":60.73112,"lon":16.63541},{"id":"SE0712283000007120","name":"HΓ₯llstaudden Junsele","municipality":"SollefteΓ₯","lat":63.7496,"lon":16.9062},{"id":"SE0A21440000001824","name":"HΓ₯lsjΓΆn","municipality":"Ale","lat":58.03478,"lon":12.27191},{"id":"SE0910685000000832","name":"HΓ₯rdasjΓΆn, Landsbro","municipality":"Vetlanda","lat":57.38488,"lon":14.88821},{"id":"SE0722380000003083","name":"HΓ₯rkmobadet, Lits camping","municipality":"Γstersund","lat":63.3205,"lon":14.8665},{"id":"SE0632132000003615","name":"HΓ₯rte, Koharet","municipality":"Nordanstig","lat":61.9923,"lon":17.3925},{"id":"SE0110160000001441","name":"HΓ€gernΓ€sbadet","municipality":"TΓ€by","lat":59.44174,"lon":18.13019},{"id":"SE0712280000003740","name":"HΓ€ggdΓ₯nger, Byvikens badplats","municipality":"HΓ€rnΓΆsand","lat":62.55277,"lon":17.86664},{"id":"SE0411080000000214","name":"HΓ€gnviken, TjurkΓΆ","municipality":"Karlskrona","lat":56.12215,"lon":15.61423},{"id":"SE0930862000000734","name":"HΓ€ljanΓ€s badplats","municipality":"Emmaboda","lat":56.54508,"lon":15.39394},{"id":"SE0251984000002586","name":"HΓ€llarna vΓ€stra","municipality":"Arboga","lat":59.27389,"lon":15.85514},{"id":"SE0411083000000659","name":"HΓ€lleviks strand","municipality":"SΓΆlvesborg","lat":56.012,"lon":14.7071},{"id":"SE0110139000001716","name":"HΓ€llkana","municipality":"Upplands-Bro","lat":59.52571,"lon":17.68487},{"id":"SE0251981000003267","name":"HΓ€llsjΓΆbadet","municipality":"Sala","lat":60.04325,"lon":16.55881},{"id":"SE0A21407000003881","name":"HΓ€lsΓΆ, Tjolmen","municipality":"ΓckerΓΆ","lat":57.7271,"lon":11.65645},{"id":"SE0632121000003969","name":"HΓ€mmen, Svabensverk","municipality":"OvanΓ₯ker","lat":61.04391,"lon":15.79502},{"id":"SE0910617000000933","name":"HΓ€ren (GnosjΓΆ Strand)","municipality":"GnosjΓΆ","lat":57.32151,"lon":13.72693},{"id":"SE0210381000005657","name":"HΓ€rjarΓΆ BlΓ₯hΓ€ll","municipality":"EnkΓΆping","lat":59.46159,"lon":17.40564},{"id":"SE0210381000005658","name":"HΓ€rjarΓΆ ΓppelnΓ€sgrund","municipality":"EnkΓΆping","lat":59.48858,"lon":17.42908},{"id":"SE0722361000004309","name":"HΓ€rjedalsgΓ₯rden, Γlvros","municipality":"HΓ€rjedalen","lat":62.04852,"lon":14.63804},{"id":"SE0722361000004435","name":"HΓ€rjeΓ₯sjΓΆn, HΓ€rjevik","municipality":"HΓ€rjedalen","lat":61.90296,"lon":14.19703},{"id":"SE0722361000003973","name":"HΓ€rjΓ₯sjΓΆn, KlubbnΓ€set","municipality":"HΓ€rjedalen","lat":61.87574,"lon":14.23781},{"id":"SE0A21480000000392","name":"HΓ€rlanda tjΓ€rn","municipality":"GΓΆteborg","lat":57.70844,"lon":12.05304},{"id":"SE0632161000002738","name":"HΓ€rnsandsviken, DΓ₯asen, Los","municipality":"Ljusdal","lat":61.7294,"lon":15.3118},{"id":"SE0712280000003742","name":"HΓ€rnΓΆn, Smitingens badplats","municipality":"HΓ€rnΓΆsand","lat":62.59915,"lon":18.03248},{"id":"SE0712280000003738","name":"HΓ€rnΓΆn, SΓ€lstens badplats","municipality":"HΓ€rnΓΆsand","lat":62.64492,"lon":17.97545},{"id":"SE0411081000000159","name":"HΓ€rstorpssjΓΆn","municipality":"Ronneby","lat":56.211,"lon":15.25539},{"id":"SE0110180000001834","name":"HΓ€sselby AllmΓ€nna Bad","municipality":"Stockholm","lat":59.3734,"lon":17.803},{"id":"SE0110180000004489","name":"HΓ€sselby strandbad","municipality":"Stockholm","lat":59.3638,"lon":17.821},{"id":"SE0A21427000001495","name":"HΓ€stedalen, Hunnebostrand","municipality":"SotenΓ€s","lat":58.4475,"lon":11.2948},{"id":"SE0A21480000000505","name":"HΓ€stevik","municipality":"GΓΆteborg","lat":57.71543,"lon":11.71029},{"id":"SE0241880000005921","name":"HΓ€sthagens bryggbad","municipality":"Γrebro","lat":59.26531,"lon":15.15194},{"id":"SE0110138000002107","name":"HΓ€sthagsbadet","municipality":"TyresΓΆ","lat":59.22837,"lon":18.18487},{"id":"SE0241881000003227","name":"HΓ€sthagsbrottet","municipality":"Kumla","lat":59.10849,"lon":15.23104},{"id":"SE0910682000002055","name":"HΓ€stsjΓΆn, Ekerydsbadet","municipality":"NΓ€ssjΓΆ","lat":57.73963,"lon":14.70783},{"id":"SE0611762000004428","name":"HΓ€stskoholmen","municipality":"Munkfors","lat":59.8461,"lon":13.54963},{"id":"SE0A21452000001885","name":"HΓ€stΓΆsjΓΆn","municipality":"Tranemo","lat":57.37029,"lon":13.2761},{"id":"SE0910687000000861","name":"HΓ€ttebaden","municipality":"TranΓ₯s","lat":58.0358,"lon":15.03327},{"id":"SE0441284000000489","name":"HΓΆganΓ€s, Kvickbadet","municipality":"HΓΆganΓ€s","lat":56.19332,"lon":12.55304},{"id":"SE0441284000000491","name":"HΓΆganΓ€s, Margreteberg","municipality":"HΓΆganΓ€s","lat":56.21288,"lon":12.54187},{"id":"SE0622080000002935","name":"HΓΆganΓ€s,SvΓ€rdsjΓΆ","municipality":"Falun","lat":60.76039,"lon":15.91941},{"id":"SE0632180000002883","name":"HΓΆgbacka","municipality":"GΓ€vle","lat":61.01205,"lon":16.97732},{"id":"SE0622026000002616","name":"HΓΆgbergsdammen","municipality":"Gagnef","lat":60.5064,"lon":14.9437},{"id":"SE0632181000002980","name":"HΓΆgbobadet","municipality":"Sandviken","lat":60.6698,"lon":16.82217},{"id":"SE0241864000007520","name":"HΓΆgfors","municipality":"Ljusnarsberg","lat":59.9796,"lon":14.9955},{"id":"SE0812425000003823","name":"HΓΆglands badplats","municipality":"Dorotea","lat":64.58101,"lon":15.86964},{"id":"SE0611765000002316","name":"HΓΆgsjΓΆn","municipality":"Γ
rjΓ€ng","lat":59.54559,"lon":12.02578},{"id":"SE0251984000002590","name":"HΓΆgsjΓΆn norra","municipality":"Arboga","lat":59.38788,"lon":15.73037},{"id":"SE0110180000001869","name":"HΓΆkarΓ€ngsbadet, Drevviken","municipality":"Stockholm","lat":59.2468,"lon":18.11},{"id":"SE0110140000001346","name":"HΓΆkmossenbadet","municipality":"Nykvarn","lat":59.17116,"lon":17.40518},{"id":"SE0251980000003948","name":"HΓΆkΓ₯sengropen","municipality":"VΓ€sterΓ₯s","lat":59.66329,"lon":16.60528},{"id":"SE0622083000002598","name":"HΓΆnsan","municipality":"Hedemora","lat":60.2847,"lon":15.98106},{"id":"SE0A21407000003882","name":"HΓΆnΓΆ, HΓ€sten","municipality":"ΓckerΓΆ","lat":57.68411,"lon":11.63504},{"id":"SE0A21407000003884","name":"HΓΆnΓΆ, Jungfruviken","municipality":"ΓckerΓΆ","lat":57.68731,"lon":11.66863},{"id":"SE0A21407000003883","name":"HΓΆnΓΆ, Lapposand","municipality":"ΓckerΓΆ","lat":57.69183,"lon":11.6342},{"id":"SE0251981000003255","name":"HΓΆrendesjΓΆn, Paradisbadet","municipality":"Sala","lat":59.95906,"lon":16.20965},{"id":"SE0241864000002529","name":"HΓΆrken, Norra HΓΆrken","municipality":"Ljusnarsberg","lat":60.0329,"lon":14.91929},{"id":"SE0712282000003439","name":"HΓΆrsΓ₯ngs havsbad","municipality":"Kramfors","lat":62.8266,"lon":18.17895},{"id":"SE0411083000000662","name":"HΓΆrvik","municipality":"SΓΆlvesborg","lat":56.0429,"lon":14.7608},{"id":"SE0230580000001814","name":"HΓΆvern","municipality":"LinkΓΆping","lat":58.38246,"lon":16.03326},{"id":"SE0712284000003049","name":"Idbyns havsbad","municipality":"ΓrnskΓΆldsvik","lat":63.29108,"lon":18.88155},{"id":"SE0622023000003661","name":"IdbΓ€ckstjΓ€rn","municipality":"Malung","lat":60.69528,"lon":13.67397},{"id":"SE0622039000003666","name":"Idre badplats, IdresjΓΆn","municipality":"Γlvdalen","lat":61.85536,"lon":12.73663},{"id":"SE0930834000001941","name":"IglasjΓΆn","municipality":"TorsΓ₯s","lat":56.36615,"lon":15.82628},{"id":"SE0940980000000270","name":"Ihreviken","municipality":"Gotland","lat":57.8427,"lon":18.6019},{"id":"SE0611780000002379","name":"IlandatjΓ€rn","municipality":"Karlstad","lat":59.43382,"lon":13.41719},{"id":"SE0910687000000863","name":"Illern","municipality":"TranΓ₯s","lat":58.00604,"lon":15.01193},{"id":"SE0441256000000288","name":"Immeln","municipality":"Γstra GΓΆinge","lat":56.208,"lon":14.25017},{"id":"SE0632184000002993","name":"Ingan, NΓ€sviken","municipality":"Hudiksvall","lat":61.7615,"lon":16.8714},{"id":"SE0910686000001604","name":"IngarpasjΓΆn","municipality":"EksjΓΆ","lat":57.56738,"lon":15.00646},{"id":"SE0910686000001605","name":"IngatorpssjΓΆn","municipality":"EksjΓΆ","lat":57.63797,"lon":15.40674},{"id":"SE0A21482000000090","name":"IngetorpssjΓΆn","municipality":"KungΓ€lv","lat":57.93026,"lon":11.89151},{"id":"SE0920780000001545","name":"Innaren, Brittatorp-KΓ€llarback","municipality":"VΓ€xjΓΆ","lat":57.0327,"lon":14.9372},{"id":"SE0920780000001543","name":"Innaren, Rottne","municipality":"VΓ€xjΓΆ","lat":57.0127,"lon":14.8906},{"id":"SE0230581000006660","name":"Inre hamn/ Motala strΓΆm","municipality":"NorrkΓΆping","lat":58.5948,"lon":16.2006},{"id":"SE0230581000007560","name":"Inre hamnen, plaskdammen","municipality":"NorrkΓΆping","lat":58.5951,"lon":16.2},{"id":"SE0110182000001194","name":"InsjΓΆbadet","municipality":"Nacka","lat":59.33423,"lon":18.33376},{"id":"SE0622029000002437","name":"InsjΓΆn, Γ
lbybadet","municipality":"Leksand","lat":60.6875,"lon":15.1144},{"id":"SE0930882000001958","name":"Ishult","municipality":"Oskarshamn","lat":57.4763,"lon":16.2753},{"id":"SE0930840000000017","name":"Ispe Udde, BorgmΓ€starviken","municipality":"MΓΆrbylΓ₯nga","lat":56.74537,"lon":16.51948},{"id":"SE0441290000000665","name":"IvΓΆ badplats","municipality":"Kristianstad","lat":56.1112,"lon":14.3824},{"id":"SE0441272000000296","name":"IvΓΆsjΓΆn, Klackabackens badplats","municipality":"BromΓΆlla","lat":56.15059,"lon":14.45477},{"id":"SE0441272000000297","name":"IvΓΆsjΓΆn, Korsholmen","municipality":"BromΓΆlla","lat":56.08762,"lon":14.46227},{"id":"SE0441272000000298","name":"IvΓΆsjΓΆn, Orudden","municipality":"BromΓΆlla","lat":56.13151,"lon":14.47651},{"id":"SE0441272000000301","name":"IvΓΆsjΓΆn, StrandΓ€ngen","municipality":"BromΓΆlla","lat":56.06731,"lon":14.45303},{"id":"SE0722380000005054","name":"Jamtli Brygga","municipality":"Γstersund","lat":63.1865,"lon":14.6293},{"id":"SE0230584000000722","name":"Jernevid","municipality":"Vadstena","lat":58.455,"lon":14.789},{"id":"SE0A21491000001031","name":"Jogen","municipality":"Ulricehamn","lat":57.8784,"lon":13.62863},{"id":"SE0220481000001263","name":"JogersΓΆ","municipality":"OxelΓΆsund","lat":58.66507,"lon":17.05107},{"id":"SE0110180000004020","name":"Johannelundsbadet (Minneberg), MΓ€laren","municipality":"Stockholm","lat":59.34107,"lon":17.98647},{"id":"SE0110180000001836","name":"Johannesdalsbadet (VΓ₯rberg), MΓ€laren","municipality":"Stockholm","lat":59.2757,"lon":17.8683},{"id":"SE0251980000003562","name":"Johannisberg, Badviken","municipality":"VΓ€sterΓ₯s","lat":59.57433,"lon":16.52797},{"id":"SE0441284000000500","name":"Jonstorp, Revet","municipality":"HΓΆganΓ€s","lat":56.233,"lon":12.68778},{"id":"SE0611760000002395","name":"Jordkullen","municipality":"Storfors","lat":59.57234,"lon":14.27106},{"id":"SE0441290000000682","name":"Juleboda badplats","municipality":"Kristianstad","lat":55.7657,"lon":14.1979},{"id":"SE0632181000002977","name":"Jungfruholmen Hammarby","municipality":"Sandviken","lat":60.5385,"lon":16.56238},{"id":"SE0930861000003586","name":"Jungnerholmarna, EmΓ₯n","municipality":"MΓΆnsterΓ₯s","lat":57.13227,"lon":16.25356},{"id":"SE0822521000003344","name":"Junosuando, IsojΓ€rvi","municipality":"Pajala","lat":67.41383,"lon":22.50906},{"id":"SE0930880000004431","name":"Jutnabben","municipality":"Kalmar","lat":56.6856,"lon":16.3702},{"id":"SE0930884000001430","name":"Juttern, StjΓ€rneviksbadet","municipality":"Vimmerby","lat":57.79438,"lon":15.84135},{"id":"SE0622085000002443","name":"JΓ€garnΓ€s","municipality":"Ludvika","lat":60.11805,"lon":15.19902},{"id":"SE0241880000002653","name":"JΓ€rleΓ₯n, NorsebΓ€ck","municipality":"Γrebro","lat":59.44275,"lon":15.16034},{"id":"SE0110127000001410","name":"JΓ€rnafjΓ€rden, Getryggen","municipality":"Botkyrka","lat":59.09739,"lon":17.6836},{"id":"SE0411081000000153","name":"JΓ€rnavik","municipality":"Ronneby","lat":56.17523,"lon":15.07783},{"id":"SE0230513000003860","name":"JΓ€rnlunden, Groveda","municipality":"Kinda","lat":58.1666,"lon":15.6917},{"id":"SE0230513000001200","name":"JΓ€rnlunden, Hallstad Γ€ngar","municipality":"Kinda","lat":58.15207,"lon":15.64725},{"id":"SE0230580000001763","name":"JΓ€rnlunden, Hulta","municipality":"LinkΓΆping","lat":58.19209,"lon":15.59251},{"id":"SE0930860000003836","name":"JΓ€rnsjΓΆn, JΓ€rnforsen","municipality":"Hultsfred","lat":57.4064,"lon":15.602},{"id":"SE0622085000002459","name":"JΓ€rnsta","municipality":"Ludvika","lat":60.24777,"lon":14.95827},{"id":"SE0441283000004404","name":"JΓ€rnvΓ€gsmΓ€nnens brygga","municipality":"Helsingborg","lat":56.0536,"lon":12.6827},{"id":"SE0A21494000004338","name":"JΓ€rpΓ₯s badsjΓΆ","municipality":"LidkΓΆping","lat":58.37351,"lon":12.96611},{"id":"SE0910680000004317","name":"JΓ€rsnΓ€s LillsjΓΆn","municipality":"JΓΆnkΓΆping","lat":57.77542,"lon":14.56085},{"id":"SE0812462000003697","name":"JΓ€rvsjΓΆby","municipality":"Vilhelmina","lat":64.61949,"lon":17.21781},{"id":"SE0822505000004046","name":"JΓ€rvtrΓ€sk","municipality":"Arvidsjaur","lat":65.20743,"lon":19.50025},{"id":"SE0A11383000000628","name":"KagabΓ€cksviken","municipality":"Varberg","lat":57.04667,"lon":12.28533},{"id":"SE0110114000001601","name":"Kairobadet","municipality":"Upplands-VΓ€sby","lat":59.5219,"lon":17.8445},{"id":"SE0632184000004161","name":"Kaked, Forsa","municipality":"Hudiksvall","lat":61.709,"lon":16.9563},{"id":"SE0441280000006159","name":"KalkbrottssjΓΆn, Klagshamn","municipality":"MalmΓΆ","lat":55.52891,"lon":12.93502},{"id":"SE0632180000002893","name":"Kalkudden, LimΓΆn","municipality":"GΓ€vle","lat":60.71047,"lon":17.33195},{"id":"SE0441283000000314","name":"Kallbadhuset","municipality":"Helsingborg","lat":56.0554,"lon":12.6806},{"id":"SE0441282000000416","name":"Kallbadhuset","municipality":"Landskrona","lat":55.8691,"lon":12.8142},{"id":"SE0A21484000000562","name":"Kallbadhuset","municipality":"Lysekil","lat":58.26835,"lon":11.43664},{"id":"SE0441293000000426","name":"KallsjΓΆn","municipality":"HΓ€ssleholm","lat":56.2325,"lon":13.9615},{"id":"SE0210305000000758","name":"Kalmarsandbadet","municipality":"HΓ₯bo","lat":59.5522,"lon":17.5536},{"id":"SE0930880000000786","name":"Kalmarsundsparken","municipality":"Kalmar","lat":56.6542,"lon":16.3508},{"id":"SE0A21465000001071","name":"Kalv, KalvsjΓΆn","municipality":"Svenljunga","lat":57.24309,"lon":13.07218},{"id":"SE0930840000000011","name":"Kalvhagen","municipality":"MΓΆrbylΓ₯nga","lat":56.52204,"lon":16.37064},{"id":"SE0A21407000003874","name":"Kalvsund, KΓΆrrgΓ₯rn","municipality":"ΓckerΓΆ","lat":57.71132,"lon":11.67673},{"id":"SE0110180000001837","name":"Kanaanbadet V, MΓ€laren","municipality":"Stockholm","lat":59.3492,"lon":17.8544},{"id":"SE0110180000001838","name":"Kanaanbadet Γ, MΓ€laren","municipality":"Stockholm","lat":59.3489,"lon":17.8569},{"id":"SE0441233000000027","name":"Kanalbadet","municipality":"Vellinge","lat":55.41367,"lon":12.93418},{"id":"SE0230583000001859","name":"Karlsby badplats","municipality":"Motala","lat":58.63922,"lon":15.12588},{"id":"SE0910685000000833","name":"KarlsjΓΆsjΓΆn, Alseda","municipality":"Vetlanda","lat":57.3731,"lon":15.2813},{"id":"SE0110125000000968","name":"KarlskΓ€r","municipality":"EkerΓΆ","lat":59.41865,"lon":17.66524},{"id":"SE0632183000002907","name":"Karlslund","municipality":"BollnΓ€s","lat":61.355,"lon":16.401},{"id":"SE0241880000006179","name":"Karlslunds Tegelbad","municipality":"Γrebro","lat":59.26872,"lon":15.15244},{"id":"SE0822583000003173","name":"KarsikkojΓ€rvi","municipality":"Haparanda","lat":65.9034,"lon":23.9639},{"id":"SE0910685000000834","name":"KarsnΓ€sasjΓΆn, Nye","municipality":"Vetlanda","lat":57.33741,"lon":15.26837},{"id":"SE0A21496000001686","name":"KarstorpssjΓΆn, Barnbadet","municipality":"SkΓΆvde","lat":58.42319,"lon":13.85546},{"id":"SE0411081000000154","name":"KarΓΆn","municipality":"Ronneby","lat":56.16615,"lon":15.28967},{"id":"SE0930880000000788","name":"Kattrumpan","municipality":"Kalmar","lat":56.6661,"lon":16.3725},{"id":"SE0A11381000006980","name":"KattvΓ€gen hundbad","municipality":"Laholm","lat":56.4765,"lon":12.9246},{"id":"SE0822521000003339","name":"KauhajΓ€rvi","municipality":"Pajala","lat":66.9199,"lon":23.14216},{"id":"SE0822583000007061","name":"KenkΓ€lahti, SeskarΓΆ","municipality":"Haparanda","lat":65.7054,"lon":23.7253},{"id":"SE0930885000000476","name":"KesnΓ€sviken","municipality":"Borgholm","lat":57.17606,"lon":17.05581},{"id":"SE0441290000000670","name":"Kiaby badplats","municipality":"Kristianstad","lat":56.0714,"lon":14.3278},{"id":"SE0A21481000004066","name":"KikΓ₯s lΓ₯ngvattens badplats","municipality":"MΓΆlndal","lat":57.64957,"lon":12.05389},{"id":"SE0812409000003114","name":"Killingsand","municipality":"Robertsfors","lat":64.1777,"lon":21.03767},{"id":"SE0A21480000000520","name":"Killingsholmen","municipality":"GΓΆteborg","lat":57.57608,"lon":11.92541},{"id":"SE0611780000002389","name":"Killstadbadet, Gapern","municipality":"Karlstad","lat":59.50914,"lon":13.61906},{"id":"SE0632183000002908","name":"KilΓ₯n","municipality":"BollnΓ€s","lat":61.20829,"lon":16.54738},{"id":"SE0930880000000789","name":"Kindbergsudde","municipality":"Kalmar","lat":56.6734,"lon":16.3767},{"id":"SE0A21401000007201","name":"Kindbobadet","municipality":"HΓ€rryda","lat":57.6575,"lon":12.1035},{"id":"SE0611737000003327","name":"KindsjΓΆn, skolan","municipality":"Torsby","lat":60.65121,"lon":12.70561},{"id":"SE0230513000001203","name":"KisasjΓΆn","municipality":"Kinda","lat":57.991,"lon":15.647},{"id":"SE0632132000003031","name":"KittesjΓΆn, Yttre","municipality":"Nordanstig","lat":61.9086,"lon":17.03},{"id":"SE0251962000002553","name":"Klackbergsbadet","municipality":"Norberg","lat":60.07184,"lon":15.90301},{"id":"SE0611715000002407","name":"KlacksjΓΆn","municipality":"Kil","lat":59.5162,"lon":13.167},{"id":"SE0441280000000064","name":"Klagshamns badplats","municipality":"MalmΓΆ","lat":55.5251,"lon":12.8955},{"id":"SE0910682000002050","name":"KlappasjΓΆn, Klappabadet","municipality":"NΓ€ssjΓΆ","lat":57.55407,"lon":14.51392},{"id":"SE0230582000001083","name":"KlarsjΓΆn","municipality":"SΓΆderkΓΆping","lat":58.44238,"lon":16.22469},{"id":"SE0930840000000012","name":"Kleva","municipality":"MΓΆrbylΓ₯nga","lat":56.54893,"lon":16.38992},{"id":"SE0930821000001937","name":"Kleven Aboda-Klint","municipality":"HΓΆgsby","lat":57.06383,"lon":15.95852},{"id":"SE0930885000000740","name":"Klinta bodar","municipality":"Borgholm","lat":56.88574,"lon":16.72093},{"id":"SE0920767000000691","name":"Klintbadet","municipality":"Markaryd","lat":56.54864,"lon":13.72479},{"id":"SE0441292000005435","name":"Klitterhus","municipality":"Γngelholm","lat":56.26438,"lon":12.83529},{"id":"SE0622080000004240","name":"KlockarnΓ€s Enviken","municipality":"Falun","lat":60.80863,"lon":15.77418},{"id":"SE0910685000000835","name":"KlockesjΓΆn, Ramkvilla","municipality":"Vetlanda","lat":57.20438,"lon":14.945},{"id":"SE0241885000002505","name":"Klotenbadet","municipality":"Lindesberg","lat":59.89328,"lon":15.27977},{"id":"SE0220483000005276","name":"Klubbetorp","municipality":"Katrineholm","lat":58.84294,"lon":16.52429},{"id":"SE0822580000003233","name":"Klubbviken","municipality":"LuleΓ₯","lat":65.53715,"lon":22.22891},{"id":"SE0A21484000000551","name":"Klyftan","municipality":"Lysekil","lat":58.20957,"lon":11.42122},{"id":"SE0A21419000000652","name":"KlΓ€desholmens badplats","municipality":"TjΓΆrn","lat":57.94818,"lon":11.54925},{"id":"SE0611737000003319","name":"KlΓ€ggen Vallviken","municipality":"Torsby","lat":60.26506,"lon":12.78653},{"id":"SE0220461000001069","name":"KlΓ€mmingsberg KlΓ€mmingen","municipality":"Gnesta","lat":59.0825,"lon":17.31122},{"id":"SE0A21492000000945","name":"KnarrbysjΓΆns badplats","municipality":"Γ
mΓ₯l","lat":59.00002,"lon":12.46199},{"id":"SE0411080000000217","name":"Knipehall","municipality":"Karlskrona","lat":56.17614,"lon":15.67709},{"id":"SE0441281000004058","name":"KnivsΓ₯sens stenbrott","municipality":"Lund","lat":55.65911,"lon":13.4007},{"id":"SE0622085000002444","name":"KnutsbotjΓ€rnen","municipality":"Ludvika","lat":60.12838,"lon":15.20063},{"id":"SE0110182000001169","name":"Kocktorpsbadet","municipality":"Nacka","lat":59.3138,"lon":18.25144},{"id":"SE0210381000001366","name":"Koffsan","municipality":"EnkΓΆping","lat":59.53869,"lon":17.13529},{"id":"SE0930883000001627","name":"Kogarens badplats vid DrΓΆppstad","municipality":"VΓ€stervik","lat":57.7998,"lon":16.2693},{"id":"SE0A11383000005415","name":"Kohagen","municipality":"Varberg","lat":57.04004,"lon":12.29132},{"id":"SE0920764000006520","name":"Kojtets badplats Vislanda","municipality":"Alvesta","lat":5.1081,"lon":11.7775},{"id":"SE0251982000002538","name":"KolarbysjΓΆn","municipality":"Fagersta","lat":60.02565,"lon":15.80154},{"id":"SE0822581000002754","name":"KolertrΓ€sket, Koler","municipality":"PiteΓ₯","lat":65.50473,"lon":20.47515},{"id":"SE0411082000000166","name":"Kollevik","municipality":"Karlshamn","lat":56.1605,"lon":14.8983},{"id":"SE0611784000003289","name":"KolsjΓΆn","municipality":"Arvika","lat":59.69577,"lon":12.42478},{"id":"SE0910685000000836","name":"KolsjΓΆn, Karlstorp","municipality":"Vetlanda","lat":57.52295,"lon":15.52084},{"id":"SE0A21430000000177","name":"Kolstorpevattnet","municipality":"Munkedal","lat":58.5784,"lon":11.5345},{"id":"SE0251983000002580","name":"Kolsva kommunalbad","municipality":"KΓΆping","lat":59.60908,"lon":15.82526},{"id":"SE0930883000001645","name":"Koltorpets badplats (ΓbΓ€len)","municipality":"VΓ€stervik","lat":57.5931,"lon":16.4065},{"id":"SE0241860000003273","name":"Kommunalbadet","municipality":"LaxΓ₯","lat":58.9325,"lon":14.6443},{"id":"SE0712284000003039","name":"KomnΓ€sbadet, BillsjΓΆsjΓΆn","municipality":"ΓrnskΓΆldsvik","lat":63.31414,"lon":18.42385},{"id":"SE0220488000002074","name":"KomΓΆtet, Anderviken","municipality":"Trosa","lat":58.92577,"lon":17.5882},{"id":"SE0241864000002528","name":"Kopparberg, DjΓ€kens bygge","municipality":"Ljusnarsberg","lat":59.86994,"lon":14.95256},{"id":"SE0712284000003062","name":"KornsjΓΆstrand, KornsjΓΆ","municipality":"ΓrnskΓΆldsvik","lat":63.21584,"lon":18.42391},{"id":"SE0812401000003540","name":"KorptjΓ€rn","municipality":"Nordmaling","lat":63.6151,"lon":19.56539},{"id":"SE0A21487000006240","name":"Korseberg","municipality":"VΓ€nersborg","lat":58.3546,"lon":12.3077},{"id":"SE0632181000004406","name":"Korsika","municipality":"Sandviken","lat":60.59799,"lon":16.63062},{"id":"SE0A21473000001980","name":"KorssjΓΆns badplats","municipality":"TΓΆreboda","lat":58.79867,"lon":14.22527},{"id":"SE0110186000001454","name":"KottlasjΓΆn, badviken","municipality":"LidingΓΆ","lat":59.34881,"lon":18.17922},{"id":"SE0930880000004432","name":"Koudden","municipality":"Kalmar","lat":56.7061,"lon":16.3736},{"id":"SE0632104000004461","name":"Kratte masugn","municipality":"Hofors","lat":60.45093,"lon":16.3808},{"id":"SE0411080000000209","name":"Kristianopel","municipality":"Karlskrona","lat":56.25438,"lon":16.04206},{"id":"SE0110180000004555","name":"Kristinebergsbadet","municipality":"Stockholm","lat":59.3382,"lon":18.0009},{"id":"SE0910662000001097","name":"KroksjΓΆbadet","municipality":"Gislaved","lat":57.45856,"lon":13.59917},{"id":"SE0A21486000003350","name":"Krokstrand","municipality":"StrΓΆmstad","lat":59.0043,"lon":11.4466},{"id":"SE0A11384000000462","name":"Krokvik","municipality":"Kungsbacka","lat":57.3405,"lon":12.1304},{"id":"SE0611780000002382","name":"KroppkΓ€rrssjΓΆn","municipality":"Karlstad","lat":59.39104,"lon":13.53552},{"id":"SE0622062000002668","name":"KrΓ₯kbergsbadet, OrsasjΓΆn","municipality":"Mora","lat":61.03708,"lon":14.52849},{"id":"SE0930884000001431","name":"KrΓΆnbadet","municipality":"Vimmerby","lat":57.71851,"lon":15.83457},{"id":"SE0411081000000155","name":"Kuggeboda","municipality":"Ronneby","lat":56.14457,"lon":15.37732},{"id":"SE0210381000001371","name":"Kulla","municipality":"EnkΓΆping","lat":59.66954,"lon":17.46968},{"id":"SE0622084000002487","name":"Kullhytt-DammsjΓΆn","municipality":"Avesta","lat":60.2193,"lon":16.3679},{"id":"SE0110187000005215","name":"KullΓΆbadet","municipality":"Vaxholm","lat":59.4107,"lon":18.30143},{"id":"SE0110138000002108","name":"Kumlabadet","municipality":"TyresΓΆ","lat":59.2148,"lon":18.19005},{"id":"SE0722380000004715","name":"KungsgΓ₯rdsviken","municipality":"Γstersund","lat":63.18478,"lon":14.46733},{"id":"SE0622083000002609","name":"Kuppdammen, Garpenbergsbadet","municipality":"Hedemora","lat":60.31562,"lon":16.18623},{"id":"SE0920763000001136","name":"KuppersmΓ₯la, Ryd","municipality":"Tingsryd","lat":56.44912,"lon":14.68142},{"id":"SE0A21439000001358","name":"KuserudssjΓΆn, RΓ₯danefors badpl","municipality":"FΓ€rgelanda","lat":58.53305,"lon":12.09425},{"id":"SE0822581000002739","name":"Kvarndammen, Altersbruk","municipality":"PiteΓ₯","lat":65.48683,"lon":21.42448},{"id":"SE0611737000003323","name":"Kvarndammen, LikenΓ€s","municipality":"Torsby","lat":60.61665,"lon":13.04765},{"id":"SE0251984000002584","name":"KvarnsjΓΆn","municipality":"Arboga","lat":59.3744,"lon":15.9422},{"id":"SE0110120000006780","name":"KvarnsjΓΆn","municipality":"VΓ€rmdΓΆ","lat":59.3117,"lon":18.3969},{"id":"SE0110126000006019","name":"KvarnsjΓΆn, AffΓ€rsviken","municipality":"Huddinge","lat":59.1906,"lon":17.9903},{"id":"SE0110126000002123","name":"KvarnsjΓΆn, Nyboviksbadet","municipality":"Huddinge","lat":59.1916,"lon":17.9728},{"id":"SE0632161000004471","name":"KvarnsjΓΆn,RamsjΓΆ","municipality":"Ljusdal","lat":62.1917,"lon":15.6609},{"id":"SE0251980000002552","name":"Kvicksund, Sandabadet","municipality":"VΓ€sterΓ₯s","lat":59.46859,"lon":16.2934},{"id":"SE0A21442000001336","name":"KvinnestadssjΓΆn","municipality":"VΓ₯rgΓ₯rda","lat":58.0089,"lon":12.8735},{"id":"SE0441291000000327","name":"Kyhl, Borrby strand","municipality":"Simrishamn","lat":55.43516,"lon":14.2382},{"id":"SE0A21490000001802","name":"KypesjΓΆns badplats, BorΓ₯s","municipality":"BorΓ₯s","lat":57.74163,"lon":12.95826},{"id":"SE0441282000000411","name":"Kyrkbacken","municipality":"Landskrona","lat":55.9099,"lon":12.6716},{"id":"SE0632161000002743","name":"KyrkbyvΓ₯gen, Ljusnan, JΓ€rvsΓΆ","municipality":"Ljusdal","lat":61.7165,"lon":16.19},{"id":"SE0611764000002878","name":"KyrkebysjΓΆn, Buda","municipality":"Grums","lat":59.2618,"lon":13.096},{"id":"SE0822583000004069","name":"Kyrkholmen, Karungi","municipality":"Haparanda","lat":66.0456,"lon":23.9729},{"id":"SE0910662000001100","name":"KyrksjΓΆbadet","municipality":"Gislaved","lat":57.34341,"lon":13.51903},{"id":"SE0A21446000001376","name":"KyrksjΓΆn MΓΆlltorp","municipality":"Karlsborg","lat":58.49743,"lon":14.41173},{"id":"SE0611760000002393","name":"Kyrksten, Alkvettern","municipality":"Storfors","lat":59.43554,"lon":14.40063},{"id":"SE0110182000004479","name":"Kyrksundsbadet","municipality":"Nacka","lat":59.25991,"lon":18.34316},{"id":"SE0632121000002713","name":"KyrktjΓ€rn, Nabban","municipality":"OvanΓ₯ker","lat":61.34188,"lon":16.07568},{"id":"SE0622085000002463","name":"Kyrkviken","municipality":"Ludvika","lat":60.07208,"lon":14.92507},{"id":"SE0110182000006540","name":"Kyrkviksbadet","municipality":"Nacka","lat":59.306,"lon":18.1392},{"id":"SE0930862000000735","name":"KΓ₯rahult, Γ
fors badplats","municipality":"Emmaboda","lat":56.75058,"lon":15.49385},{"id":"SE0930885000000738","name":"KΓ₯rehamns badplats","municipality":"Borgholm","lat":56.95237,"lon":16.88956},{"id":"SE0A21419000000653","name":"KΓ₯reviks badplats","municipality":"TjΓΆrn","lat":57.93368,"lon":11.59406},{"id":"SE0A21402000000095","name":"KΓ₯sjΓΆbadet","municipality":"Partille","lat":57.7116,"lon":12.1424},{"id":"SE0230582000001084","name":"KΓ€llbukten","municipality":"SΓΆderkΓΆping","lat":58.34308,"lon":16.80834},{"id":"SE0A21415000000108","name":"KΓ€llsnΓ€s","municipality":"Stenungsund","lat":57.9874,"lon":11.7891},{"id":"SE0110182000001237","name":"KΓ€lltorpsbadet","municipality":"Nacka","lat":59.29478,"lon":18.16229},{"id":"SE0622061000004088","name":"KΓ€llvass, Staren","municipality":"Smedjebacken","lat":60.1834,"lon":15.307},{"id":"SE0622080000002936","name":"KΓ€llviken,Falun","municipality":"Falun","lat":60.56855,"lon":15.64465},{"id":"SE0A21407000003877","name":"KΓ€llΓΆ-Knippla TranbΓ€rsviken","municipality":"ΓckerΓΆ","lat":57.75437,"lon":11.6552},{"id":"SE0441233000000022","name":"KΓ€mpinge strandbad","municipality":"Vellinge","lat":55.40174,"lon":12.96481},{"id":"SE0822580000003231","name":"KΓ€ngsΓΆn, RΓ₯neΓ₯","municipality":"LuleΓ₯","lat":65.8422,"lon":22.37235},{"id":"SE0110186000001448","name":"KΓ€ppalabadet","municipality":"LidingΓΆ","lat":59.34926,"lon":18.22761},{"id":"SE0622083000002601","name":"KΓ€ringtjΓ€rn, VΓ€sterbybadet","municipality":"Hedemora","lat":60.31502,"lon":15.92401},{"id":"SE0110188000002172","name":"KΓ€rleksudden","municipality":"NorrtΓ€lje","lat":59.75259,"lon":18.72929},{"id":"SE0910642000007000","name":"KΓ€rleksudden","municipality":"MullsjΓΆ","lat":57.9076,"lon":13.8764},{"id":"SE0A11383000000645","name":"KΓ€rradal norra(1)","municipality":"Varberg","lat":57.187,"lon":12.20809},{"id":"SE0A11383000000646","name":"KΓ€rradal sΓΆdra(2)","municipality":"Varberg","lat":57.18299,"lon":12.21212},{"id":"SE0920763000001133","name":"KΓ€rrasand","municipality":"Tingsryd","lat":56.58428,"lon":14.78514},{"id":"SE0632161000004201","name":"KΓ€rrbacken, SΓ₯nghussjΓΆn,FΓ€rila","municipality":"Ljusdal","lat":61.7467,"lon":15.7292},{"id":"SE0110125000004300","name":"KΓ€rsΓΆgΓ₯rden","municipality":"EkerΓΆ","lat":59.32286,"lon":17.91808},{"id":"SE0411082000000167","name":"KΓΆpegΓ₯rd","municipality":"Karlshamn","lat":56.1899,"lon":15.0444},{"id":"SE0A11380000000377","name":"KΓΆpenhamn","municipality":"Halmstad","lat":56.64092,"lon":12.88981},{"id":"SE0930885000000470","name":"KΓΆpingebukten","municipality":"Borgholm","lat":56.88201,"lon":16.71495},{"id":"SE0712284000003054","name":"KΓΆpmanholmens havsbad","municipality":"ΓrnskΓΆldsvik","lat":63.17991,"lon":18.59405},{"id":"SE0920781000001319","name":"KΓΆsen, Angelstad","municipality":"Ljungby","lat":56.82553,"lon":13.76518},{"id":"SE0A21491000001016","name":"KΓΆttkullasjΓΆn","municipality":"Ulricehamn","lat":57.69679,"lon":13.5758},{"id":"SE0632180000002896","name":"Laduholmen, Forsbacka","municipality":"GΓ€vle","lat":60.61251,"lon":16.88301},{"id":"SE0210380000001166","name":"LafssjΓΆn, Lafsenbadet","municipality":"Uppsala","lat":60.02428,"lon":17.81576},{"id":"SE0A21452000001875","name":"LagmanshagasjΓΆn","municipality":"Tranemo","lat":57.53992,"lon":13.62932},{"id":"SE0A21486000000106","name":"Lagunen Camping","municipality":"StrΓΆmstad","lat":58.91373,"lon":11.20383},{"id":"SE0A11381000000183","name":"Laholmsbukten, Birger Pers vΓ€g","municipality":"Laholm","lat":56.5137,"lon":12.9419},{"id":"SE0A11381000000181","name":"Laholmsbukten, Fiskaregatan","municipality":"Laholm","lat":56.4566,"lon":12.908},{"id":"SE0A11381000000182","name":"Laholmsbukten, KolonivΓ€gen","municipality":"Laholm","lat":56.4921,"lon":12.9352},{"id":"SE0822584000004004","name":"Lainio","municipality":"Kiruna","lat":67.76699,"lon":22.34395},{"id":"SE0822582000003218","name":"LakatrΓ€sk","municipality":"Boden","lat":66.23823,"lon":21.14675},{"id":"SE0611783000002344","name":"LakenesjΓΆn, SΓΆrby","municipality":"Hagfors","lat":59.96733,"lon":13.47289},{"id":"SE0A21401000000056","name":"LandvettersjΓΆn-FrΓΆkens udde","municipality":"HΓ€rryda","lat":57.66485,"lon":12.14005},{"id":"SE0A21401000000058","name":"LandvettersjΓΆn-PΓ₯len","municipality":"HΓ€rryda","lat":57.685,"lon":12.2001},{"id":"SE0A21401000000059","name":"LandvettersjΓΆn-SjΓΆholmen","municipality":"HΓ€rryda","lat":57.68288,"lon":12.20148},{"id":"SE0441290000000674","name":"LandΓΆns badplats","municipality":"Kristianstad","lat":55.97205,"lon":14.40862},{"id":"SE0A21485000000587","name":"Lanesund","municipality":"Uddevalla","lat":58.33701,"lon":11.72693},{"id":"SE0241814000004068","name":"Lanna badgruva","municipality":"Lekeberg","lat":59.24496,"lon":14.92337},{"id":"SE0110188000002165","name":"Largen","municipality":"NorrtΓ€lje","lat":59.59478,"lon":18.52956},{"id":"SE0622061000003664","name":"Larsbo, SΓ€ven","municipality":"Smedjebacken","lat":60.1294,"lon":15.5988},{"id":"SE0441283000000317","name":"LarΓΆdbaden","municipality":"Helsingborg","lat":56.08852,"lon":12.64611},{"id":"SE0812462000003698","name":"Latikberg","municipality":"Vilhelmina","lat":64.62469,"lon":17.02786},{"id":"SE0822505000004045","name":"Lauker","municipality":"Arvidsjaur","lat":65.65187,"lon":19.78979},{"id":"SE0812425000002821","name":"LavsjΓΆ badplats","municipality":"Dorotea","lat":64.19188,"lon":16.66719},{"id":"SE0920763000001137","name":"Laxasand, Midingstorp","municipality":"Tingsryd","lat":56.42919,"lon":14.8253},{"id":"SE0812462000003704","name":"LaxbΓ€cken","municipality":"Vilhelmina","lat":64.63431,"lon":16.38863},{"id":"SE0220461000001067","name":"Laxne KlΓ€mmingen","municipality":"Gnesta","lat":59.16461,"lon":17.19331},{"id":"SE0241864000004858","name":"LaxtjΓ€rn","municipality":"Ljusnarsberg","lat":59.88697,"lon":15.00891},{"id":"SE0A11380000000378","name":"Laxvik","municipality":"Halmstad","lat":56.5974,"lon":12.9205},{"id":"SE0712284000003066","name":"Ledings badplats","municipality":"ΓrnskΓΆldsvik","lat":63.51605,"lon":18.59412},{"id":"SE0622062000002673","name":"LefsnΓ€sbadet, SollerΓΆn Siljan","municipality":"Mora","lat":60.89982,"lon":14.58128},{"id":"SE0822583000004070","name":"LeipijΓ€rvi","municipality":"Haparanda","lat":65.9605,"lon":23.8828},{"id":"SE0241814000003852","name":"Leken","municipality":"Lekeberg","lat":59.27661,"lon":14.78257},{"id":"SE0622029000002430","name":"Leksand strand","municipality":"Leksand","lat":60.7479,"lon":14.9704},{"id":"SE0A21460000001409","name":"LelΓ₯ng, BΓ€ckeviken","municipality":"Bengtsfors","lat":59.1822,"lon":12.04415},{"id":"SE0A21460000001408","name":"LelΓ₯ng, KrΓ₯kviken","municipality":"Bengtsfors","lat":59.13572,"lon":12.19525},{"id":"SE0611783000002339","name":"LemundtjΓ€rn, EdebΓ€ck","municipality":"Hagfors","lat":60.05947,"lon":13.58313},{"id":"SE0920760000001295","name":"LenhovdasjΓΆn","municipality":"Uppvidinge","lat":56.9981,"lon":15.2923},{"id":"SE0441284000000487","name":"Lerberget, hamnpiren","municipality":"HΓΆganΓ€s","lat":56.17453,"lon":12.55624},{"id":"SE0441284000000494","name":"Lerhamn, hamnpiren,","municipality":"HΓΆganΓ€s","lat":56.25686,"lon":12.51939},{"id":"SE0441290000000669","name":"Lerjevallens badplats","municipality":"Kristianstad","lat":56.2062,"lon":14.3164},{"id":"SE0A11384000000449","name":"Lerkil","municipality":"Kungsbacka","lat":57.4573,"lon":11.9164},{"id":"SE0241883000002576","name":"LersjΓΆn","municipality":"Karlskoga","lat":59.4157,"lon":14.5909},{"id":"SE0611766000002373","name":"LersjΓΆn, SΓΆdra Viken","municipality":"Sunne","lat":59.87052,"lon":13.01978},{"id":"SE0611782000002353","name":"LesjΓΆbyn","municipality":"Filipstad","lat":60.01325,"lon":14.15401},{"id":"SE0441272000000299","name":"LevrasjΓΆn, RΓ₯bybadet","municipality":"BromΓΆlla","lat":56.10636,"lon":14.5018},{"id":"SE0230512000001922","name":"Liden","municipality":"Ydre","lat":57.8581,"lon":15.0539},{"id":"SE0920764000001595","name":"LidnΓ€s","municipality":"Alvesta","lat":57.07294,"lon":14.60114},{"id":"SE0220480000002080","name":"LidsjΓΆn, Edstorpsbadet","municipality":"NykΓΆping","lat":58.8792,"lon":16.8792},{"id":"SE0611783000002345","name":"LidsjΓΆn, Lidsbron","municipality":"Hagfors","lat":59.91776,"lon":13.67399},{"id":"SE0822581000002741","name":"LidtrΓ€sket, Svensbyn","municipality":"PiteΓ₯","lat":65.30125,"lon":21.26008},{"id":"SE0251904000002593","name":"Liens camping","municipality":"Skinnskatteberg","lat":59.8096,"lon":15.5284},{"id":"SE0220480000002079","name":"Likstammen, Likstammens badplats","municipality":"NykΓΆping","lat":58.9294,"lon":17.2619},{"id":"SE0622080000004242","name":"Liljans HerrgΓ₯rd Karlsvik","municipality":"Falun","lat":60.54455,"lon":15.5815},{"id":"SE0441282000000415","name":"Lill-Olas ","municipality":"Landskrona","lat":55.9062,"lon":12.8014},{"id":"SE0A11383000000632","name":"Lilla Apelviken","municipality":"Varberg","lat":57.08937,"lon":12.2415},{"id":"SE0A21419000000656","name":"Lilla AskerΓΆns badplats","municipality":"TjΓΆrn","lat":58.09447,"lon":11.72584},{"id":"SE0712262000002728","name":"Lilla BandsjΓΆn","municipality":"TimrΓ₯","lat":62.4844,"lon":17.2586},{"id":"SE0910686000001606","name":"Lilla Bellen","municipality":"EksjΓΆ","lat":57.57397,"lon":15.3339},{"id":"SE0A21498000000898","name":"Lilla HavssjΓΆn","municipality":"Tidaholm","lat":58.10508,"lon":14.08815},{"id":"SE0930881000000809","name":"Lilla HindsjΓΆn","municipality":"Nybro","lat":56.9768,"lon":15.86772},{"id":"SE0A21438000000765","name":"Lilla Le:s badplats","municipality":"Dals-Ed","lat":58.90986,"lon":11.93573},{"id":"SE0230580000001766","name":"Lilla RΓ€ngen, Brokind","municipality":"LinkΓΆping","lat":58.21489,"lon":15.67561},{"id":"SE0230580000001767","name":"Lilla RΓ€ngen, VΓ€sterby","municipality":"LinkΓΆping","lat":58.24776,"lon":15.66072},{"id":"SE0622061000002662","name":"Lilla SnΓΆΓ₯n, Haggen","municipality":"Smedjebacken","lat":60.0776,"lon":15.2315},{"id":"SE0A21441000001041","name":"Lilla StamsjΓΆn","municipality":"Lerum","lat":57.75908,"lon":12.28011},{"id":"SE0622082000002420","name":"Lilla UlvsjΓΆn, Ulfshyttan","municipality":"SΓ€ter","lat":60.30814,"lon":15.38002},{"id":"SE0230581000001749","name":"Lilla ΓlgsjΓΆn","municipality":"NorrkΓΆping","lat":58.71135,"lon":16.32281},{"id":"SE0411082000000168","name":"Lilla ΓjasjΓΆn","municipality":"Karlshamn","lat":56.3097,"lon":15.0032},{"id":"SE0A21462000000730","name":"Lille VΓ€ktor","municipality":"Lilla Edet","lat":58.19177,"lon":12.06409},{"id":"SE0A21480000000508","name":"Lilleby","municipality":"GΓΆteborg","lat":57.74636,"lon":11.75106},{"id":"SE0930860000001005","name":"LillesjΓΆ, MΓΆrlunda","municipality":"Hultsfred","lat":57.3392,"lon":15.8829},{"id":"SE0441286000000351","name":"Lilleskog","municipality":"Ystad","lat":55.42499,"lon":13.76547},{"id":"SE0722361000003972","name":"LillhΓ€rdal, OrrmosjΓΆn","municipality":"HΓ€rjedalen","lat":61.83558,"lon":14.10601},{"id":"SE0812404000003685","name":"LillsandsjΓΆn","municipality":"Vindeln","lat":64.31803,"lon":19.68338},{"id":"SE0230581000001750","name":"LillsjΓΆbadet","municipality":"NorrkΓΆping","lat":58.6586,"lon":16.1462},{"id":"SE0722380000003084","name":"LillsjΓΆhΓΆgen","municipality":"Γstersund","lat":63.17688,"lon":15.1905},{"id":"SE0110139000001714","name":"LillsjΓΆn","municipality":"Upplands-Bro","lat":59.49077,"lon":17.71686},{"id":"SE0622084000002480","name":"LillsjΓΆn","municipality":"Avesta","lat":60.1322,"lon":16.06513},{"id":"SE0230580000001820","name":"LillsjΓΆn, Nykil","municipality":"LinkΓΆping","lat":58.27601,"lon":15.40446},{"id":"SE0611766000002374","name":"LillsjΓΆn, Uddheden","municipality":"Sunne","lat":59.94913,"lon":12.9165},{"id":"SE0722380000003085","name":"LillsjΓΆns badplats","municipality":"Γstersund","lat":63.16738,"lon":14.7034},{"id":"SE0622080000002937","name":"Lilltorpet,Falun","municipality":"Falun","lat":60.59537,"lon":15.57516},{"id":"SE0241860000004426","name":"LillΓ€ngen","municipality":"LaxΓ₯","lat":59.10091,"lon":14.65327},{"id":"SE0930860000000989","name":"Linden, LΓΆnneberga","municipality":"Hultsfred","lat":57.5242,"lon":15.6859},{"id":"SE0241885000002506","name":"LindesjΓΆn, Loppholmen","municipality":"Lindesberg","lat":59.59161,"lon":15.22111},{"id":"SE0A21485000000601","name":"LindesnΓ€s","municipality":"Uddevalla","lat":58.32838,"lon":11.88187},{"id":"SE0622021000004016","name":"LindesnΓ€sbadet","municipality":"Vansbro","lat":60.33046,"lon":14.52345},{"id":"SE0A21480000002252","name":"LindΓ₯s","municipality":"GΓΆteborg","lat":57.56772,"lon":11.92494},{"id":"SE0241885000004994","name":"Lingon och MjΓΆlk, LindesjΓΆn","municipality":"Lindesberg","lat":59.57998,"lon":15.23022},{"id":"SE0441292000000342","name":"Lingvallen","municipality":"Γngelholm","lat":56.28263,"lon":12.82719},{"id":"SE0930881000000812","name":"LinneasjΓΆn, Campingen","municipality":"Nybro","lat":56.7379,"lon":15.91961},{"id":"SE0930881000000813","name":"LinneasjΓΆn, StrandvΓ€gen","municipality":"Nybro","lat":56.7397,"lon":15.91635},{"id":"SE0920780000001547","name":"LinnebjΓΆrkesjΓΆn, NΓ€sby","municipality":"VΓ€xjΓΆ","lat":56.9842,"lon":15.1816},{"id":"SE0910665000001262","name":"LinnesjΓΆn","municipality":"Vaggeryd","lat":57.3956,"lon":14.1663},{"id":"SE0A21419000000657","name":"Linnevikens badplats","municipality":"TjΓΆrn","lat":58.01526,"lon":11.52298},{"id":"SE0722361000003970","name":"LinsellsjΓΆn","municipality":"HΓ€rjedalen","lat":62.15228,"lon":13.95589},{"id":"SE0632184000002997","name":"LintjΓ€rn, Hudiksvall-HΓΆg","municipality":"Hudiksvall","lat":61.7862,"lon":16.9923},{"id":"SE0251907000002869","name":"LisjΓΆbadet, SΓΆrsjΓΆn","municipality":"Surahammar","lat":59.68427,"lon":16.08563},{"id":"SE0622031000006079","name":"Ljugaren, Born","municipality":"RΓ€ttvik","lat":60.9602,"lon":15.2848},{"id":"SE0940980000000245","name":"Ljugarn","municipality":"Gotland","lat":57.331,"lon":18.7134},{"id":"SE0812480000002806","name":"Ljumviken","municipality":"UmeΓ₯","lat":63.695,"lon":20.3678},{"id":"SE0712260000003021","name":"Ljungaverk","municipality":"Γ
nge","lat":62.48873,"lon":16.07658},{"id":"SE0930880000000797","name":"LjungnΓ€s","municipality":"Kalmar","lat":56.8281,"lon":16.419},{"id":"SE0A11382000000615","name":"LjungsjΓΆn","municipality":"Falkenberg","lat":56.9923,"lon":12.5559},{"id":"SE0230580000001813","name":"LjungsjΓΆn","municipality":"LinkΓΆping","lat":58.52804,"lon":15.45178},{"id":"SE0441233000000023","name":"Ljungskogens strandbad","municipality":"Vellinge","lat":55.39027,"lon":12.9343},{"id":"SE0622082000002417","name":"Ljusternbadet","municipality":"SΓ€ter","lat":60.33519,"lon":15.73795},{"id":"SE0722361000004237","name":"LofssjΓΆn","municipality":"HΓ€rjedalen","lat":62.16133,"lon":13.22097},{"id":"SE0930883000001651","name":"Loftahammars badplats","municipality":"VΓ€stervik","lat":57.8975,"lon":16.6876},{"id":"SE0920767000000688","name":"LokasjΓΆn","municipality":"Markaryd","lat":56.4541,"lon":13.57747},{"id":"SE0622039000004493","name":"LokatjΓ€rn Loka","municipality":"Γlvdalen","lat":61.28144,"lon":14.00536},{"id":"SE0722380000003086","name":"Lokebadet, LocknesjΓΆn","municipality":"Γstersund","lat":62.99601,"lon":14.82571},{"id":"SE0441262000000326","name":"Lomma Norra","municipality":"Lomma","lat":55.6783,"lon":13.0571},{"id":"SE0110188000002156","name":"Lommaren","municipality":"NorrtΓ€lje","lat":59.75829,"lon":18.6746},{"id":"SE0712284000003042","name":"LomsjΓΆns bad","municipality":"ΓrnskΓΆldsvik","lat":63.3332,"lon":18.6649},{"id":"SE0230509000001420","name":"Lorabadet","municipality":"ΓdeshΓΆg","lat":58.23721,"lon":14.81524},{"id":"SE0722361000004141","name":"Lossen, TjΓ€rnbrΓ€cka","municipality":"HΓ€rjedalen","lat":62.44423,"lon":12.7349},{"id":"SE0910680000003500","name":"LovsjΓΆbadet","municipality":"JΓΆnkΓΆping","lat":57.66866,"lon":14.18812},{"id":"SE0712281000003461","name":"Lucksta Matfors","municipality":"Sundsvall","lat":62.2982,"lon":17.0531},{"id":"SE0822580000003242","name":"Lulsundet","municipality":"LuleΓ₯","lat":65.59844,"lon":22.17442},{"id":"SE0822580000003235","name":"Lulviksbadet","municipality":"LuleΓ₯","lat":65.55072,"lon":22.15294},{"id":"SE0110188000002171","name":"Lundabadet","municipality":"NorrtΓ€lje","lat":59.7724,"lon":18.80672},{"id":"SE0930885000000742","name":"LundegΓ₯rds badplats","municipality":"Borgholm","lat":56.90761,"lon":16.72624},{"id":"SE0110125000000963","name":"Lundhagen","municipality":"EkerΓΆ","lat":59.26843,"lon":17.80526},{"id":"SE0251984000002588","name":"Lunger bokΓ€rr","municipality":"Arboga","lat":59.29763,"lon":15.68318},{"id":"SE0611760000002397","name":"Lungsund","municipality":"Storfors","lat":59.52473,"lon":14.1937},{"id":"SE0441293000000427","name":"LursjΓΆns badplats, HΓ€stveda","municipality":"HΓ€ssleholm","lat":56.2713,"lon":13.8926},{"id":"SE0A21485000000606","name":"Lyckorna Kungsparken","municipality":"Uddevalla","lat":58.21324,"lon":11.90209},{"id":"SE0A11384000006940","name":"Lygnern - BrΓ€ckaviken","municipality":"Kungsbacka","lat":57.4468,"lon":12.1895},{"id":"SE0A11382000002852","name":"LyngsjΓΆn Krogsered","municipality":"Falkenberg","lat":57.0722,"lon":12.9426},{"id":"SE0930883000003827","name":"Lysingsbadet, Bastubadet","municipality":"VΓ€stervik","lat":57.734,"lon":16.6722},{"id":"SE0930883000001633","name":"Lysingsbadet, JudΓΆn sandstrand","municipality":"VΓ€stervik","lat":57.7386,"lon":16.67232},{"id":"SE0930883000001632","name":"Lysingsbadet, Korpaholmarna","municipality":"VΓ€stervik","lat":57.7333,"lon":16.6791},{"id":"SE0210380000001171","name":"LyssnaΓ€ngsbadet","municipality":"Uppsala","lat":59.78272,"lon":17.63381},{"id":"SE0220428000001522","name":"Lytterstabadet","municipality":"VingΓ₯ker","lat":58.9805,"lon":15.91002},{"id":"SE0110188000005455","name":"LΓ₯garΓΆ-Γrn","municipality":"NorrtΓ€lje","lat":59.7737,"lon":18.9346},{"id":"SE0A21419000000658","name":"LΓ₯ka badplats","municipality":"TjΓΆrn","lat":58.03036,"lon":11.7645},{"id":"SE0441262000000324","name":"LΓ₯nga Bryggan i BjΓ€rred","municipality":"Lomma","lat":55.71088,"lon":13.02352},{"id":"SE0910686000001607","name":"LΓ₯nganΓ€ssjΓΆn","municipality":"EksjΓΆ","lat":57.6366,"lon":14.94969},{"id":"SE0A11382000000549","name":"LΓ₯ngasand","municipality":"Falkenberg","lat":56.8323,"lon":12.5937},{"id":"SE0910665000001266","name":"LΓ₯ngasjΓΆn","municipality":"Vaggeryd","lat":57.3738,"lon":13.986},{"id":"SE0411082000004198","name":"LΓ₯ngasjΓΆn sΓΆdra","municipality":"Karlshamn","lat":56.2221,"lon":14.8526},{"id":"SE0411082000000169","name":"LΓ₯ngasjΓΆns badplats","municipality":"Karlshamn","lat":56.2296,"lon":14.852},{"id":"SE0632182000002702","name":"LΓ₯ngbobadet","municipality":"SΓΆderhamn","lat":61.09369,"lon":16.69615},{"id":"SE0A21460000001417","name":"LΓ₯ngbrohΓΆljen, Kampudden","municipality":"Bengtsfors","lat":58.93216,"lon":12.29129},{"id":"SE0930881000000814","name":"LΓ₯ngegΓΆl","municipality":"Nybro","lat":56.90471,"lon":15.54755},{"id":"SE0A21435000000123","name":"LΓ₯ngekΓ€rr","municipality":"Tanum","lat":58.4843,"lon":11.32564},{"id":"SE0A21438000000761","name":"LΓ₯ngelidtjΓ€rns badplats","municipality":"Dals-Ed","lat":58.96958,"lon":12.00995},{"id":"SE0A21442000001331","name":"LΓ₯ngen Lena","municipality":"VΓ₯rgΓ₯rda","lat":57.99869,"lon":12.58878},{"id":"SE0A21484000000567","name":"LΓ₯ngevik","municipality":"Lysekil","lat":58.28312,"lon":11.43208},{"id":"SE0251981000003258","name":"LΓ₯ngforsen, MΓ₯ns Ols badet","municipality":"Sala","lat":59.9269,"lon":16.5665},{"id":"SE0251981000003259","name":"LΓ₯ngforsen, Skuggans bad","municipality":"Sala","lat":59.9345,"lon":16.5606},{"id":"SE0110180000001831","name":"LΓ₯ngholmens klippbad, MΓ€laren","municipality":"Stockholm","lat":59.32281,"lon":18.0347},{"id":"SE0110180000001832","name":"LΓ₯ngholmens strandbad","municipality":"Stockholm","lat":59.322,"lon":18.0241},{"id":"SE0910662000001105","name":"LΓ₯nghultsbadet","municipality":"Gislaved","lat":57.24365,"lon":13.32207},{"id":"SE0632183000002913","name":"LΓ₯ngnΓ€s","municipality":"BollnΓ€s","lat":61.3411,"lon":16.4104},{"id":"SE0632121000003619","name":"LΓ₯ngrΓΆsten, RΓΆstabo","municipality":"OvanΓ₯ker","lat":61.44564,"lon":16.05527},{"id":"SE0712283000003009","name":"LΓ₯ngsele, Strandbadet","municipality":"SollefteΓ₯","lat":63.18333,"lon":17.05221},{"id":"SE0110182000001162","name":"LΓ₯ngsjΓΆbadet","municipality":"Nacka","lat":59.30818,"lon":18.19132},{"id":"SE0110180000001872","name":"LΓ₯ngsjΓΆbadet strandbad","municipality":"Stockholm","lat":59.2649,"lon":17.975},{"id":"SE0812421000003962","name":"LΓ₯ngsjΓΆby badplats","municipality":"Storuman","lat":65.05878,"lon":16.89859},{"id":"SE0622081000002415","name":"LΓ₯ngsjΓΆn","municipality":"BorlΓ€nge","lat":60.41753,"lon":15.50745},{"id":"SE0110115000001987","name":"LΓ₯ngsjΓΆn syd","municipality":"Vallentuna","lat":59.59092,"lon":18.3415},{"id":"SE0712280000003737","name":"LΓ₯ngsjΓΆn, BrΓ₯nsvikens badplats","municipality":"HΓ€rnΓΆsand","lat":62.62572,"lon":17.74208},{"id":"SE0611764000002873","name":"LΓ₯ngsjΓΆn, Hasseldalen","municipality":"Grums","lat":59.41646,"lon":13.08099},{"id":"SE0110126000005174","name":"LΓ₯ngsjΓΆn, KrΓ₯kviksbadet","municipality":"Huddinge","lat":59.2685,"lon":17.962},{"id":"SE0210380000001172","name":"LΓ₯ngsjΓΆn, Sandviksbadet","municipality":"Uppsala","lat":60.0282,"lon":17.57289},{"id":"SE0251904000002595","name":"LΓ₯ngsvan, Sandviksbadet","municipality":"Skinnskatteberg","lat":59.7067,"lon":15.7993},{"id":"SE0930880000000783","name":"LΓ₯ngviken","municipality":"Kalmar","lat":56.6503,"lon":16.3377},{"id":"SE0712284000003077","name":"LΓ₯ngviksmons badplats","municipality":"ΓrnskΓΆldsvik","lat":63.63008,"lon":18.68402},{"id":"SE0632184000004344","name":"LΓ₯ngvind, SnΓ€cken","municipality":"Hudiksvall","lat":61.41996,"lon":17.1687},{"id":"SE0722361000004436","name":"LΓ₯ngΓ₯","municipality":"HΓ€rjedalen","lat":62.44868,"lon":13.24099},{"id":"SE0411080000000201","name":"LΓ₯ngΓΆ","municipality":"Karlskrona","lat":56.18183,"lon":15.58166},{"id":"SE0920780000001536","name":"LΓ€djasjΓΆn","municipality":"VΓ€xjΓΆ","lat":57.1042,"lon":14.6912},{"id":"SE0920761000001395","name":"LΓ€en, Lessebo badplats","municipality":"Lessebo","lat":56.74621,"lon":15.28795},{"id":"SE0812480000004415","name":"LΓ€nkebo SΓΆrmjΓΆle","municipality":"UmeΓ₯","lat":63.65119,"lon":19.99515},{"id":"SE0220486000001907","name":"LΓ€nnasjΓΆn, MerlΓ€nnabadet","municipality":"StrΓ€ngnΓ€s","lat":59.28179,"lon":16.99158},{"id":"SE0110182000001188","name":"LΓ€nnerstabadet","municipality":"Nacka","lat":59.2998,"lon":18.2557},{"id":"SE0220428000001523","name":"LΓ€ppe camping","municipality":"VingΓ₯ker","lat":59.13269,"lon":15.83747},{"id":"SE0441286000000345","name":"LΓΆderups strandbad","municipality":"Ystad","lat":55.38257,"lon":14.11049},{"id":"SE0930883000001635","name":"LΓΆgarbergen","municipality":"VΓ€stervik","lat":57.7679,"lon":16.6369},{"id":"SE0241880000002648","name":"LΓΆgardammen","municipality":"Γrebro","lat":59.28735,"lon":14.9829},{"id":"SE0251980000004297","name":"LΓΆgastrand","municipality":"VΓ€sterΓ₯s","lat":59.59921,"lon":16.53868},{"id":"SE0712262000002730","name":"LΓΆgdΓΆsjΓΆn","municipality":"TimrΓ₯","lat":62.5429,"lon":17.3833},{"id":"SE0A21482000000089","name":"LΓΆkeberg","municipality":"KungΓ€lv","lat":57.90343,"lon":11.75261},{"id":"SE0930840000000016","name":"LΓΆkenΓ€s","municipality":"MΓΆrbylΓ₯nga","lat":56.7255,"lon":16.50927},{"id":"SE0A21486000003351","name":"LΓΆkholmen","municipality":"StrΓΆmstad","lat":58.99603,"lon":11.14416},{"id":"SE0632121000002718","name":"LΓΆssnan, GrΓ€ngsbo bygdegΓ₯rd","municipality":"OvanΓ₯ker","lat":61.32395,"lon":15.9006},{"id":"SE0210380000001173","name":"LΓΆtsjΓΆn, Borgardalsbadet","municipality":"Uppsala","lat":59.86246,"lon":17.94345},{"id":"SE0910682000007280","name":"LΓΆvhultsdammen","municipality":"NΓ€ssjΓΆ","lat":57.6552,"lon":14.7585},{"id":"SE0812462000003702","name":"LΓΆvliden","municipality":"Vilhelmina","lat":64.64956,"lon":16.59578},{"id":"SE0110140000001347","name":"LΓΆvnΓ€sbadet","municipality":"Nykvarn","lat":59.13774,"lon":17.45692},{"id":"SE0632104000002784","name":"LΓΆvsjΓΆn","municipality":"Hofors","lat":60.39113,"lon":16.43774},{"id":"SE0110180000001839","name":"LΓΆvstabadet, MΓ€laren","municipality":"Stockholm","lat":59.3876,"lon":17.7807},{"id":"SE0210360000000983","name":"LΓΆvstabruk","municipality":"Tierp","lat":60.4015,"lon":17.8916},{"id":"SE0251980000002550","name":"LΓΆvudden","municipality":"VΓ€sterΓ₯s","lat":59.5796,"lon":16.5212},{"id":"SE0812482000004381","name":"LΓΆvΓ₯ngers havsbad, KlΓΆssand","municipality":"SkellefteΓ₯","lat":64.29814,"lon":21.2964},{"id":"SE0812462000003694","name":"LΓΆvΓ₯sen","municipality":"Vilhelmina","lat":64.56476,"lon":16.71372},{"id":"SE0241885000004857","name":"LΓΆvΓ₯sen","municipality":"Lindesberg","lat":59.62833,"lon":15.20405},{"id":"SE0812480000002807","name":"LΓΆvΓΆsundet","municipality":"UmeΓ₯","lat":63.71544,"lon":20.38859},{"id":"SE0920760000001297","name":"Madkroken, NottebΓ€ck","municipality":"Uppvidinge","lat":57.10036,"lon":15.16227},{"id":"SE0920780000001546","name":"Madkroken, Sandskog","municipality":"VΓ€xjΓΆ","lat":57.0717,"lon":15.1095},{"id":"SE0920780000001555","name":"MadsjΓΆ","municipality":"VΓ€xjΓΆ","lat":56.816,"lon":14.675},{"id":"SE0110126000002125","name":"Magelungen, Γ
gesta GΓ₯rd","municipality":"Huddinge","lat":59.2254,"lon":18.1},{"id":"SE0110126000004054","name":"Magelungen, Γ
gesta Naturistbad","municipality":"Huddinge","lat":59.2272,"lon":18.0979},{"id":"SE0441292000000340","name":"Magnarp","municipality":"Γngelholm","lat":56.29629,"lon":12.79009},{"id":"SE0251907000002871","name":"MagsjΓΆn, Campingbadet","municipality":"Surahammar","lat":59.72972,"lon":16.21133},{"id":"SE0910662000001103","name":"MajsjΓΆn","municipality":"Gislaved","lat":57.29505,"lon":13.35511},{"id":"SE0441278000000139","name":"Malens havsbad","municipality":"BΓ₯stad","lat":56.4322,"lon":12.8605},{"id":"SE0220483000001123","name":"Malmen","municipality":"Katrineholm","lat":59.04426,"lon":16.24069},{"id":"SE0822505000007420","name":"Malmesjaur ","municipality":"Arvidsjaur","lat":65.9296,"lon":19.1842},{"id":"SE0632104000002786","name":"MalmjΓ€rn","municipality":"Hofors","lat":60.48184,"lon":16.53621},{"id":"SE0251983000002579","name":"MalmΓΆn","municipality":"KΓΆping","lat":59.47697,"lon":16.06274},{"id":"SE0632184000003026","name":"Malnbaden, Hudiksvall","municipality":"Hudiksvall","lat":61.7172,"lon":17.1757},{"id":"SE0622085000005999","name":"MalsjΓΆn","municipality":"Ludvika","lat":60.24066,"lon":15.23061},{"id":"SE0110188000002157","name":"Malsta","municipality":"NorrtΓ€lje","lat":59.76935,"lon":18.64082},{"id":"SE0110180000001840","name":"Maltesholmsbadet, Brygga","municipality":"Stockholm","lat":59.3562,"lon":17.8393},{"id":"SE0110180000001841","name":"Maltesholmsbadet, Strand","municipality":"Stockholm","lat":59.3561,"lon":17.8411},{"id":"SE0611784000003290","name":"Mangen","municipality":"Arvika","lat":59.73568,"lon":12.81042},{"id":"SE0611780000002391","name":"Mangenbadet","municipality":"Karlstad","lat":59.61744,"lon":13.87914},{"id":"SE0A21480000000531","name":"Marholmen","municipality":"GΓΆteborg","lat":57.62308,"lon":11.91885},{"id":"SE0611780000004053","name":"Mariebergsskogen","municipality":"Karlstad","lat":59.36845,"lon":13.48802},{"id":"SE0110180000007140","name":"Marieviksbadet: barnbassΓ€ng","municipality":"Stockholm","lat":59.3099,"lon":18.0319},{"id":"SE0110180000007340","name":"Marieviksbadet: lekbassΓ€ng","municipality":"Stockholm","lat":59.3094,"lon":18.0317},{"id":"SE0A21452000001873","name":"MarjebosjΓΆn","municipality":"Tranemo","lat":57.44707,"lon":13.4849},{"id":"SE0210319000000774","name":"Marma Badplats","municipality":"Γlvkarleby","lat":60.49068,"lon":17.42059},{"id":"SE0220428000001525","name":"Marmorbyn","municipality":"VingΓ₯ker","lat":59.04689,"lon":16.06441},{"id":"SE0A21460000001413","name":"MarsjΓΆn","municipality":"Bengtsfors","lat":58.80749,"lon":12.20175},{"id":"SE0A21490000001799","name":"MarsjΓΆns badplats, RΓ₯ngedala","municipality":"BorΓ₯s","lat":57.81873,"lon":13.13281},{"id":"SE0110192000002136","name":"Marsta","municipality":"NynΓ€shamn","lat":58.95229,"lon":17.77998},{"id":"SE0A21482000000081","name":"Marstrand strandverket","municipality":"KungΓ€lv","lat":57.88255,"lon":11.58115},{"id":"SE0220480000002213","name":"Marsviken, KrognΓ€sbadet","municipality":"NykΓΆping","lat":58.6634,"lon":16.9649},{"id":"SE0622081000002411","name":"Masenbadet","municipality":"BorlΓ€nge","lat":60.52796,"lon":15.35892},{"id":"SE0822583000003175","name":"MatinjΓ€rvi, SeskarΓΆ","municipality":"Haparanda","lat":65.7357,"lon":23.7292},{"id":"SE0622083000002600","name":"Matsbobadet","municipality":"Hedemora","lat":60.29857,"lon":15.9623},{"id":"SE0230583000002192","name":"Medevi, Torpa Sand","municipality":"Motala","lat":58.66719,"lon":14.92629},{"id":"SE0812480000004414","name":"Megrundet HΓΆrnefors","municipality":"UmeΓ₯","lat":63.6205,"lon":19.9295},{"id":"SE0210360000000984","name":"Mehedeby","municipality":"Tierp","lat":60.45339,"lon":17.38603},{"id":"SE0611715000002402","name":"Mellan Fryken, Hagudden","municipality":"Kil","lat":59.6347,"lon":13.3102},{"id":"SE0611785000003584","name":"Mellan Svan, Strand","municipality":"SΓ€ffle","lat":59.1978,"lon":12.5473},{"id":"SE0A11315000000361","name":"MellanfΓ€rgen, Ibro","municipality":"Hylte","lat":56.92142,"lon":13.33532},{"id":"SE0A21496000001689","name":"Melldala, LΓ₯ngenbadet","municipality":"SkΓΆvde","lat":58.51164,"lon":13.74639},{"id":"SE0220482000000751","name":"MellΓΆsabadet","municipality":"Flen","lat":59.09788,"lon":16.55126},{"id":"SE0822584000003688","name":"MerasjΓ€rvi","municipality":"Kiruna","lat":67.53346,"lon":21.90095},{"id":"SE0812462000003695","name":"Meselefors","municipality":"Vilhelmina","lat":64.4336,"lon":16.80133},{"id":"SE0712284000003045","name":"MesjΓΆns badplats, HΓ€danberg","municipality":"ΓrnskΓΆldsvik","lat":63.6013,"lon":18.19796},{"id":"SE0920763000001138","name":"MidingsbrΓ₯te","municipality":"Tingsryd","lat":56.39569,"lon":14.83041},{"id":"SE0241860000004260","name":"Midsommarbadet","municipality":"LaxΓ₯","lat":58.9828,"lon":14.6241},{"id":"SE0411081000000156","name":"Millegarne","municipality":"Ronneby","lat":56.13348,"lon":15.31762},{"id":"SE0A21465000001057","name":"MjΓΆbΓ€ck, KorrasjΓΆn","municipality":"Svenljunga","lat":57.31564,"lon":12.86969},{"id":"SE0441290000000667","name":"MjΓΆnΓ€s badplats","municipality":"Kristianstad","lat":56.2387,"lon":14.3065},{"id":"SE0A21489000001698","name":"MjΓΆrn, BergsjΓΆdalsviken","municipality":"AlingsΓ₯s","lat":57.8863,"lon":12.46583},{"id":"SE0A21489000001699","name":"MjΓΆrn, LΓΆvekulle camping","municipality":"AlingsΓ₯s","lat":57.92585,"lon":12.48434},{"id":"SE0812482000003383","name":"Mobackenbadet","municipality":"SkellefteΓ₯","lat":64.75122,"lon":20.86667},{"id":"SE0A21491000001021","name":"Mogden HΓΆkerum","municipality":"Ulricehamn","lat":57.84841,"lon":13.2957},{"id":"SE0632182000002707","name":"Mohed, FlorsjΓΆn","municipality":"SΓΆderhamn","lat":61.29248,"lon":16.82396},{"id":"SE0622026000006400","name":"MojesjΓΆn, N:a badplatsen","municipality":"Gagnef","lat":60.6067,"lon":15.0853},{"id":"SE0622026000002619","name":"MojesjΓΆn, S:a badplatsen","municipality":"Gagnef","lat":60.6004,"lon":15.0889},{"id":"SE0611780000002390","name":"MolkomsjΓΆn, Graningebadet","municipality":"Karlstad","lat":59.59508,"lon":13.70952},{"id":"SE0411081000000160","name":"MollsjΓΆn","municipality":"Ronneby","lat":56.32601,"lon":15.31209},{"id":"SE0441278000000137","name":"Morgonbryggan, Torekovs hamn","municipality":"BΓ₯stad","lat":56.4264,"lon":12.6218},{"id":"SE0210331000002564","name":"MorgongΓ₯va, AxsjΓΆbadet","municipality":"Heby","lat":59.92641,"lon":16.95405},{"id":"SE0712284000003050","name":"MosjΓΆns badplats","municipality":"ΓrnskΓΆldsvik","lat":63.3168,"lon":19.01809},{"id":"SE0822505000004531","name":"Moskosels Camping (Lullejaur)","municipality":"Arvidsjaur","lat":65.88375,"lon":19.46347},{"id":"SE0441264000000306","name":"Mossbylund","municipality":"Skurup","lat":55.41329,"lon":13.62527},{"id":"SE0441264000000307","name":"Mossbystrand","municipality":"Skurup","lat":55.41691,"lon":13.6375},{"id":"SE0930884000001432","name":"MossjΓΆn","municipality":"Vimmerby","lat":57.63586,"lon":15.69723},{"id":"SE0230583000001856","name":"Motala, GrΓΆna Plan, \"KnΓ€ppaviken\"","municipality":"Motala","lat":58.55151,"lon":15.09323},{"id":"SE0230583000001855","name":"Motala, Jerusalemsbadet","municipality":"Motala","lat":58.51108,"lon":15.0117},{"id":"SE0230583000001857","name":"Motala, Pariserviken","municipality":"Motala","lat":58.53561,"lon":14.98625},{"id":"SE0230583000001858","name":"Motala, RΓ₯ssnΓ€sbadet","municipality":"Motala","lat":58.52846,"lon":14.99157},{"id":"SE0910642000001969","name":"MullsjΓΆn, Barnbadet","municipality":"MullsjΓΆ","lat":57.91333,"lon":13.88806},{"id":"SE0910642000001970","name":"MullsjΓΆn, Hotell MullsjΓΆ","municipality":"MullsjΓΆ","lat":57.9054,"lon":13.8735},{"id":"SE0A21497000001187","name":"MullsjΓΆn, Varpet","municipality":"Hjo","lat":58.3082,"lon":14.2079},{"id":"SE0241882000002520","name":"Multen","municipality":"Askersund","lat":58.91444,"lon":15.05453},{"id":"SE0241814000003849","name":"Multen Sixtorp","municipality":"Lekeberg","lat":59.18032,"lon":14.64814},{"id":"SE0910680000003498","name":"Munkabobadet","municipality":"JΓΆnkΓΆping","lat":57.6821,"lon":13.8981},{"id":"SE0712260000003024","name":"MunkbysjΓΆn","municipality":"Γ
nge","lat":62.41783,"lon":16.37974},{"id":"SE0622083000002599","name":"Munkenbadet","municipality":"Hedemora","lat":60.28429,"lon":15.97063},{"id":"SE0110191000004834","name":"Munkholmsbadet","municipality":"Sigtuna","lat":59.6074,"lon":17.7386},{"id":"SE0A21460000001416","name":"Munkvillan","municipality":"Bengtsfors","lat":58.92148,"lon":12.21332},{"id":"SE0712284000003043","name":"MyckelgensjΓΆ badplats","municipality":"ΓrnskΓΆldsvik","lat":63.5594,"lon":17.63738},{"id":"SE0910686000001608","name":"Mycklaflon, NorrsΓ₯nna","municipality":"EksjΓΆ","lat":57.58887,"lon":15.24677},{"id":"SE0110182000001221","name":"MyrsjΓΆbadet","municipality":"Nacka","lat":59.33549,"lon":18.26794},{"id":"SE0632121000003620","name":"MΓ₯llΓ₯ngen, SkrΓ€ddrabonΓ€set","municipality":"OvanΓ₯ker","lat":61.20085,"lon":15.85944},{"id":"SE0230586000001277","name":"MΓ₯nsjΓΆn","municipality":"MjΓΆlby","lat":58.39606,"lon":15.06987},{"id":"SE0722380000003088","name":"MΓ₯nstabadet, NΓ€kten","municipality":"Γstersund","lat":62.96534,"lon":14.56514},{"id":"SE0920763000001122","name":"MΓ₯rdslyckesand","municipality":"Tingsryd","lat":56.5282,"lon":14.96218},{"id":"SE0230581000001746","name":"MΓ₯rn, MΓ₯rΓ€ngsbadet","municipality":"NorrkΓΆping","lat":58.5842,"lon":15.8708},{"id":"SE0722380000004079","name":"MΓ₯rtensviken, StorsjΓΆstrand","municipality":"Γstersund","lat":63.14708,"lon":14.68969},{"id":"SE0110181000001348","name":"MΓ₯snaren, EklundsnΓ€sbadet","municipality":"SΓΆdertΓ€lje","lat":59.16983,"lon":17.58998},{"id":"SE0920781000001321","name":"MΓ€en, Torpa","municipality":"Ljungby","lat":56.70116,"lon":13.57838},{"id":"SE0220484000000698","name":"MΓ€larbaden","municipality":"Eskilstuna","lat":59.45199,"lon":16.45099},{"id":"SE0110181000001342","name":"MΓ€larbadet","municipality":"SΓΆdertΓ€lje","lat":59.22303,"lon":17.6129},{"id":"SE0110123000001470","name":"MΓ€laren, BonΓ€sbadet","municipality":"JΓ€rfΓ€lla","lat":59.46741,"lon":17.79936},{"id":"SE0110123000004306","name":"MΓ€laren, Bruket","municipality":"JΓ€rfΓ€lla","lat":59.43462,"lon":17.77663},{"id":"SE0110123000003608","name":"MΓ€laren, GΓΆrvΓ€lnsbadet","municipality":"JΓ€rfΓ€lla","lat":59.42745,"lon":17.76969},{"id":"SE0110123000001471","name":"MΓ€laren, KallhΓ€llsbadet","municipality":"JΓ€rfΓ€lla","lat":59.45179,"lon":17.79315},{"id":"SE0110127000001375","name":"MΓ€laren, Slagstabadet","municipality":"Botkyrka","lat":59.25879,"lon":17.84766},{"id":"SE0110123000001468","name":"MΓ€laren, ΓngsjΓΆbadet","municipality":"JΓ€rfΓ€lla","lat":59.505,"lon":17.8086},{"id":"SE0441291000000328","name":"MΓ€larhusen","municipality":"Simrishamn","lat":55.40662,"lon":14.21988},{"id":"SE0110180000001842","name":"MΓ€larhΓΆjdsbadet V, MΓ€laren","municipality":"Stockholm","lat":59.29994,"lon":17.92399},{"id":"SE0110180000001843","name":"MΓ€larhΓΆjdsbadet Γ, MΓ€laren","municipality":"Stockholm","lat":59.3003,"lon":17.9257},{"id":"SE0110188000002178","name":"MΓ€lbybadet","municipality":"NorrtΓ€lje","lat":59.9917,"lon":18.80057},{"id":"SE0230562000000882","name":"MΓ€seln","municipality":"FinspΓ₯ng","lat":58.69755,"lon":15.7198},{"id":"SE0A11383000000619","name":"MΓ€sen Karl Gustav","municipality":"Varberg","lat":57.2557,"lon":12.5459},{"id":"SE0441290000004258","name":"MΓ€sters vΓ€gs badplats","municipality":"Kristianstad","lat":55.9009,"lon":14.2809},{"id":"SE0241883000002569","name":"MΓΆckeln NΓ€set VΓ€stra","municipality":"Karlskoga","lat":59.3198,"lon":14.5223},{"id":"SE0241883000002570","name":"MΓΆckeln NΓ€set Γstra","municipality":"Karlskoga","lat":59.321,"lon":14.5265},{"id":"SE0241883000002571","name":"MΓΆckeln Sandtorpsbadet","municipality":"Karlskoga","lat":59.3232,"lon":14.56512},{"id":"SE0241883000002568","name":"MΓΆckeln Strandvallen","municipality":"Karlskoga","lat":59.3161,"lon":14.5167},{"id":"SE0241883000002567","name":"MΓΆckeln ΓspenΓ€s","municipality":"Karlskoga","lat":59.29029,"lon":14.49459},{"id":"SE0920765000001654","name":"MΓΆckeln, BΓΆkhults badplats","municipality":"Γlmhult","lat":56.56948,"lon":14.12879},{"id":"SE0920765000001655","name":"MΓΆckeln, DiΓΆ badplats","municipality":"Γlmhult","lat":56.64656,"lon":14.21911},{"id":"SE0241883000002572","name":"MΓΆckeln, Sandviksbadet","municipality":"Karlskoga","lat":59.32057,"lon":14.57224},{"id":"SE0920765000001653","name":"MΓΆckelns badplats","municipality":"Γlmhult","lat":56.58104,"lon":14.1535},{"id":"SE0441284000002330","name":"MΓΆlle, FΓ₯gelviken","municipality":"HΓΆganΓ€s","lat":56.27482,"lon":12.49863},{"id":"SE0441284000002331","name":"MΓΆlle, Ransvik","municipality":"HΓΆganΓ€s","lat":56.29084,"lon":12.47624},{"id":"SE0441284000000495","name":"MΓΆlle, Solviken","municipality":"HΓΆganΓ€s","lat":56.2893,"lon":12.4842},{"id":"SE0110128000002185","name":"MΓΆllebadet","municipality":"Salem","lat":59.19138,"lon":17.78238},{"id":"SE0241862000002256","name":"MΓΆllebadet","municipality":"Degerfors","lat":59.26578,"lon":14.47778},{"id":"SE0441293000000421","name":"MΓΆllerΓΆdssjΓΆn","municipality":"HΓ€ssleholm","lat":56.2795,"lon":13.7291},{"id":"SE0930840000000014","name":"MΓΆllstorp","municipality":"MΓΆrbylΓ₯nga","lat":56.66139,"lon":16.46873},{"id":"SE0930861000000853","name":"MΓΆnsterΓ₯s, NynΓ€s","municipality":"MΓΆnsterΓ₯s","lat":57.03127,"lon":16.46299},{"id":"SE0110125000000965","name":"MΓΆrby","municipality":"EkerΓΆ","lat":59.33038,"lon":17.77639},{"id":"SE0930882000001963","name":"MΓΆrtfors","municipality":"Oskarshamn","lat":57.5206,"lon":16.4655},{"id":"SE0712280000003732","name":"MΓΆrtsjΓΆn, Snibbens badplats","municipality":"HΓ€rnΓΆsand","lat":62.7992,"lon":17.86934},{"id":"SE0712280000003733","name":"MΓΆrtsjΓΆn, UtansjΓΆ badplats","municipality":"HΓ€rnΓΆsand","lat":62.7776,"lon":17.9076},{"id":"SE0822580000004416","name":"MΓΆrΓΆn","municipality":"LuleΓ₯","lat":65.48426,"lon":21.95946},{"id":"SE0920763000001130","name":"Nabben, Linneryd","municipality":"Tingsryd","lat":56.6536,"lon":15.14717},{"id":"SE0712260000003025","name":"Naggen","municipality":"Γ
nge","lat":62.27176,"lon":15.9745},{"id":"SE0930880000004433","name":"Nakenbadet","municipality":"Kalmar","lat":56.7251,"lon":16.3764},{"id":"SE0611783000002333","name":"Naren, UvanΓ₯","municipality":"Hagfors","lat":60.27074,"lon":13.79746},{"id":"SE0A21447000005515","name":"Narven, GΓ₯rdsjΓΆ","municipality":"GullspΓ₯ng","lat":58.86826,"lon":14.35507},{"id":"SE0241860000004430","name":"Nationalparken Vitsand","municipality":"LaxΓ₯","lat":58.72855,"lon":14.56924},{"id":"SE0611715000002400","name":"Nedre Fryken, Frykenbaden","municipality":"Kil","lat":59.5472,"lon":13.3403},{"id":"SE0611715000002398","name":"Nedre Fryken, Fryksta 1B,bryggan","municipality":"Kil","lat":59.5209,"lon":13.3255},{"id":"SE0611715000003966","name":"Nedre Fryken, Fryksta 1E, mitten","municipality":"Kil","lat":59.5211,"lon":13.3278},{"id":"SE0230581000001754","name":"Nedre Glottern GransjΓΆnΓ€sbadet","municipality":"NorrkΓΆping","lat":58.6788,"lon":16.2001},{"id":"SE0622031000002951","name":"Nedre GΓ€rdsjΓΆ","municipality":"RΓ€ttvik","lat":60.92318,"lon":15.21234},{"id":"SE0A21492000000942","name":"Nedre Kalven, Forsbackabaden","municipality":"Γ
mΓ₯l","lat":59.08024,"lon":12.59642},{"id":"SE0110136000004380","name":"Nedre Rudan","municipality":"Haninge","lat":59.16177,"lon":18.12848},{"id":"SE0110192000002129","name":"Nickstabadet","municipality":"NynΓ€shamn","lat":58.90539,"lon":17.93438},{"id":"SE0822583000004119","name":"Nikkala hamn","municipality":"Haparanda","lat":65.7735,"lon":23.8952},{"id":"SE0722361000004036","name":"NilsvallstjΓ€rn","municipality":"HΓ€rjedalen","lat":62.04087,"lon":14.40421},{"id":"SE0910687000000864","name":"NissagΓΆlen","municipality":"TranΓ₯s","lat":58.07143,"lon":14.98675},{"id":"SE0940980000000255","name":"Nisseviken","municipality":"Gotland","lat":57.132,"lon":18.2156},{"id":"SE0622023000002474","name":"NissΓ₯ngen","municipality":"Malung","lat":60.67607,"lon":13.49038},{"id":"SE0910604000000974","name":"Noen","municipality":"Aneby","lat":57.93803,"lon":14.73655},{"id":"SE0241884000003201","name":"NorasjΓΆn, Alnstorps ΓΆ","municipality":"Nora","lat":59.51859,"lon":15.05384},{"id":"SE0241884000003202","name":"NorasjΓΆn, Gustavsbergs camping","municipality":"Nora","lat":59.53512,"lon":15.04163},{"id":"SE0241884000003205","name":"NorasjΓΆn, TrΓ€ngbo camping","municipality":"Nora","lat":59.52617,"lon":15.04537},{"id":"SE0712260000004027","name":"Nordanede","municipality":"Γ
nge","lat":62.54688,"lon":16.41526},{"id":"SE0622039000004514","name":"NordomsjΓΆns badplats","municipality":"Γlvdalen","lat":61.70303,"lon":13.12463},{"id":"SE0A21480000004453","name":"Nordrevik","municipality":"GΓΆteborg","lat":57.56283,"lon":11.92724},{"id":"SE0722361000004308","name":"NordsjΓΆn, Ytterberg","municipality":"HΓ€rjedalen","lat":62.0891,"lon":14.47109},{"id":"SE0A21482000000086","name":"NordΓΆn RΓΆkan","municipality":"KungΓ€lv","lat":57.89325,"lon":11.68793},{"id":"SE0A21482000005698","name":"NordΓΆns badplats","municipality":"KungΓ€lv","lat":57.89358,"lon":11.6886},{"id":"SE0632161000002747","name":"Norebadet, VΓ€xnan","municipality":"Ljusdal","lat":61.8405,"lon":16.0465},{"id":"SE0251962000002556","name":"Noren, Campingbadet","municipality":"Norberg","lat":60.07138,"lon":15.91289},{"id":"SE0622039000003667","name":"Noren, NornΓ€s","municipality":"Γlvdalen","lat":61.43492,"lon":13.25058},{"id":"SE0411083000000661","name":"Norje Havsbad","municipality":"SΓΆlvesborg","lat":56.1266,"lon":14.6757},{"id":"SE0110180000007460","name":"Norr MΓ€larstrands bryggbad","municipality":"Stockholm","lat":59.327,"lon":18.0371},{"id":"SE0230580000007040","name":"Norra badet, Viggeby JΓ€rnlunden","municipality":"LinkΓΆping","lat":58.2119,"lon":15.6365},{"id":"SE0920780000006500","name":"Norra BergundasjΓΆn, BergsnΓ€sviken ","municipality":"VΓ€xjΓΆ","lat":56.8814,"lon":14.7517},{"id":"SE0441290000004518","name":"Norra Friseboda badplats","municipality":"Kristianstad","lat":55.8117,"lon":14.2125},{"id":"SE0A21484000007541","name":"Norra hamnen","municipality":"Lysekil","lat":58.2791,"lon":11.4257},{"id":"SE0611766000002366","name":"Norra HΓ€llsjΓΆn","municipality":"Sunne","lat":59.95413,"lon":13.29209},{"id":"SE0251982000003560","name":"Norra MorsjΓΆn allm. bryggan","municipality":"Fagersta","lat":59.9242,"lon":15.8131},{"id":"SE0A21466000001496","name":"Norra SΓ€mbadet, SΓ€msjΓΆn","municipality":"Herrljunga","lat":57.99163,"lon":13.16188},{"id":"SE0930840000004472","name":"Norra viken, MΓΆrbylΓ₯nga","municipality":"MΓΆrbylΓ₯nga","lat":56.53047,"lon":16.37506},{"id":"SE0241880000004203","name":"Norra Γssundet","municipality":"Γrebro","lat":59.2833,"lon":15.41031},{"id":"SE0920763000001135","name":"Norraryd, Ryd","municipality":"Tingsryd","lat":56.48684,"lon":14.70173},{"id":"SE0110187000006821","name":"Norrbergsbadet","municipality":"Vaxholm","lat":59.407,"lon":18.3477},{"id":"SE0441282000000410","name":"Norreborg","municipality":"Landskrona","lat":55.9204,"lon":12.6961},{"id":"SE0632132000003033","name":"NorrfjΓ€rden","municipality":"Nordanstig","lat":62.0651,"lon":17.4307},{"id":"SE0110187000003621","name":"Norrhamnen, Batteriet","municipality":"Vaxholm","lat":59.40554,"lon":18.35246},{"id":"SE0712281000003462","name":"Norrhassel Matfors","municipality":"Sundsvall","lat":62.2553,"lon":16.7454},{"id":"SE0930880000000792","name":"Norrlidsbadet","municipality":"Kalmar","lat":56.7048,"lon":16.3755},{"id":"SE0812480000002803","name":"NorrmjΓΆle havsbad","municipality":"UmeΓ₯","lat":63.65722,"lon":20.12834},{"id":"SE0910684000002000","name":"NorrsjΓΆn","municipality":"SΓ€vsjΓΆ","lat":57.3063,"lon":14.53409},{"id":"SE0241885000002509","name":"NorrsjΓΆn","municipality":"Lindesberg","lat":59.80031,"lon":15.14495},{"id":"SE0920760000007620","name":"NorrsjΓΆn","municipality":"Uppvidinge","lat":57.1142,"lon":15.1702},{"id":"SE0210330000001160","name":"NorrsjΓΆn, Eda","municipality":"Knivsta","lat":59.8105,"lon":17.90804},{"id":"SE0632121000004694","name":"NorrsjΓΆn, Norrby","municipality":"OvanΓ₯ker","lat":61.36917,"lon":16.11579},{"id":"SE0822581000002753","name":"Norrstrandsbadet","municipality":"PiteΓ₯","lat":65.32468,"lon":21.50171},{"id":"SE0110163000000816","name":"Norrviken, Trollholmen","municipality":"Sollentuna","lat":59.48011,"lon":17.92772},{"id":"SE0110163000003632","name":"Norrviken, TorparΓ€ngen","municipality":"Sollentuna","lat":59.4645,"lon":17.92709},{"id":"SE0812417000003743","name":"NorsjΓΆn, StΓ€djan","municipality":"NorsjΓΆ","lat":64.90191,"lon":19.47083},{"id":"SE0930884000001433","name":"Nossenbadet","municipality":"Vimmerby","lat":57.66101,"lon":15.89986},{"id":"SE0822580000003241","name":"Notviken","municipality":"LuleΓ₯","lat":65.60578,"lon":22.09565},{"id":"SE0110181000001339","name":"Nya MalmsjΓΆbadet","municipality":"SΓΆdertΓ€lje","lat":59.23466,"lon":17.53676},{"id":"SE0210380000005940","name":"Nya VΓ₯rdsΓ€trabadet","municipality":"Uppsala","lat":59.7906,"lon":17.61494},{"id":"SE0441290000000681","name":"Nyagrops badplats","municipality":"Kristianstad","lat":55.7879,"lon":14.2035},{"id":"SE0441286000000347","name":"Nybrostrand","municipality":"Ystad","lat":55.43055,"lon":13.94418},{"id":"SE0812480000002811","name":"NydalasjΓΆn","municipality":"UmeΓ₯","lat":63.83636,"lon":20.34052},{"id":"SE0A21452000001879","name":"NygΓ₯rdssjΓΆn","municipality":"Tranemo","lat":57.54999,"lon":13.48838},{"id":"SE0A11382000002851","name":"NyhagasjΓΆn","municipality":"Falkenberg","lat":56.867,"lon":12.7727},{"id":"SE0441284000000493","name":"NyhamnslΓ€ge, bryggan","municipality":"HΓΆganΓ€s","lat":56.23703,"lon":12.534},{"id":"SE0611782000002357","name":"Nykroppa","municipality":"Filipstad","lat":59.62141,"lon":14.29044},{"id":"SE0210381000001370","name":"Nylada","municipality":"EnkΓΆping","lat":59.64312,"lon":17.39212},{"id":"SE0611730000003522","name":"NysockensjΓΆn, LΓΆgarberget","municipality":"Eda","lat":59.76332,"lon":12.3691},{"id":"SE0110188000003622","name":"NysΓ€ttra badet","municipality":"NorrtΓ€lje","lat":59.81327,"lon":18.90366},{"id":"SE0411082000000171","name":"NytΓ€ppet","municipality":"Karlshamn","lat":56.1676,"lon":14.9789},{"id":"SE0712284000003040","name":"NyΓ€ngets havsbad","municipality":"ΓrnskΓΆldsvik","lat":63.25073,"lon":18.68597},{"id":"SE0110125000000964","name":"NΓ€rlundabadet","municipality":"EkerΓΆ","lat":59.29475,"lon":17.78661},{"id":"SE0622021000002828","name":"NΓ€rsjΓΆbadet","municipality":"Vansbro","lat":60.40562,"lon":14.49872},{"id":"SE0110160000001442","name":"NΓ€saΓ€ngsbadet","municipality":"TΓ€by","lat":59.426,"lon":18.1062},{"id":"SE0A21495000000806","name":"NΓ€sbadet","municipality":"Skara","lat":58.4194,"lon":13.62066},{"id":"SE0930840000000008","name":"NΓ€sby","municipality":"MΓΆrbylΓ₯nga","lat":56.24934,"lon":16.48519},{"id":"SE0A21480000000530","name":"NΓ€set","municipality":"GΓΆteborg","lat":57.61531,"lon":11.90012},{"id":"SE0920763000001127","name":"NΓ€set","municipality":"Tingsryd","lat":56.50756,"lon":15.14683},{"id":"SE0632183000002916","name":"NΓ€set","municipality":"BollnΓ€s","lat":61.22063,"lon":16.58862},{"id":"SE0822580000003691","name":"NΓ€set, Sunderbyn","municipality":"LuleΓ₯","lat":65.64432,"lon":21.94627},{"id":"SE0110181000004287","name":"NΓ€sets Udde/Glashyttan","municipality":"SΓΆdertΓ€lje","lat":59.16539,"lon":17.66319},{"id":"SE0910642000001977","name":"NΓ€ssjΓΆn","municipality":"MullsjΓΆ","lat":57.864,"lon":13.7869},{"id":"SE0622026000002622","name":"NΓ€ssjΓΆn","municipality":"Gagnef","lat":60.5598,"lon":15.1187},{"id":"SE0622039000003668","name":"NΓ€ssjΓΆn Grundsand","municipality":"Γlvdalen","lat":61.26365,"lon":14.05686},{"id":"SE0622039000004492","name":"NΓ€ssjΓΆn Liden","municipality":"Γlvdalen","lat":61.26251,"lon":14.0789},{"id":"SE0812462000003701","name":"NΓ€stansjΓΆ","municipality":"Vilhelmina","lat":64.76727,"lon":16.55228},{"id":"SE0910683000001511","name":"NΓ€stasjΓΆn","municipality":"VΓ€rnamo","lat":57.18914,"lon":13.92333},{"id":"SE0822581000002751","name":"NΓ€sudden, LΓ₯ngnΓ€s","municipality":"PiteΓ₯","lat":65.34814,"lon":21.30644},{"id":"SE0712283000003007","name":"NΓ€sΓ₯ker, OmsjΓΆ strandbad","municipality":"SollefteΓ₯","lat":63.53444,"lon":17.12078},{"id":"SE0622084000002481","name":"NΓ€vden","municipality":"Avesta","lat":60.19891,"lon":16.13867},{"id":"SE0A21441000001042","name":"NÀÀs, SΓ€velΓ₯ngen","municipality":"Lerum","lat":57.81796,"lon":12.38911},{"id":"SE0910685000000837","name":"NΓΆmmen, BjΓΆrkΓΆby","municipality":"Vetlanda","lat":57.5212,"lon":14.8776},{"id":"SE0910682000002012","name":"NΓΆmmen, StensjΓΆns badpl.","municipality":"NΓ€ssjΓΆ","lat":57.58009,"lon":14.82685},{"id":"SE0A21415000001713","name":"NΓΆsnΓ€s","municipality":"Stenungsund","lat":58.0622,"lon":11.8124},{"id":"SE0930882000001964","name":"NΓΆtΓΆ","municipality":"Oskarshamn","lat":57.157,"lon":16.4739},{"id":"SE0930883000001628","name":"Odensvi badplats (KyrksjΓΆn)","municipality":"VΓ€stervik","lat":57.8911,"lon":16.1639},{"id":"SE0A11383000000620","name":"OklΓ₯ngen","municipality":"Varberg","lat":57.29849,"lon":12.59461},{"id":"SE0930861000000849","name":"OknΓΆ, Grindstugan","municipality":"MΓΆnsterΓ₯s","lat":57.01234,"lon":16.49696},{"id":"SE0930861000000851","name":"OknΓΆ, StenskΓ€r","municipality":"MΓΆnsterΓ₯s","lat":57.00721,"lon":16.5177},{"id":"SE0930861000000852","name":"OknΓΆ, Torpet","municipality":"MΓΆnsterΓ₯s","lat":56.9957,"lon":16.5161},{"id":"SE0220486000001904","name":"OknΓΆbadet","municipality":"StrΓ€ngnΓ€s","lat":59.48948,"lon":17.08305},{"id":"SE0A11382000000541","name":"Olofsbo","municipality":"Falkenberg","lat":56.9178,"lon":12.3828},{"id":"SE0622029000002427","name":"OlsnΓ€s","municipality":"Leksand","lat":60.7858,"lon":14.7796},{"id":"SE0712282000003441","name":"OmnesjΓΆn","municipality":"Kramfors","lat":62.95042,"lon":18.3485},{"id":"SE0622029000002425","name":"Opplimen, Bodlindor","municipality":"Leksand","lat":60.7945,"lon":15.0529},{"id":"SE0632183000002917","name":"Orbaden","municipality":"BollnΓ€s","lat":61.54209,"lon":16.36729},{"id":"SE0622031000002955","name":"OresjΓΆn, Furudalsbadet SjΓΆstigen","municipality":"RΓ€ttvik","lat":61.167,"lon":15.1429},{"id":"SE0622031000005255","name":"OresjΓΆn, Svartviksbadet","municipality":"RΓ€ttvik","lat":61.13984,"lon":15.19621},{"id":"SE0411060000000273","name":"Orlunden","municipality":"OlofstrΓΆm","lat":56.2527,"lon":14.61945},{"id":"SE0110126000006020","name":"OrlΓ₯ngen, RingsΓ€ttrabadet","municipality":"Huddinge","lat":59.2037,"lon":18.0591},{"id":"SE0110126000002124","name":"OrlΓ₯ngen, Sundby GΓ₯rd","municipality":"Huddinge","lat":59.2013,"lon":18.0226},{"id":"SE0110180000004458","name":"Ormbergets bryggbad (GrΓΆndal), MΓ€laren","municipality":"Stockholm","lat":59.3185,"lon":18.00401},{"id":"SE0632183000002918","name":"Orn","municipality":"BollnΓ€s","lat":61.25255,"lon":16.65276},{"id":"SE0A21442000001335","name":"OrnungasjΓΆn","municipality":"VΓ₯rgΓ₯rda","lat":57.97068,"lon":12.92722},{"id":"SE0930881000000815","name":"OrranΓ€sasjΓΆn","municipality":"Nybro","lat":56.84478,"lon":15.71183},{"id":"SE0220482000000750","name":"OrrestaΓΆbadet","municipality":"Flen","lat":59.07274,"lon":16.58367},{"id":"SE0722380000003089","name":"Orrvikenbadet","municipality":"Γstersund","lat":63.10456,"lon":14.4255},{"id":"SE0441273000000002","name":"OsbysjΓΆn, Ebbarps camping","municipality":"Osby","lat":56.3638,"lon":13.9976},{"id":"SE0441273000004182","name":"OsbysjΓΆn, Spegeldammen","municipality":"Osby","lat":56.3672,"lon":13.986},{"id":"SE0910680000004439","name":"Oset","municipality":"JΓΆnkΓΆping","lat":57.79351,"lon":14.25885},{"id":"SE0910683000001517","name":"Osudden, VidΓΆstern","municipality":"VΓ€rnamo","lat":57.16156,"lon":14.05216},{"id":"SE0A21498000000900","name":"OtterstorpasjΓΆn, Valstad","municipality":"Tidaholm","lat":58.10232,"lon":13.82654},{"id":"SE0632104000002792","name":"Ottnaren, Tummen","municipality":"Hofors","lat":60.50524,"lon":16.58132},{"id":"SE0110180000004188","name":"OxhΓ₯lsbadet (Stora Essingen), MΓ€laren","municipality":"Stockholm","lat":59.3223,"lon":17.9843},{"id":"SE0712262000002723","name":"Oxviken","municipality":"TimrΓ₯","lat":62.4668,"lon":17.6575},{"id":"SE0910685000000838","name":"PaddingetorpasjΓΆn, BΓ€ckaby","municipality":"Vetlanda","lat":57.3034,"lon":14.9682},{"id":"SE0822521000003982","name":"Pajala, Myllyoja","municipality":"Pajala","lat":67.19902,"lon":23.40188},{"id":"SE0712281000003463","name":"Pallviken Matfors","municipality":"Sundsvall","lat":62.3091,"lon":17.1482},{"id":"SE0A21435000000120","name":"Pannberget","municipality":"Tanum","lat":58.79938,"lon":11.21898},{"id":"SE0441283000004057","name":"Parapeten","municipality":"Helsingborg","lat":56.04462,"lon":12.68631},{"id":"SE0812421000004042","name":"PautrΓ€sks badplats","municipality":"Storuman","lat":64.80422,"lon":17.53066},{"id":"SE0812460000003079","name":"PengsjΓΆns badplats","municipality":"VΓ€nnΓ€s","lat":63.84781,"lon":19.63852},{"id":"SE0822521000003338","name":"PentΓ€sjΓ€rvi","municipality":"Pajala","lat":67.0227,"lon":23.35338},{"id":"SE0712283000007121","name":"Per-Hans BΓ€cken","municipality":"SollefteΓ₯","lat":63.4479,"lon":16.8783},{"id":"SE0622023000002476","name":"Per-JostjΓ€rn","municipality":"Malung","lat":60.72732,"lon":13.54719},{"id":"SE0611782000002348","name":"Persbergs badplats","municipality":"Filipstad","lat":59.74941,"lon":14.25049},{"id":"SE0411080000000211","name":"Petersvik","municipality":"Karlskrona","lat":56.0915,"lon":15.8258},{"id":"SE0441293000000420","name":"PickelsjΓΆns badplats","municipality":"HΓ€ssleholm","lat":56.3452,"lon":13.6544},{"id":"SE0210380000006420","name":"Pilsbo badplats","municipality":"Uppsala","lat":59.6911,"lon":17.6343},{"id":"SE0A21484000000564","name":"Pinneviksbadet","municipality":"Lysekil","lat":58.26854,"lon":11.42382},{"id":"SE0822581000002750","name":"Pite Havsbad","municipality":"PiteΓ₯","lat":65.23235,"lon":21.5393},{"id":"SE0A21489000001700","name":"Playa MjΓΆrn","municipality":"AlingsΓ₯s","lat":57.93055,"lon":12.50216},{"id":"SE0822581000002758","name":"Pottan (Stordjupviken), Hemlun","municipality":"PiteΓ₯","lat":65.32105,"lon":21.38255},{"id":"SE0110180000003634","name":"Primusbadet, Lilla Essingen","municipality":"Stockholm","lat":59.3271,"lon":18.0011},{"id":"SE0910683000001503","name":"ProstsjΓΆn","municipality":"VΓ€rnamo","lat":57.18963,"lon":14.04913},{"id":"SE0622026000002623","name":"PrΓ€st-Olles sjΓΆ","municipality":"Gagnef","lat":60.5478,"lon":15.141},{"id":"SE0622061000002659","name":"PrΓ€stabadet, Norra Barken","municipality":"Smedjebacken","lat":60.1391,"lon":15.431},{"id":"SE0A21441000001043","name":"PrΓ€stens Brygga, MjΓΆrn","municipality":"Lerum","lat":57.9205,"lon":12.38754},{"id":"SE0632183000002919","name":"PrΓ€stnΓ€set","municipality":"BollnΓ€s","lat":61.32042,"lon":16.37079},{"id":"SE0A21402000000097","name":"PrΓ€sttjΓ€rn","municipality":"Partille","lat":57.7223,"lon":12.1218},{"id":"SE0712282000003442","name":"PrΓ€sttjΓ€rnen","municipality":"Kramfors","lat":62.99636,"lon":18.16974},{"id":"SE0441263000000099","name":"PudesjΓΆ","municipality":"Svedala","lat":55.57701,"lon":13.25077},{"id":"SE0A11380000003937","name":"PΓ₯arp","municipality":"Halmstad","lat":56.60238,"lon":12.91315},{"id":"SE0622083000002607","name":"PΓ₯lsbenningsbadet","municipality":"Hedemora","lat":60.25975,"lon":16.14174},{"id":"SE0441283000000315","name":"PΓ₯lsjΓΆbaden","municipality":"Helsingborg","lat":56.06414,"lon":12.6767},{"id":"SE0611737000003321","name":"PΓ€gertjΓ€rn","municipality":"Torsby","lat":60.19849,"lon":12.62344},{"id":"SE0441287000004634","name":"PΓ€rlan","municipality":"Trelleborg","lat":55.36192,"lon":13.43535},{"id":"SE0930880000000795","name":"Rafshagen","municipality":"Kalmar","lat":56.7576,"lon":16.381},{"id":"SE0A21439000001356","name":"Ragnerudsbadet","municipality":"FΓ€rgelanda","lat":58.64469,"lon":12.10135},{"id":"SE0812425000002824","name":"Rajastrands badplats","municipality":"Dorotea","lat":64.61741,"lon":15.46638},{"id":"SE0230512000001917","name":"RaklΓ₯ngen, NΓ€s","municipality":"Ydre","lat":57.952,"lon":15.05788},{"id":"SE0110138000002110","name":"Rakstabadet","municipality":"TyresΓΆ","lat":59.22529,"lon":18.30117},{"id":"SE0A21401000000062","name":"RammsjΓΆn","municipality":"HΓ€rryda","lat":57.66551,"lon":12.50637},{"id":"SE0A21490000001806","name":"RammsjΓΆns badplats, Dannike","municipality":"BorΓ₯s","lat":57.69163,"lon":13.21935},{"id":"SE0910685000000839","name":"Ramsen, PaulistrΓΆm","municipality":"Vetlanda","lat":57.46188,"lon":15.50753},{"id":"SE0110136000002144","name":"RamsjΓΆn","municipality":"Haninge","lat":59.17867,"lon":18.20153},{"id":"SE0241880000002652","name":"RamsjΓΆn","municipality":"Γrebro","lat":59.45242,"lon":15.02407},{"id":"SE0110192000002135","name":"Rangsta","municipality":"NynΓ€shamn","lat":58.99028,"lon":17.76214},{"id":"SE0910665000001281","name":"RasjΓΆn","municipality":"Vaggeryd","lat":57.5868,"lon":13.9158},{"id":"SE0822582000004174","name":"Rasmyran","municipality":"Boden","lat":65.85426,"lon":21.46942},{"id":"SE0812462000003693","name":"Rasten Saiwa","municipality":"Vilhelmina","lat":64.62162,"lon":16.68139},{"id":"SE0110163000003637","name":"Ravalen","municipality":"Sollentuna","lat":59.45385,"lon":17.90826},{"id":"SE0230562000004341","name":"Regna, sjΓΆn Regnaren","municipality":"FinspΓ₯ng","lat":58.89521,"lon":15.7004},{"id":"SE0812418000005114","name":"RentjΓ€rn","municipality":"MalΓ₯","lat":65.08412,"lon":18.97329},{"id":"SE0110187000001349","name":"ResarΓΆ, Γverbybadet","municipality":"Vaxholm","lat":59.43384,"lon":18.30958},{"id":"SE0712283000003006","name":"Resele, HΓΆvens strandbad","municipality":"SollefteΓ₯","lat":63.34214,"lon":17.05308},{"id":"SE0241860000003274","name":"Reveln","municipality":"LaxΓ₯","lat":58.90487,"lon":14.71898},{"id":"SE0A21465000001063","name":"RevesjΓΆ, Stora KvarnsjΓΆn","municipality":"Svenljunga","lat":57.44096,"lon":13.05742},{"id":"SE0812480000004522","name":"Revet havsbad,Holmsund","municipality":"UmeΓ₯","lat":63.73131,"lon":20.43674},{"id":"SE0441280000000072","name":"Ribersborg","municipality":"MalmΓΆ","lat":55.60269,"lon":12.95775},{"id":"SE0441290000003900","name":"Rigeleje badplats","municipality":"Kristianstad","lat":55.7808,"lon":14.2015},{"id":"SE0110187000005617","name":"RindΓΆ MΓ₯ldepΓ₯n","municipality":"Vaxholm","lat":59.4011,"lon":18.43006},{"id":"SE0A11380000000370","name":"RingenΓ€s","municipality":"Halmstad","lat":56.68059,"lon":12.70963},{"id":"SE0A11382000003773","name":"RingsegΓ₯rd, Skrea sand","municipality":"Falkenberg","lat":56.867,"lon":12.5382},{"id":"SE0441285000000004","name":"RingsjΓΆn SjΓΆholmen, VΓ€stra str","municipality":"EslΓΆv","lat":55.9064,"lon":13.4387},{"id":"SE0441266000000045","name":"RingsjΓΆstrand norra","municipality":"HΓΆrby","lat":55.85264,"lon":13.58744},{"id":"SE0611715000002403","name":"RingstadtjΓ€rn","municipality":"Kil","lat":59.6068,"lon":13.1279},{"id":"SE0411080000000205","name":"RingΓΆ","municipality":"Karlskrona","lat":56.18385,"lon":15.64998},{"id":"SE0220480000002083","name":"RinkebysjΓΆn, Rinkebybadet","municipality":"NykΓΆping","lat":58.7659,"lon":16.6897},{"id":"SE0A21484000000565","name":"RinkenΓ€s","municipality":"Lysekil","lat":58.27689,"lon":11.41835},{"id":"SE0230560000001274","name":"RinnasjΓΆn","municipality":"Boxholm","lat":58.27774,"lon":14.95643},{"id":"SE0611715000002404","name":"Rinnen","municipality":"Kil","lat":59.6074,"lon":13.0622},{"id":"SE0611784000003291","name":"Rinnen","municipality":"Arvika","lat":59.66469,"lon":13.01204},{"id":"SE0622061000003665","name":"Risingsbo, Leran","municipality":"Smedjebacken","lat":60.14739,"lon":15.35474},{"id":"SE0930881000000801","name":"RismΓ₯la gΓΆl","municipality":"Nybro","lat":56.76666,"lon":15.87882},{"id":"SE0812425000002822","name":"RisnΓ€sets badplats","municipality":"Dorotea","lat":64.24652,"lon":16.51527},{"id":"SE0230561000001212","name":"Risten-Byrum","municipality":"Γ
tvidaberg","lat":58.3137,"lon":16.0587},{"id":"SE0A21465000001061","name":"RoasjΓΆ, RoasjΓΆn","municipality":"Svenljunga","lat":57.54088,"lon":12.97556},{"id":"SE0910680000003496","name":"RocksjΓΆbadet","municipality":"JΓΆnkΓΆping","lat":57.77664,"lon":14.19658},{"id":"SE0910665000001282","name":"RolstorpasjΓΆn","municipality":"Vaggeryd","lat":57.3728,"lon":14.2796},{"id":"SE0441256000000287","name":"RolstorpssjΓΆn, Gummarp","municipality":"Γstra GΓΆinge","lat":56.31773,"lon":14.24765},{"id":"SE0A21482000000088","name":"RomesjΓΆn","municipality":"KungΓ€lv","lat":57.93607,"lon":11.99395},{"id":"SE0A11382000000546","name":"Rosendals havsbad norr","municipality":"Falkenberg","lat":56.9563,"lon":12.3543},{"id":"SE0110191000001488","name":"Rosersbergsbadet","municipality":"Sigtuna","lat":59.57077,"lon":17.8506},{"id":"SE0910682000002059","name":"RosjΓΆn, Skullaryds badpl.","municipality":"NΓ€ssjΓΆ","lat":57.75828,"lon":14.91863},{"id":"SE0A21486000006099","name":"RossΓΆ","municipality":"StrΓΆmstad","lat":58.84247,"lon":11.1525},{"id":"SE0930884000001435","name":"RostorpesjΓΆn","municipality":"Vimmerby","lat":57.60582,"lon":15.7567},{"id":"SE0822581000006059","name":"Rosviks badplats","municipality":"PiteΓ₯","lat":65.4209,"lon":21.67896},{"id":"SE0930861000006620","name":"Rotholmsudden, OknΓΆ","municipality":"MΓΆnsterΓ₯s","lat":57.0033,"lon":16.5092},{"id":"SE0920761000001396","name":"Rottnen, GΓΆkaskratt-Hovmantorp","municipality":"Lessebo","lat":56.78372,"lon":15.13136},{"id":"SE0920780000001548","name":"Rottnen, Sandstaden","municipality":"VΓ€xjΓΆ","lat":56.7444,"lon":15.0639},{"id":"SE0920780000001549","name":"Rottnen, TjusΓ₯s (Knallaholm)","municipality":"VΓ€xjΓΆ","lat":56.7036,"lon":15.1045},{"id":"SE0611766000003999","name":"Rottnen, TrΓΆtvik","municipality":"Sunne","lat":59.92648,"lon":12.90774},{"id":"SE0230580000001757","name":"Roxen Berg","municipality":"LinkΓΆping","lat":58.48681,"lon":15.54083},{"id":"SE0230580000001758","name":"Roxen Sandvik","municipality":"LinkΓΆping","lat":58.53882,"lon":15.62084},{"id":"SE0622080000002939","name":"RoxnΓ€s,Falun","municipality":"Falun","lat":60.57935,"lon":15.69921},{"id":"SE0210319000000778","name":"Rullsand","municipality":"Γlvkarleby","lat":60.6418,"lon":17.4729},{"id":"SE0930884000001436","name":"RumsjΓΆn","municipality":"Vimmerby","lat":57.69964,"lon":15.74735},{"id":"SE0930885000000739","name":"Runsten, BjΓ€rbybadet","municipality":"Borgholm","lat":56.70482,"lon":16.73909},{"id":"SE0441283000000310","name":"RydebΓ€ck","municipality":"Helsingborg","lat":55.96693,"lon":12.75618},{"id":"SE0A21490000004855","name":"Rydets badplats Sandared","municipality":"BorΓ₯s","lat":57.70504,"lon":12.79463},{"id":"SE0920763000006860","name":"Ryds badplats","municipality":"Tingsryd","lat":56.4703,"lon":14.698},{"id":"SE0632121000005759","name":"Ryggesbo, MΓΆrtsjΓΆn","municipality":"OvanΓ₯ker","lat":61.49234,"lon":15.81592},{"id":"SE0A21487000004226","name":"RyrsjΓΆn","municipality":"VΓ€nersborg","lat":58.3343,"lon":12.145},{"id":"SE0920781000001324","name":"RyssbysjΓΆn","municipality":"Ljungby","lat":56.86028,"lon":14.16063},{"id":"SE0441286000000348","name":"RyssvΓ€gen","municipality":"Ystad","lat":55.43461,"lon":13.89239},{"id":"SE0930883000001640","name":"Ryven, Γverum","municipality":"VΓ€stervik","lat":57.9798,"lon":16.3636},{"id":"SE0441292000000334","name":"RΓ₯bocka","municipality":"Γngelholm","lat":56.25566,"lon":12.83042},{"id":"SE0611783000002342","name":"RΓ₯dasjΓΆn, RΓ₯dastrand","municipality":"Hagfors","lat":60.01401,"lon":13.60322},{"id":"SE0611783000002341","name":"RΓ₯dasjΓΆn, Uddeholm","municipality":"Hagfors","lat":60.02403,"lon":13.61153},{"id":"SE0611783000002343","name":"RΓ₯dasjΓΆn, Γ
sarnabadet","municipality":"Hagfors","lat":59.98764,"lon":13.63724},{"id":"SE0A21481000000394","name":"RΓ₯dasjΓΆns badplats","municipality":"MΓΆlndal","lat":57.6633,"lon":12.059},{"id":"SE0110188000002167","name":"RΓ₯dmansΓΆ, Γ
kerΓΆbadet","municipality":"NorrtΓ€lje","lat":59.73351,"lon":18.93399},{"id":"SE0A11384000000463","name":"RΓ₯gelund","municipality":"Kungsbacka","lat":57.3299,"lon":12.1498},{"id":"SE0A21484000000550","name":"RΓ₯ggΓ₯rdsviks badplats","municipality":"Lysekil","lat":58.20374,"lon":11.45855},{"id":"SE0A21491000001022","name":"RΓ₯nnavΓ€gssjΓΆn","municipality":"Ulricehamn","lat":57.67545,"lon":13.49023},{"id":"SE0110188000002177","name":"RΓ₯nΓ€s, Lindbacken","municipality":"NorrtΓ€lje","lat":59.81144,"lon":18.31544},{"id":"SE0230512000001920","name":"RΓ₯sasjΓΆn, RΓ₯Γ₯s","municipality":"Ydre","lat":57.76885,"lon":15.34396},{"id":"SE0930882000001965","name":"RΓ₯ssvik","municipality":"Oskarshamn","lat":57.3724,"lon":16.2377},{"id":"SE0241885000002504","name":"RΓ₯svalen, Viken","municipality":"Lindesberg","lat":59.68993,"lon":15.11552},{"id":"SE0441283000000312","name":"RΓ₯Γ₯ badhus","municipality":"Helsingborg","lat":55.99562,"lon":12.73933},{"id":"SE0441283000000313","name":"RΓ₯Γ₯ vallar","municipality":"Helsingborg","lat":56.00146,"lon":12.72844},{"id":"SE0622085000002442","name":"RΓ€fsnΓ€s","municipality":"Ludvika","lat":60.10017,"lon":15.21553},{"id":"SE0622083000002604","name":"RΓ€llingen, LΓ₯ngshyttebadet","municipality":"Hedemora","lat":60.46354,"lon":16.02968},{"id":"SE0611730000003573","name":"RΓ€nken, Flogned","municipality":"Eda","lat":59.7707,"lon":12.2815},{"id":"SE0812417000003744","name":"RΓ€nnudden, NorsjΓΆvallens badpl","municipality":"NorsjΓΆ","lat":64.93748,"lon":19.37405},{"id":"SE0712281000003464","name":"RΓ€nnΓΆ Matfors","municipality":"Sundsvall","lat":62.3433,"lon":17.0158},{"id":"SE0822583000006880","name":"RΓ€pilahti, SeskarΓΆ ","municipality":"Haparanda","lat":65.7397,"lon":23.7938},{"id":"SE0920763000001134","name":"RΓ€vabacken, Urshult","municipality":"Tingsryd","lat":56.5437,"lon":14.80522},{"id":"SE0712281000003468","name":"RΓ€veln StΓΆde","municipality":"Sundsvall","lat":62.3716,"lon":16.8264},{"id":"SE0230562000000872","name":"RΓ€vudden, Gron","municipality":"FinspΓ₯ng","lat":58.71193,"lon":15.84141},{"id":"SE0411081000000161","name":"RΓΆabysjΓΆn","municipality":"Ronneby","lat":56.25963,"lon":15.13722},{"id":"SE0812401000003537","name":"RΓΆdviken","municipality":"Nordmaling","lat":63.5723,"lon":19.4535},{"id":"SE0712262000002731","name":"RΓΆjesjΓΆn","municipality":"TimrΓ₯","lat":62.6304,"lon":17.2389},{"id":"SE0920765000001658","name":"RΓΆmningen, RamnΓ€s badplats","municipality":"Γlmhult","lat":56.57444,"lon":13.91133},{"id":"SE0822580000003232","name":"RΓΆrbΓ€cken","municipality":"LuleΓ₯","lat":65.79739,"lon":22.60343},{"id":"SE0A21482000000085","name":"RΓΆrtΓ₯ngen","municipality":"KungΓ€lv","lat":57.92139,"lon":11.75227},{"id":"SE0441291000000331","name":"RΓΆrum, KnΓ€bΓ€ckshusen","municipality":"Simrishamn","lat":55.64218,"lon":14.27792},{"id":"SE0A21407000003878","name":"RΓΆrΓΆ, Gula skΓ€ren","municipality":"ΓckerΓΆ","lat":57.77624,"lon":11.62287},{"id":"SE0110163000000817","name":"RΓΆsjΓΆbadet","municipality":"Sollentuna","lat":59.43851,"lon":17.99475},{"id":"SE0110162000001091","name":"RΓΆsjΓΆn","municipality":"Danderyd","lat":59.43932,"lon":18.00122},{"id":"SE0920780000001560","name":"Salen, TΓΆrnΓ₯kra","municipality":"VΓ€xjΓΆ","lat":56.8132,"lon":14.5849},{"id":"SE0910685000000840","name":"Saljen, Stenberga","municipality":"Vetlanda","lat":57.3361,"lon":15.4038},{"id":"SE0822583000004122","name":"Salmis- Kirveskari","municipality":"Haparanda","lat":65.794,"lon":24.0063},{"id":"SE0632180000002884","name":"SaltharsfjΓ€rden","municipality":"GΓ€vle","lat":60.92686,"lon":17.15787},{"id":"SE0A21480000004452","name":"Saltholmen/Aspholmen","municipality":"GΓΆteborg","lat":57.65878,"lon":11.83657},{"id":"SE0A21480000000534","name":"Saltholmens Kallbadhus","municipality":"GΓΆteborg","lat":57.6587,"lon":11.8447},{"id":"SE0A21430000000180","name":"SaltkΓ€llan","municipality":"Munkedal","lat":58.4315,"lon":11.7004},{"id":"SE0441286000000349","name":"SaltsjΓΆbaden","municipality":"Ystad","lat":55.42463,"lon":13.85138},{"id":"SE0A21486000003348","name":"SaltΓΆ","municipality":"StrΓΆmstad","lat":58.87048,"lon":11.141},{"id":"SE0411080000000199","name":"SaltΓΆsand","municipality":"Karlskrona","lat":56.16587,"lon":15.56324},{"id":"SE0622029000004467","name":"SalutjΓ€rn","municipality":"Leksand","lat":60.8233,"lon":15.1415},{"id":"SE0812480000002800","name":"Sanabadet","municipality":"UmeΓ₯","lat":64.2304,"lon":20.26698},{"id":"SE0812480000002799","name":"Sand TavelsjΓΆ","municipality":"UmeΓ₯","lat":64.02369,"lon":20.02695},{"id":"SE0A21490000005799","name":"Sandared Scoutstranden","municipality":"BorΓ₯s","lat":57.70703,"lon":12.79165},{"id":"SE0A21490000001779","name":"Sandareds badplats, ViaredssjΓΆn","municipality":"BorΓ₯s","lat":57.7066,"lon":12.78522},{"id":"SE0210319000000771","name":"Sandbankarna","municipality":"Γlvkarleby","lat":60.6459,"lon":17.4284},{"id":"SE0110188000002155","name":"Sandbanken","municipality":"NorrtΓ€lje","lat":59.73809,"lon":18.3473},{"id":"SE0A11383000000623","name":"Sanddamm","municipality":"Varberg","lat":57.21558,"lon":12.15528},{"id":"SE0230582000001085","name":"Sanden","municipality":"SΓΆderkΓΆping","lat":58.33672,"lon":16.8167},{"id":"SE0230563000001312","name":"SandgΓ€rdet, Gryt","municipality":"Valdemarsvik","lat":58.1787,"lon":16.81646},{"id":"SE0441286000000344","name":"Sandhammaren","municipality":"Ystad","lat":55.38448,"lon":14.19872},{"id":"SE0940980000003923","name":"Sandhamn","municipality":"Gotland","lat":57.3244,"lon":18.1676},{"id":"SE0910642000001976","name":"SandhemssjΓΆn","municipality":"MullsjΓΆ","lat":57.9878,"lon":13.7773},{"id":"SE0110138000003944","name":"Sandholmsbadet","municipality":"TyresΓΆ","lat":59.20162,"lon":18.40887},{"id":"SE0622080000002940","name":"Sandlandet, Runn","municipality":"Falun","lat":60.58569,"lon":15.74855},{"id":"SE0712281000003459","name":"SandnΓ€set Indals-Liden","municipality":"Sundsvall","lat":62.6968,"lon":16.3935},{"id":"SE0A21442000001334","name":"SandsjΓΆn","municipality":"VΓ₯rgΓ₯rda","lat":57.90654,"lon":12.80807},{"id":"SE0910665000004347","name":"SandsjΓΆn","municipality":"Vaggeryd","lat":57.5347,"lon":14.2395},{"id":"SE0A21463000000954","name":"SandsjΓΆn, Torestorp","municipality":"Mark","lat":57.39271,"lon":12.72964},{"id":"SE0A21466000001498","name":"Sandsken","municipality":"Herrljunga","lat":57.93229,"lon":13.16355},{"id":"SE0632180000002897","name":"SandsnΓ€s, Hedesunda","municipality":"GΓ€vle","lat":60.34977,"lon":17.02052},{"id":"SE0722361000003974","name":"SandtjΓ€rn","municipality":"HΓ€rjedalen","lat":62.03957,"lon":14.36509},{"id":"SE0822582000003220","name":"SandtrΓ€sk","municipality":"Boden","lat":66.12261,"lon":21.33078},{"id":"SE0712284000003046","name":"Sandudden, NorrbΓΆle","municipality":"ΓrnskΓΆldsvik","lat":63.43685,"lon":18.26005},{"id":"SE0930885000000478","name":"Sandvik","municipality":"Borgholm","lat":57.06783,"lon":16.85137},{"id":"SE0930880000004417","name":"Sandvik","municipality":"Kalmar","lat":56.55169,"lon":16.2249},{"id":"SE0220480000003899","name":"Sandvik badplats, NynΓ€s ","municipality":"NykΓΆping","lat":58.795,"lon":17.4187},{"id":"SE0411080000000224","name":"Sandvik, HasslΓΆ","municipality":"Karlskrona","lat":56.09846,"lon":15.46051},{"id":"SE0622085000002460","name":"Sandvika","municipality":"Ludvika","lat":60.2133,"lon":14.979},{"id":"SE0622084000002482","name":"Sandvikabadet","municipality":"Avesta","lat":60.31502,"lon":16.42392},{"id":"SE0940980000000242","name":"Sandviken","municipality":"Gotland","lat":57.3975,"lon":18.8749},{"id":"SE0411083000000660","name":"Sandviken","municipality":"SΓΆlvesborg","lat":56.0197,"lon":14.5984},{"id":"SE0210330000006800","name":"Sandvikens badplats","municipality":"Knivsta","lat":59.7726,"lon":17.6389},{"id":"SE0A21439000001359","name":"SandvikesjΓΆns badplats","municipality":"FΓ€rgelanda","lat":58.59506,"lon":11.88282},{"id":"SE0822581000002764","name":"SandΓΆn, Borgarudden","municipality":"PiteΓ₯","lat":65.35071,"lon":21.57417},{"id":"SE0A21427000000039","name":"SandΓΆn, SmΓΆgen","municipality":"SotenΓ€s","lat":58.3651,"lon":11.2252},{"id":"SE0920765000001660","name":"SandΓΆrens badplats","municipality":"Γlmhult","lat":56.491,"lon":14.47158},{"id":"SE0910680000003495","name":"Sannabadet","municipality":"JΓΆnkΓΆping","lat":57.79295,"lon":14.24342},{"id":"SE0A21430000000179","name":"SannesjΓΆn","municipality":"Munkedal","lat":58.6831,"lon":11.8764},{"id":"SE0A21435000000121","name":"SannΓ€s","municipality":"Tanum","lat":58.73785,"lon":11.24564},{"id":"SE0822583000003176","name":"Santasaari","municipality":"Haparanda","lat":65.7773,"lon":23.8486},{"id":"SE0411081000000147","name":"Saxemara inre","municipality":"Ronneby","lat":56.16132,"lon":15.23421},{"id":"SE0411081000000149","name":"Saxemara yttre","municipality":"Ronneby","lat":56.15816,"lon":15.23008},{"id":"SE0622085000002461","name":"Saxen","municipality":"Ludvika","lat":60.1538,"lon":14.9767},{"id":"SE0A21485000000603","name":"Saxenhof","municipality":"Uddevalla","lat":58.26369,"lon":11.83889},{"id":"SE0930840000000015","name":"SaxnΓ€s","municipality":"MΓΆrbylΓ₯nga","lat":56.68797,"lon":16.47631},{"id":"SE0930882000001967","name":"Saxtorp","municipality":"Oskarshamn","lat":57.3532,"lon":16.1749},{"id":"SE0441261000005638","name":"SaxtorpssjΓΆn","municipality":"KΓ€vlinge","lat":55.82011,"lon":12.94973},{"id":"SE0441280000004366","name":"Scaniabadplatsen, Djuphavsbadet","municipality":"MalmΓΆ","lat":55.6174,"lon":12.9739},{"id":"SE0441278000000145","name":"Segelstorps strand","municipality":"BΓ₯stad","lat":56.356,"lon":12.7404},{"id":"SE0712281000003452","name":"SegersjΓΆn Sundsvall","municipality":"Sundsvall","lat":62.4429,"lon":17.0439},{"id":"SE0251907000002866","name":"Seglingsberg","municipality":"Surahammar","lat":59.82732,"lon":16.13496},{"id":"SE0A21486000000101","name":"SelΓ€ter","municipality":"StrΓΆmstad","lat":58.95476,"lon":11.12954},{"id":"SE0722380000003091","name":"SemsΓ₯ns utlopp","municipality":"Γstersund","lat":63.2121,"lon":14.6172},{"id":"SE0411080000000215","name":"Senoren","municipality":"Karlskrona","lat":56.12268,"lon":15.73197},{"id":"SE0910685000000841","name":"SerarpasjΓΆn, NΓ€shult","municipality":"Vetlanda","lat":57.26767,"lon":15.37261},{"id":"SE0A21465000001066","name":"Sexdrega, HagalundssjΓΆn","municipality":"Svenljunga","lat":57.56715,"lon":13.12201},{"id":"SE0632132000003034","name":"SGU Badet. BergsjΓΆ","municipality":"Nordanstig","lat":61.9523,"lon":17.076},{"id":"SE0441280000000066","name":"Sibbarp","municipality":"MalmΓΆ","lat":55.57685,"lon":12.9094},{"id":"SE0230580000001822","name":"SibborpesjΓΆn","municipality":"LinkΓΆping","lat":58.57586,"lon":15.43794},{"id":"SE0441292000000333","name":"Sibirien","municipality":"Γngelholm","lat":56.2373,"lon":12.81426},{"id":"SE0110182000001238","name":"Sickla strandbad","municipality":"Nacka","lat":59.30176,"lon":18.12414},{"id":"SE0712281000004229","name":"SidsjΓΆn","municipality":"Sundsvall","lat":62.3702,"lon":17.2776},{"id":"SE0812482000003391","name":"SidtjΓ€rn, Boliden","municipality":"SkellefteΓ₯","lat":64.86469,"lon":20.39106},{"id":"SE0210380000001176","name":"SiggeforasjΓΆn","municipality":"Uppsala","lat":59.97927,"lon":17.1547},{"id":"SE0940980000000266","name":"Sigvalde trΓ€sk","municipality":"Gotland","lat":57.3417,"lon":18.5253},{"id":"SE0812462000003700","name":"SiksjΓΆnΓ€s","municipality":"Vilhelmina","lat":64.7823,"lon":16.35973},{"id":"SE0622031000004469","name":"Siljan, Helsingland","municipality":"RΓ€ttvik","lat":60.88162,"lon":15.11626},{"id":"SE0622031000002964","name":"Siljan, StiftsgΓ₯rden","municipality":"RΓ€ttvik","lat":60.8925,"lon":15.0934},{"id":"SE0622031000002966","name":"Siljan, Vikarbybadet","municipality":"RΓ€ttvik","lat":60.90107,"lon":15.01468},{"id":"SE0622031000002963","name":"Siljansbadet, campingen","municipality":"RΓ€ttvik","lat":60.88545,"lon":15.11083},{"id":"SE0622029000002428","name":"SiljansnΓ€s camping","municipality":"Leksand","lat":60.7663,"lon":14.8792},{"id":"SE0220488000002075","name":"Sillebadet","municipality":"Trosa","lat":58.93882,"lon":17.4198},{"id":"SE0A21480000000507","name":"Sillvik","municipality":"GΓΆteborg","lat":57.74202,"lon":11.73193},{"id":"SE0611765000002314","name":"Silvergruvan, TΓΆck","municipality":"Γ
rjΓ€ng","lat":59.50688,"lon":11.82681},{"id":"SE0622061000002666","name":"Silvhyttan, Staren","municipality":"Smedjebacken","lat":60.177,"lon":15.2947},{"id":"SE0251981000003261","name":"SilvkΓΆparens bad","municipality":"Sala","lat":59.95644,"lon":16.51299},{"id":"SE0251982000005375","name":"Simbadet","municipality":"Fagersta","lat":59.98216,"lon":15.82409},{"id":"SE0812480000002812","name":"Simphamns badplats","municipality":"UmeΓ₯","lat":63.6617,"lon":20.23188},{"id":"SE0A21496000001682","name":"SimsjΓΆn Herrekvarn","municipality":"SkΓΆvde","lat":58.34891,"lon":13.77945},{"id":"SE0A21496000001681","name":"SimsjΓΆn Stora Badviken","municipality":"SkΓΆvde","lat":58.36077,"lon":13.79124},{"id":"SE0A21496000001683","name":"SimsjΓΆn Villeviken","municipality":"SkΓΆvde","lat":58.35866,"lon":13.77735},{"id":"SE0110188000002170","name":"SingΓΆ, Backbyn","municipality":"NorrtΓ€lje","lat":60.17677,"lon":18.77865},{"id":"SE0622031000002972","name":"SinksjΓΆbadet","municipality":"RΓ€ttvik","lat":61.0357,"lon":15.2089},{"id":"SE0611737000003313","name":"SirsjΓΆn, RΓΆbjΓΆrkeby","municipality":"Torsby","lat":60.15544,"lon":13.03647},{"id":"SE0A21481000000397","name":"SisjΓΆns badplats","municipality":"MΓΆlndal","lat":57.62557,"lon":11.97454},{"id":"SE0110191000001490","name":"SjudargΓ₯rdsbadet","municipality":"Sigtuna","lat":59.61775,"lon":17.68155},{"id":"SE0822581000004029","name":"SjulnΓ€s, SandΓΆn","municipality":"PiteΓ₯","lat":65.34399,"lon":21.24742},{"id":"SE0910682000002033","name":"SjunnarydssjΓΆn Ormaryds badpl.","municipality":"NΓ€ssjΓΆ","lat":57.65771,"lon":14.82468},{"id":"SE0A11381000000195","name":"SjΓΆaltesjΓΆn","municipality":"Laholm","lat":56.3569,"lon":13.2198},{"id":"SE0920764000001596","name":"SjΓΆanΓ€s","municipality":"Alvesta","lat":56.97718,"lon":14.61673},{"id":"SE0910617000000935","name":"SjΓΆarpssjΓΆn","municipality":"GnosjΓΆ","lat":57.34603,"lon":13.74299},{"id":"SE0632101000002769","name":"SjΓΆbacken","municipality":"Ockelbo","lat":60.89317,"lon":16.71322},{"id":"SE0A21490000003596","name":"SjΓΆbo badplats, ΓresjΓΆ","municipality":"BorΓ₯s","lat":57.7603,"lon":12.94518},{"id":"SE0812482000003398","name":"SjΓΆbottsand badplats","municipality":"SkellefteΓ₯","lat":64.62302,"lon":21.05009},{"id":"SE0920764000004377","name":"SjΓΆby badplats","municipality":"Alvesta","lat":56.73899,"lon":14.58668},{"id":"SE0230560000001272","name":"SjΓΆgarpesjΓΆn","municipality":"Boxholm","lat":58.2017,"lon":14.99906},{"id":"SE0A11315000000357","name":"SjΓΆgΓ₯rdssjΓΆn, Torup","municipality":"Hylte","lat":56.95345,"lon":13.08247},{"id":"SE0220428000001526","name":"SjΓΆgΓΆlets badplats","municipality":"VingΓ₯ker","lat":59.01067,"lon":16.03681},{"id":"SE0251982000003561","name":"SjΓΆhagen","municipality":"Fagersta","lat":59.969,"lon":15.8413},{"id":"SE0A21488000001012","name":"SjΓΆlanda","municipality":"TrollhΓ€ttan","lat":58.2116,"lon":12.2631},{"id":"SE0A21494000006480","name":"SjΓΆlunda","municipality":"LidkΓΆping","lat":58.5437,"lon":13.1457},{"id":"SE0A21490000003593","name":"SjΓΆmarkens badplats, ViaredssjΓΆn","municipality":"BorΓ₯s","lat":57.71171,"lon":12.83304},{"id":"SE0622081000002412","name":"SjΓΆmyran","municipality":"BorlΓ€nge","lat":60.42155,"lon":15.34223},{"id":"SE0A11382000000610","name":"SjΓΆnevadssjΓΆn","municipality":"Falkenberg","lat":56.9819,"lon":12.7468},{"id":"SE0611785000003583","name":"SjΓΆnsjΓΆ, SΓΆdra Ed","municipality":"SΓ€ffle","lat":59.17543,"lon":12.84176},{"id":"SE0930862000004655","name":"SjΓΆtorpets badplat","municipality":"Emmaboda","lat":56.63029,"lon":15.57403},{"id":"SE0A21496000001690","name":"SjΓΆtorpssjΓΆn","municipality":"SkΓΆvde","lat":58.34273,"lon":13.84402},{"id":"SE0622084000002486","name":"SjΓΆvik","municipality":"Avesta","lat":60.1608,"lon":16.3318},{"id":"SE0110180000007461","name":"SjΓΆvikskajens bryggbad","municipality":"Stockholm","lat":59.3073,"lon":18.0334},{"id":"SE0611766000002848","name":"SkacksjΓΆn","municipality":"Sunne","lat":59.72639,"lon":13.31099},{"id":"SE0930883000001652","name":"Skaftekulla badplats (Γlmaren)","municipality":"VΓ€stervik","lat":57.8116,"lon":16.3502},{"id":"SE0930883000001644","name":"Skaftets badplats (Toven)","municipality":"VΓ€stervik","lat":57.6494,"lon":16.5756},{"id":"SE0241860000003269","name":"Skagern","municipality":"LaxΓ₯","lat":58.93024,"lon":14.3344},{"id":"SE0611781000002296","name":"Skagern, Gottbol","municipality":"Kristinehamn","lat":59.0623,"lon":14.2927},{"id":"SE0A21447000000822","name":"Skagern, Otterberget","municipality":"GullspΓ₯ng","lat":58.91204,"lon":14.29004},{"id":"SE0A21447000000821","name":"Skagern, Skagersvik","municipality":"GullspΓ₯ng","lat":58.96883,"lon":14.11247},{"id":"SE0632183000002921","name":"Skajet","municipality":"BollnΓ€s","lat":61.29803,"lon":16.36106},{"id":"SE0A21490000001798","name":"Skalle badplats, ΓresjΓΆ","municipality":"BorΓ₯s","lat":57.80926,"lon":13.00611},{"id":"SE0A21490000001805","name":"SkansasjΓΆns badplats, MΓ₯lsryd","municipality":"BorΓ₯s","lat":57.66162,"lon":13.05304},{"id":"SE0930861000000859","name":"Skansenbadet, AlsterΓ₯n (Γ
lem)","municipality":"MΓΆnsterΓ₯s","lat":56.95182,"lon":16.37681},{"id":"SE0441278000000138","name":"Skansenbadet, BΓ₯stad hamn","municipality":"BΓ₯stad","lat":56.4363,"lon":12.8405},{"id":"SE0812462000003703","name":"Skansholm","municipality":"Vilhelmina","lat":64.67645,"lon":16.28867},{"id":"SE0441233000000026","name":"SkanΓΆrs Havsbad","municipality":"Vellinge","lat":55.41386,"lon":12.82671},{"id":"SE0A11383000003765","name":"Skarpenord","municipality":"Varberg","lat":57.10123,"lon":12.24119},{"id":"SE0812421000004448","name":"SkarvsjΓΆby badplats","municipality":"Storuman","lat":64.9627,"lon":17.07601},{"id":"SE0441287000000440","name":"Skateholm","municipality":"Trelleborg","lat":55.3766,"lon":13.47358},{"id":"SE0110160000001444","name":"SkavlΓΆten","municipality":"TΓ€by","lat":59.46777,"lon":18.10551},{"id":"SE0220482000000748","name":"Skebokvarnsbadet","municipality":"Flen","lat":59.05975,"lon":16.72891},{"id":"SE0930883000001649","name":"Skedshults badplats (Vindommen)","municipality":"VΓ€stervik","lat":58.0897,"lon":16.4877},{"id":"SE0251983000002581","name":"SkedvisjΓΆn, Tuna Udde","municipality":"KΓΆping","lat":59.58757,"lon":15.69009},{"id":"SE0A21463000000955","name":"Skene HedgΓ€rdesjΓΆn","municipality":"Mark","lat":57.50888,"lon":12.63413},{"id":"SE0441292000000341","name":"Skepparkroken","municipality":"Γngelholm","lat":56.28685,"lon":12.81604},{"id":"SE0441291000000281","name":"Skepparp","municipality":"Simrishamn","lat":55.72397,"lon":14.1967},{"id":"SE0622062000002682","name":"Skepphusviken, Moraparken","municipality":"Mora","lat":61.00911,"lon":14.52438},{"id":"SE0632183000002922","name":"Skeppstan","municipality":"BollnΓ€s","lat":61.47826,"lon":16.39616},{"id":"SE0A21485000000586","name":"Skeppsviken","municipality":"Uddevalla","lat":58.34024,"lon":11.90807},{"id":"SE0632183000002923","name":"SkidtjΓ€rn HΓ€llbo","municipality":"BollnΓ€s","lat":61.21881,"lon":16.41922},{"id":"SE0632183000002924","name":"SkidtjΓ€rn RengsjΓΆ","municipality":"BollnΓ€s","lat":61.37269,"lon":16.67096},{"id":"SE0712262000002722","name":"Skillinge","municipality":"TimrΓ₯","lat":62.44325,"lon":17.58052},{"id":"SE0251960000003013","name":"Skillingeudd","municipality":"KungsΓΆr","lat":59.4373,"lon":16.13998},{"id":"SE0930861000000860","name":"Skiren","municipality":"MΓΆnsterΓ₯s","lat":57.21809,"lon":16.17236},{"id":"SE0230562000000874","name":"Skiren","municipality":"FinspΓ₯ng","lat":58.76087,"lon":15.81381},{"id":"SE0230582000001086","name":"Skiren","municipality":"SΓΆderkΓΆping","lat":58.35441,"lon":16.21853},{"id":"SE0241882000003844","name":"Skiren","municipality":"Askersund","lat":58.85667,"lon":15.16408},{"id":"SE0611763000002282","name":"SkivtjΓ€rn","municipality":"Forshaga","lat":59.6266,"lon":13.4376},{"id":"SE0230586000001275","name":"SkogsjΓΆn","municipality":"MjΓΆlby","lat":58.34515,"lon":15.11874},{"id":"SE0A21499000000925","name":"SkogssjΓΆn","municipality":"FalkΓΆping","lat":58.17448,"lon":13.52227},{"id":"SE0210305000004074","name":"Skokloster Camping","municipality":"HΓ₯bo","lat":59.707,"lon":17.5354},{"id":"SE0712284000003056","name":"Skorpedsbadet","municipality":"ΓrnskΓΆldsvik","lat":63.38734,"lon":17.84323},{"id":"SE0A21438000000764","name":"SkottesjΓΆns badplats","municipality":"Dals-Ed","lat":58.87893,"lon":11.76215},{"id":"SE0A11382000000537","name":"Skrea N, Bryggan/ Bacchus","municipality":"Falkenberg","lat":56.8872,"lon":12.4935},{"id":"SE0A11382000002849","name":"Skrea strand Syd","municipality":"Falkenberg","lat":56.8846,"lon":12.5024},{"id":"SE0110180000004456","name":"Skrubba strandbad, Drevviken","municipality":"Stockholm","lat":59.234,"lon":18.1834},{"id":"SE0A21487000004208","name":"SkrΓ€cklan Barnbadet","municipality":"VΓ€nersborg","lat":58.3855,"lon":12.3217},{"id":"SE0A21487000004209","name":"SkrΓ€cklan Jacobs udde","municipality":"VΓ€nersborg","lat":58.3872,"lon":12.32743},{"id":"SE0A21486000000103","name":"SkurveskΓ€r","municipality":"StrΓΆmstad","lat":58.93482,"lon":11.16572},{"id":"SE0622085000002446","name":"Skuthamn","municipality":"Ludvika","lat":60.15521,"lon":15.18033},{"id":"SE0210319000000770","name":"SkutskΓ€r, Kullen","municipality":"Γlvkarleby","lat":60.62079,"lon":17.43004},{"id":"SE0210319000000769","name":"SkutskΓ€r, Simstadion","municipality":"Γlvkarleby","lat":60.62803,"lon":17.41581},{"id":"SE0A21460000001403","name":"SkΓ₯pafors LaxsjΓΆn","municipality":"Bengtsfors","lat":59.02433,"lon":12.29408},{"id":"SE0441287000000439","name":"SkΓ₯re Skansar","municipality":"Trelleborg","lat":55.37408,"lon":13.06253},{"id":"SE0A21485000000591","name":"SkΓ₯rs camping","municipality":"Uddevalla","lat":58.2963,"lon":11.5346},{"id":"SE0930861000003578","name":"SkΓ€llerydsbadet, AlsterΓ₯n (BlomstermΓ₯la)","municipality":"MΓΆnsterΓ₯s","lat":56.96787,"lon":16.33906},{"id":"SE0A11383000000617","name":"SkΓ€llingesjΓΆn","municipality":"Varberg","lat":57.18093,"lon":12.47609},{"id":"SE0110125000005355","name":"SkΓ€luddens Brygga","municipality":"EkerΓΆ","lat":59.29387,"lon":17.79969},{"id":"SE0A21419000000650","name":"SkΓ€rhamn, GrΓ₯skΓ€r","municipality":"TjΓΆrn","lat":57.98803,"lon":11.53927},{"id":"SE0A21419000004077","name":"SkΓ€rhamn, Kungsviken","municipality":"TjΓΆrn","lat":57.9938,"lon":11.54772},{"id":"SE0A11315000000359","name":"SkΓ€rshults camping","municipality":"Hylte","lat":57.03683,"lon":13.28894},{"id":"SE0411080000000220","name":"SkΓ€rsjΓΆn","municipality":"Karlskrona","lat":56.31739,"lon":15.47348},{"id":"SE0930881000000800","name":"SkΓ€rsjΓΆn","municipality":"Nybro","lat":56.66336,"lon":15.68782},{"id":"SE0910684000002003","name":"SkΓ€rsjΓΆn","municipality":"SΓ€vsjΓΆ","lat":57.34657,"lon":14.74511},{"id":"SE0622085000002468","name":"SkΓ€rsjΓΆn","municipality":"Ludvika","lat":60.04616,"lon":14.50399},{"id":"SE0251904000002596","name":"SkΓ€rsjΓΆn","municipality":"Skinnskatteberg","lat":59.8061,"lon":15.7371},{"id":"SE0910604000000977","name":"SkΓ€rsjΓΆsjΓΆn","municipality":"Aneby","lat":57.82662,"lon":14.78277},{"id":"SE0910617000000936","name":"SkΓ€rvsjΓΆ, SjΓΆbo","municipality":"GnosjΓΆ","lat":57.39442,"lon":13.82012},{"id":"SE0110180000001870","name":"SkΓΆndalsbadet, Drevviken","municipality":"Stockholm","lat":59.2484,"lon":18.12511},{"id":"SE0411080000000204","name":"SkΓΆnstavik","municipality":"Karlskrona","lat":56.20152,"lon":15.60146},{"id":"SE0411081000000162","name":"SkΓΆrjesjΓΆn","municipality":"Ronneby","lat":56.34232,"lon":15.13238},{"id":"SE0411060000000275","name":"SlagesnΓ€ssjΓΆn","municipality":"OlofstrΓΆm","lat":56.35518,"lon":14.53862},{"id":"SE0930880000007020","name":"Slipkajen","municipality":"Kalmar","lat":56.6682,"lon":16.3776},{"id":"SE0940980000000238","name":"Slite strand","municipality":"Gotland","lat":57.7018,"lon":18.8045},{"id":"SE0110138000003943","name":"Slottsbadet","municipality":"TyresΓΆ","lat":59.23326,"lon":18.30701},{"id":"SE0930883000006222","name":"Slottsholmen Myntbryggan","municipality":"VΓ€stervik","lat":57.76237,"lon":16.64174},{"id":"SE0A21421000000135","name":"Slussen","municipality":"Orust","lat":58.26199,"lon":11.76166},{"id":"SE0812421000004207","name":"Slussfors badplats","municipality":"Storuman","lat":65.42587,"lon":16.22587},{"id":"SE0712281000003473","name":"SlΓ€daviken AlnΓΆ","municipality":"Sundsvall","lat":62.4349,"lon":17.4741},{"id":"SE0812418000003417","name":"SlΓ€pptrΓ€sk","municipality":"MalΓ₯","lat":65.14805,"lon":18.58173},{"id":"SE0230581000001743","name":"SlΓ€tbaken, StegeborgsgΓ₯rden","municipality":"NorrkΓΆping","lat":58.4441,"lon":16.6033},{"id":"SE0411081000000157","name":"SlΓ€ttanΓ€s","municipality":"Ronneby","lat":56.17436,"lon":15.3955},{"id":"SE0910684000002001","name":"SlΓ€ttsjΓΆn","municipality":"SΓ€vsjΓΆ","lat":57.36414,"lon":14.46535},{"id":"SE0712262000004081","name":"Smackgrundet","municipality":"TimrΓ₯","lat":62.5052,"lon":17.4518},{"id":"SE0A11384000000450","name":"Smarholmen","municipality":"Kungsbacka","lat":57.4298,"lon":11.9216},{"id":"SE0110180000001845","name":"Smedsuddsbadet V","municipality":"Stockholm","lat":59.3251,"lon":18.0209},{"id":"SE0110180000001846","name":"Smedsuddsbadet Γ","municipality":"Stockholm","lat":59.325,"lon":18.0216},{"id":"SE0A21480000000529","name":"Smithska udden","municipality":"GΓΆteborg","lat":57.6114,"lon":11.89812},{"id":"SE0A21421000000131","name":"SmΓ₯holmarna HenΓ₯ns badplats","municipality":"Orust","lat":58.24626,"lon":11.661},{"id":"SE0441290000000677","name":"Snickarhakens badplats","municipality":"Kristianstad","lat":55.9161,"lon":14.3193},{"id":"SE0712284000003052","name":"SnippersjΓΆns bad, Gerdal","municipality":"ΓrnskΓΆldsvik","lat":63.29033,"lon":18.40075},{"id":"SE0812460000003078","name":"SnΓ₯lltjΓ€rns badplats","municipality":"VΓ€nnΓ€s","lat":63.98848,"lon":19.75007},{"id":"SE0940980000000229","name":"SnΓ€ckviken","municipality":"Gotland","lat":57.6726,"lon":18.3318},{"id":"SE0712281000003479","name":"Sodom Njurunda","municipality":"Sundsvall","lat":62.3526,"lon":17.3738},{"id":"SE0930884000001437","name":"Solaren","municipality":"Vimmerby","lat":57.77277,"lon":15.92872},{"id":"SE0910686000001610","name":"Solgen Mellby","municipality":"EksjΓΆ","lat":57.55544,"lon":15.11364},{"id":"SE0910686000001611","name":"Solgen Sanden","municipality":"EksjΓΆ","lat":57.53497,"lon":15.24091},{"id":"SE0910685000000842","name":"SolgenΓ₯n, Skede","municipality":"Vetlanda","lat":57.46848,"lon":15.17182},{"id":"SE0930884000001438","name":"Solnen","municipality":"Vimmerby","lat":57.58466,"lon":15.9331},{"id":"SE0622082000002421","name":"Solvarbo, DammsjΓΆn","municipality":"SΓ€ter","lat":60.3702,"lon":15.64382},{"id":"SE0A21490000005495","name":"Solvikens badplats, Γrtingen","municipality":"BorΓ₯s","lat":57.83766,"lon":12.9504},{"id":"SE0812418000003415","name":"Solviksbadet","municipality":"MalΓ₯","lat":65.19705,"lon":18.75185},{"id":"SE0110180000001847","name":"Solviksbadet, strandbad, MΓ€laren","municipality":"Stockholm","lat":59.31468,"lon":17.96675},{"id":"SE0110188000002182","name":"SolΓΆbadet","municipality":"NorrtΓ€lje","lat":59.67734,"lon":18.76091},{"id":"SE0230512000001916","name":"Sommen, Asby Sand","municipality":"Ydre","lat":57.92313,"lon":15.20108},{"id":"SE0230560000001270","name":"Sommen, BlΓ₯vik","municipality":"Boxholm","lat":58.06115,"lon":15.0683},{"id":"SE0230560000001271","name":"Sommen, Malexander","municipality":"Boxholm","lat":58.03081,"lon":15.28896},{"id":"SE0230512000001915","name":"Sommen, Norra Vi","municipality":"Ydre","lat":57.88562,"lon":15.35848},{"id":"SE0910687000000862","name":"Sommens badplats","municipality":"TranΓ₯s","lat":58.1396,"lon":14.9767},{"id":"SE0822583000007060","name":"Sorttisviken, SeskarΓΆ","municipality":"Haparanda","lat":65.7502,"lon":23.789},{"id":"SE0241861000002531","name":"Sottern","municipality":"Hallsberg","lat":59.01215,"lon":15.40694},{"id":"SE0241880000002640","name":"Sottern, Solberga","municipality":"Γrebro","lat":59.05272,"lon":15.55089},{"id":"SE0220482000000749","name":"Sparreholmsbadet","municipality":"Flen","lat":59.08261,"lon":16.8408},{"id":"SE0441293000000423","name":"SparrsjΓΆns badplats","municipality":"HΓ€ssleholm","lat":56.2454,"lon":13.7253},{"id":"SE0A21490000003597","name":"SparsΓΆrs badplats, ΓresjΓΆ","municipality":"BorΓ₯s","lat":57.80101,"lon":13.00658},{"id":"SE0712283000007122","name":"Spegeldammen","municipality":"SollefteΓ₯","lat":63.4566,"lon":16.7989},{"id":"SE0910686000001612","name":"Spilhammarbadet","municipality":"EksjΓΆ","lat":57.61716,"lon":15.60274},{"id":"SE0110188000002168","name":"Spillersboda, Spillersbodabade","municipality":"NorrtΓ€lje","lat":59.70283,"lon":18.86358},{"id":"SE0920764000001589","name":"SpΓ₯nen","municipality":"Alvesta","lat":56.91362,"lon":14.59117},{"id":"SE0622080000004243","name":"St Aspan Aspeboda","municipality":"Falun","lat":60.58048,"lon":15.46407},{"id":"SE0A11382000000538","name":"Stafsinge S stranden","municipality":"Falkenberg","lat":56.8958,"lon":12.459},{"id":"SE0220486000001909","name":"StallarholmsfjΓ€rden, Γstabadet","municipality":"StrΓ€ngnΓ€s","lat":59.36673,"lon":17.19944},{"id":"SE0930862000000736","name":"Stampadammen, Johansfors badpl","municipality":"Emmaboda","lat":56.69456,"lon":15.54246},{"id":"SE0812463000003133","name":"StamsjΓΆbadet","municipality":"Γ
sele","lat":64.173,"lon":17.2886},{"id":"SE0220480000002084","name":"StavsjΓΆn, StavsjΓΆbadet","municipality":"NykΓΆping","lat":58.7288,"lon":16.4312},{"id":"SE0910662000001095","name":"StengΓ₯rdshultsbadet","municipality":"Gislaved","lat":57.54904,"lon":13.81155},{"id":"SE0A11380000000366","name":"Steninge","municipality":"Halmstad","lat":56.76483,"lon":12.62906},{"id":"SE0110191000001489","name":"Steningebadet","municipality":"Sigtuna","lat":59.60052,"lon":17.81432},{"id":"SE0441233000005497","name":"Stens brygga","municipality":"Vellinge","lat":55.41673,"lon":12.83106},{"id":"SE0812421000003680","name":"Stensele, Boxan","municipality":"Storuman","lat":65.05437,"lon":17.10278},{"id":"SE0441291000000332","name":"Stenshuvud","municipality":"Simrishamn","lat":55.65604,"lon":14.27665},{"id":"SE0930881000000799","name":"StensjΓΆn","municipality":"Nybro","lat":56.98005,"lon":16.10454},{"id":"SE0A21481000000395","name":"StensjΓΆn, John Halls vΓ€gs badplats","municipality":"MΓΆlndal","lat":57.66278,"lon":12.04793},{"id":"SE0A21481000004065","name":"StensjΓΆn, strandpromenadens badplats","municipality":"MΓΆlndal","lat":57.65767,"lon":12.04363},{"id":"SE0930880000000785","name":"StensΓΆ Flundran","municipality":"Kalmar","lat":56.6474,"lon":16.3314},{"id":"SE0930880000006219","name":"StensΓΆ SΓΆdra udde","municipality":"Kalmar","lat":56.6325,"lon":16.3195},{"id":"SE0110182000001243","name":"StensΓΆbadet","municipality":"Nacka","lat":59.26005,"lon":18.17283},{"id":"SE0220481000001264","name":"Stenvik","municipality":"OxelΓΆsund","lat":58.66338,"lon":17.08634},{"id":"SE0930840000000007","name":"StenΓ₯sa","municipality":"MΓΆrbylΓ₯nga","lat":56.50113,"lon":16.62255},{"id":"SE0632182000002699","name":"StenΓΆ havsbad","municipality":"SΓΆderhamn","lat":61.24692,"lon":17.19394},{"id":"SE0110186000001458","name":"Sticklinge udde, Sandviksbadet","municipality":"LidingΓΆ","lat":59.38953,"lon":18.11165},{"id":"SE0110139000001715","name":"Stigstorp","municipality":"Upplands-Bro","lat":59.49771,"lon":17.77842},{"id":"SE0220480000002212","name":"Stigtomta, BΓ€rstabadet","municipality":"NykΓΆping","lat":58.78802,"lon":16.73899},{"id":"SE0411082000000173","name":"Stilleryd","municipality":"Karlshamn","lat":56.1613,"lon":14.8145},{"id":"SE0220461000001062","name":"StjΓ€rnhov KyrksjΓΆn Dammhagen","municipality":"Gnesta","lat":59.09252,"lon":16.99411},{"id":"SE0220461000001075","name":"StjΓ€rnhov Naten","municipality":"Gnesta","lat":59.0784,"lon":17.0102},{"id":"SE0110125000000966","name":"Stockby","municipality":"EkerΓΆ","lat":59.33184,"lon":17.67719},{"id":"SE0A21419000002833","name":"Stockevik","municipality":"TjΓΆrn","lat":57.9605,"lon":11.5466},{"id":"SE0A21452000001876","name":"StomsjΓΆn","municipality":"Tranemo","lat":57.56249,"lon":13.58452},{"id":"SE0611715000002406","name":"Stor Emsen","municipality":"Kil","lat":59.5463,"lon":13.016},{"id":"SE0241863000003431","name":"Stor-SΓ₯ngen, SΓ₯ngsbadet","municipality":"HΓ€llefors","lat":59.80448,"lon":14.48313},{"id":"SE0A21480000000518","name":"Stora AmundΓΆn","municipality":"GΓΆteborg","lat":57.59415,"lon":11.90383},{"id":"SE0A21491000001015","name":"Stora BjΓΆrken","municipality":"Ulricehamn","lat":57.89008,"lon":13.27552},{"id":"SE0A21480000000390","name":"Stora DelsjΓΆn","municipality":"GΓΆteborg","lat":57.68871,"lon":12.03517},{"id":"SE0A21489000001707","name":"Stora FΓ€rgen, Lygnareds camping","municipality":"AlingsΓ₯s","lat":57.86691,"lon":12.56027},{"id":"SE0910683000001513","name":"Stora GavlΓΆ","municipality":"VΓ€rnamo","lat":57.00666,"lon":13.83164},{"id":"SE0230562000000880","name":"Stora Gryten, BrΓ€nntorp","municipality":"FinspΓ₯ng","lat":58.86563,"lon":16.06947},{"id":"SE0930860000000993","name":"Stora HammarsjΓΆn, Hultsfred","municipality":"Hultsfred","lat":57.4542,"lon":15.7253},{"id":"SE0930881000000810","name":"Stora HindsjΓΆn","municipality":"Nybro","lat":56.93996,"lon":15.90544},{"id":"SE0930884000001429","name":"Stora HolmsjΓΆn","municipality":"Vimmerby","lat":57.65997,"lon":15.59245},{"id":"SE0A21402000000098","name":"Stora HΓ₯lsjΓΆn","municipality":"Partille","lat":57.68904,"lon":12.11898},{"id":"SE0611784000003293","name":"Stora LesjΓΆn","municipality":"Arvika","lat":59.47769,"lon":12.68677},{"id":"SE0230509000004536","name":"Stora Lund","municipality":"ΓdeshΓΆg","lat":58.218,"lon":11.995},{"id":"SE0A21441000001044","name":"Stora LΓΆvsjΓΆn","municipality":"Lerum","lat":57.85732,"lon":12.23776},{"id":"SE0A21480000000386","name":"Stora MΓΆlnesjΓΆn","municipality":"GΓΆteborg","lat":57.80512,"lon":12.07501},{"id":"SE0A21463000005699","name":"Stora NavsjΓΆn","municipality":"Mark","lat":57.4238,"lon":12.4908},{"id":"SE0411060000000278","name":"Stora OredssjΓΆn","municipality":"OlofstrΓΆm","lat":56.26093,"lon":14.54416},{"id":"SE0230580000001768","name":"Stora RΓ€ngen, Bestorp","municipality":"LinkΓΆping","lat":58.23434,"lon":15.72702},{"id":"SE0230580000001770","name":"Stora RΓ€ngen, StavsΓ€tter","municipality":"LinkΓΆping","lat":58.30087,"lon":15.68039},{"id":"SE0930885000000475","name":"Stora RΓΆr","municipality":"Borgholm","lat":56.75526,"lon":16.52683},{"id":"SE0622085000002465","name":"Stora SandsjΓΆn","municipality":"Ludvika","lat":60.18641,"lon":14.62281},{"id":"SE0230512000001914","name":"Stora SundsjΓΆn","municipality":"Ydre","lat":57.86546,"lon":15.25343},{"id":"SE0A21438000000762","name":"Stora SΓ₯gtjΓ€rns badplats","municipality":"Dals-Ed","lat":58.91843,"lon":11.90697},{"id":"SE0A21463000000959","name":"Stora ΓresjΓΆn, Ubbhult","municipality":"Mark","lat":57.5764,"lon":12.3753},{"id":"SE0441278000000146","name":"Storahults strand","municipality":"BΓ₯stad","lat":56.3273,"lon":12.7568},{"id":"SE0812425000002818","name":"StorbΓ€cks badplats","municipality":"Dorotea","lat":64.65272,"lon":15.67729},{"id":"SE0930884000001425","name":"Storebro, Dammen","municipality":"Vimmerby","lat":57.58697,"lon":15.8353},{"id":"SE0A11381000000196","name":"StoresjΓΆ","municipality":"Laholm","lat":56.423,"lon":13.4385},{"id":"SE0910682000002005","name":"StoresjΓΆn, Grimstorps badpl.","municipality":"NΓ€ssjΓΆ","lat":57.5534,"lon":14.69408},{"id":"SE0910682000002006","name":"StoresjΓΆn, Vareviksbadet","municipality":"NΓ€ssjΓΆ","lat":57.51586,"lon":14.68858},{"id":"SE0632104000002787","name":"StorgΓΆsken, RΓ₯backa","municipality":"Hofors","lat":60.51621,"lon":16.39774},{"id":"SE0822581000002737","name":"StorlΓ₯ngtrΓ€sket, LΓ₯ngtrΓ€sk","municipality":"PiteΓ₯","lat":65.38279,"lon":20.34783},{"id":"SE0822580000003238","name":"Storsand","municipality":"LuleΓ₯","lat":65.63248,"lon":22.01604},{"id":"SE0712282000003443","name":"Storsand","municipality":"Kramfors","lat":62.99003,"lon":18.5282},{"id":"SE0632180000004281","name":"Storsand - Konsumbadet - BrΓ€dviken","municipality":"GΓ€vle","lat":60.71556,"lon":17.26221},{"id":"SE0812482000004342","name":"Storsand, Bygdsiljum","municipality":"SkellefteΓ₯","lat":64.37692,"lon":20.48755},{"id":"SE0632184000002998","name":"Storsand, HΓΆlick","municipality":"Hudiksvall","lat":61.6299,"lon":17.4327},{"id":"SE0812409000003115","name":"Storsand, Ratan","municipality":"Robertsfors","lat":64.00199,"lon":20.8959},{"id":"SE0220483000001144","name":"StorsjΓΆn","municipality":"Katrineholm","lat":58.90151,"lon":16.24584},{"id":"SE0A21442000001332","name":"StorsjΓΆn","municipality":"VΓ₯rgΓ₯rda","lat":57.95534,"lon":12.7269},{"id":"SE0712282000003444","name":"StorsjΓΆn","municipality":"Kramfors","lat":62.91975,"lon":18.07167},{"id":"SE0632182000002697","name":"StorsjΓΆn TrΓΆnΓΆ","municipality":"SΓΆderhamn","lat":61.49013,"lon":16.73072},{"id":"SE0910682000002054","name":"StorsjΓΆn, Forserums badpl.","municipality":"NΓ€ssjΓΆ","lat":57.7127,"lon":14.49056},{"id":"SE0722380000003092","name":"StorsjΓΆn, GΓΆviken","municipality":"Γstersund","lat":63.1971,"lon":14.63829},{"id":"SE0722380000003093","name":"StorsjΓΆn, MinnesgΓ€rde","municipality":"Γstersund","lat":63.1598,"lon":14.6546},{"id":"SE0A21490000004361","name":"StorsjΓΆns badplats, Viskafos","municipality":"BorΓ₯s","lat":57.64629,"lon":12.84392},{"id":"SE0622029000004204","name":"StortjΓ€rn","municipality":"Leksand","lat":60.705,"lon":15.0314},{"id":"SE0822514000003186","name":"StortrΓ€sket, GrubbnΓ€sudden","municipality":"Kalix","lat":66.08259,"lon":23.24699},{"id":"SE0812421000003679","name":"Storuman, BadsjΓΆn (badbryggan)","municipality":"Storuman","lat":65.10184,"lon":17.11148},{"id":"SE0822514000003188","name":"StorΓΆns badplats","municipality":"Kalix","lat":65.73331,"lon":23.08268},{"id":"SE0611785000005779","name":"Strandbad, EkenΓ€s","municipality":"SΓ€ffle","lat":58.8946,"lon":13.2326},{"id":"SE0611785000005781","name":"Strandbad, Gaperhult","municipality":"SΓ€ffle","lat":58.9723,"lon":13.2361},{"id":"SE0611785000005780","name":"Strandbad, Γ
kershus","municipality":"SΓ€ffle","lat":58.8964,"lon":13.2507},{"id":"SE0632181000002987","name":"Strandbaden I","municipality":"Sandviken","lat":60.52296,"lon":16.72935},{"id":"SE0632181000002988","name":"Strandbaden II simskola","municipality":"Sandviken","lat":60.52908,"lon":16.73973},{"id":"SE0110138000002111","name":"Strandbadet","municipality":"TyresΓΆ","lat":59.2563,"lon":18.2771},{"id":"SE0230583000001862","name":"Strandbadet, Borensberg","municipality":"Motala","lat":58.55509,"lon":15.27782},{"id":"SE0622034000002692","name":"Strandbadet, OrsasjΓΆn","municipality":"Orsa","lat":61.11913,"lon":14.59462},{"id":"SE0A21494000006960","name":"Strandpromenaden","municipality":"LidkΓΆping","lat":58.5124,"lon":13.1489},{"id":"SE0712262000006341","name":"Strandpromenaden SΓΆrΓ₯ker","municipality":"TimrΓ₯","lat":62.4983,"lon":17.502},{"id":"SE0A21438000000767","name":"Strands badplats","municipality":"Dals-Ed","lat":59.13574,"lon":11.84204},{"id":"SE0220480000002078","name":"Strandstugeviken","municipality":"NykΓΆping","lat":58.7187,"lon":17.0903},{"id":"SE0910642000001971","name":"StrΓ₯ken, MullsjΓΆ Camping","municipality":"MullsjΓΆ","lat":57.8981,"lon":13.8525},{"id":"SE0910680000003492","name":"StrΓ₯kens badplats","municipality":"JΓΆnkΓΆping","lat":57.77243,"lon":13.84231},{"id":"SE0110182000001236","name":"StrΓ₯lsjΓΆbadet","municipality":"Nacka","lat":59.26852,"lon":18.19907},{"id":"SE0910687000000865","name":"StrΓ₯nnasjΓΆn","municipality":"TranΓ₯s","lat":58.05964,"lon":14.96839},{"id":"SE0632182000002701","name":"StrΓ₯tjΓ€ra, Skogsandbadet","municipality":"SΓΆderhamn","lat":61.17156,"lon":16.82768},{"id":"SE0A11383000000642","name":"StrΓ₯valla","municipality":"Varberg","lat":57.29788,"lon":12.16372},{"id":"SE0220486000001903","name":"StrΓ€ngnΓ€sfjΓ€rden, Abborrberget","municipality":"StrΓ€ngnΓ€s","lat":59.38461,"lon":17.0374},{"id":"SE0220486000001892","name":"StrΓ€ngnΓ€sfjΓ€rden, Husbybadet","municipality":"StrΓ€ngnΓ€s","lat":59.42603,"lon":16.95416},{"id":"SE0220486000001894","name":"StrΓ€ngnΓ€sfjΓ€rden, Kungsbergsba","municipality":"StrΓ€ngnΓ€s","lat":59.43936,"lon":16.93892},{"id":"SE0220486000001898","name":"StrΓ€ngnΓ€sfjΓ€rden, Visholmen","municipality":"StrΓ€ngnΓ€s","lat":59.3821,"lon":17.0229},{"id":"SE0210360000005719","name":"StrΓΆmarbadet","municipality":"Tierp","lat":60.28841,"lon":17.6721},{"id":"SE0411080000000202","name":"Studentviken","municipality":"Karlskrona","lat":56.18752,"lon":15.59091},{"id":"SE0411080000000200","name":"Stumholmen","municipality":"Karlskrona","lat":56.15995,"lon":15.60031},{"id":"SE0622029000002435","name":"StyrsjΓΆn","municipality":"Leksand","lat":60.6961,"lon":14.9642},{"id":"SE0A21480000000510","name":"StyrsΓΆ Bratten","municipality":"GΓΆteborg","lat":57.61838,"lon":11.79307},{"id":"SE0A21480000000526","name":"StyrsΓΆ Uttervik","municipality":"GΓΆteborg","lat":57.61123,"lon":11.76016},{"id":"SE0632182000002698","name":"StΓ₯lnΓ€s, Storsands havsbad","municipality":"SΓΆderhamn","lat":61.30838,"lon":17.21402},{"id":"SE0930882000006119","name":"StΓ₯ngehamn, BadvΓ€gen","municipality":"Oskarshamn","lat":57.2298,"lon":16.48},{"id":"SE0622080000002941","name":"StΓ₯ngtjΓ€rn, Falun","municipality":"Falun","lat":60.63099,"lon":15.55599},{"id":"SE0230580000001760","name":"StΓ₯ngΓ₯n Hjulsbro","municipality":"LinkΓΆping","lat":58.37447,"lon":15.71395},{"id":"SE0230580000003563","name":"StΓ₯ngΓ₯n Jakobsdal","municipality":"LinkΓΆping","lat":58.39631,"lon":15.68035},{"id":"SE0230580000001761","name":"StΓ₯ngΓ₯n Johannelund","municipality":"LinkΓΆping","lat":58.39836,"lon":15.66235},{"id":"SE0230580000001762","name":"StΓ₯ngΓ₯n Sturefors","municipality":"LinkΓΆping","lat":58.32423,"lon":15.72562},{"id":"SE0230580000004533","name":"StΓ₯ngΓ₯n VΓ₯rgΓ₯rd","municipality":"LinkΓΆping","lat":58.38797,"lon":15.69098},{"id":"SE0230513000001207","name":"StΓ₯ngΓ₯n, HornΓ₯berg","municipality":"Kinda","lat":57.8986,"lon":15.83471},{"id":"SE0241864000002530","name":"StΓ€lldalen, SΓ₯gdammen","municipality":"Ljusnarsberg","lat":59.9395,"lon":14.92519},{"id":"SE0411082000000172","name":"StΓ€rnΓΆ","municipality":"Karlshamn","lat":56.1454,"lon":14.8384},{"id":"SE0251981000003254","name":"StΓ€vrebadet","municipality":"Sala","lat":59.9124,"lon":16.4007},{"id":"SE0812480000002804","name":"StΓΆcksjΓΆbadet","municipality":"UmeΓ₯","lat":63.7747,"lon":20.19407},{"id":"SE0822505000004044","name":"Suddesjaur","municipality":"Arvidsjaur","lat":65.8981,"lon":19.1053},{"id":"SE0940980000000232","name":"Sudersand","municipality":"Gotland","lat":57.9513,"lon":19.2516},{"id":"SE0A21482000000078","name":"Sundhammar ","municipality":"KungΓ€lv","lat":57.8364,"lon":11.6959},{"id":"SE0A21435000000119","name":"Sundshult","municipality":"Tanum","lat":58.73997,"lon":11.57389},{"id":"SE0611783000002347","name":"SundsjΓΆn","municipality":"Hagfors","lat":59.98423,"lon":13.97521},{"id":"SE0241860000003276","name":"SundsjΓΆn","municipality":"LaxΓ₯","lat":59.07342,"lon":14.64129},{"id":"SE0441280000004499","name":"Sundspromenaden","municipality":"MalmΓΆ","lat":55.6133,"lon":12.9713},{"id":"SE0611780000002380","name":"SundstatjΓ€rn","municipality":"Karlstad","lat":59.38969,"lon":13.51009},{"id":"SE0110140000001345","name":"Sundsviksbadet","municipality":"Nykvarn","lat":59.22563,"lon":17.42875},{"id":"SE0251980000002551","name":"SundΓ€ngen","municipality":"VΓ€sterΓ₯s","lat":59.56643,"lon":16.85933},{"id":"SE0A21461000004470","name":"SunnanΓ₯ Hamn","municipality":"Mellerud","lat":58.70883,"lon":12.50931},{"id":"SE0210380000001165","name":"Sunnerstabadet","municipality":"Uppsala","lat":59.78697,"lon":17.65096},{"id":"SE0A21480000000385","name":"SurtesjΓΆn","municipality":"GΓΆteborg","lat":57.81913,"lon":12.04728},{"id":"SE0A21440000001828","name":"SurtesjΓΆn","municipality":"Ale","lat":57.82891,"lon":12.0336},{"id":"SE0812425000002820","name":"Svanabys badplats","municipality":"Dorotea","lat":64.11395,"lon":16.64836},{"id":"SE0A21421000000132","name":"Svanesund","municipality":"Orust","lat":58.14284,"lon":11.82857},{"id":"SE0822584000003332","name":"Svappavaara","municipality":"Kiruna","lat":67.64802,"lon":21.07193},{"id":"SE0411082000000174","name":"SvartasjΓΆn","municipality":"Karlshamn","lat":56.2186,"lon":14.9623},{"id":"SE0441286000000350","name":"Svarte","municipality":"Ystad","lat":55.42641,"lon":13.71669},{"id":"SE0822582000003222","name":"SvartlΓ₯","municipality":"Boden","lat":66.01172,"lon":21.16477},{"id":"SE0241862000002259","name":"SvartΓ₯badet","municipality":"Degerfors","lat":59.1315,"lon":14.4941},{"id":"SE0230586000001278","name":"SvartΓ₯n, NederlΓΆsabadet","municipality":"MjΓΆlby","lat":58.43556,"lon":15.2488},{"id":"SE0230560000001273","name":"SvartΓ₯n, TimmerΓΆbadet","municipality":"Boxholm","lat":58.1832,"lon":15.05728},{"id":"SE0812418000004270","name":"Svedjan","municipality":"MalΓ₯","lat":65.13983,"lon":18.6892},{"id":"SE0722361000003971","name":"SvegssjΓΆn, dammen","municipality":"HΓ€rjedalen","lat":62.03468,"lon":14.28506},{"id":"SE0A21465000001074","name":"Svenljunga, KolarpssjΓΆn","municipality":"Svenljunga","lat":57.53783,"lon":13.11868},{"id":"SE0A21415000000111","name":"SvenshΓΆgen, HΓ€llungen","municipality":"Stenungsund","lat":58.1292,"lon":11.946},{"id":"SE0910684000001999","name":"SvinasjΓΆn","municipality":"SΓ€vsjΓΆ","lat":57.23704,"lon":14.56147},{"id":"SE0230580000001815","name":"SvinstadsjΓΆn","municipality":"LinkΓΆping","lat":58.37086,"lon":15.83761},{"id":"SE0930880000004434","name":"SvinΓΆ","municipality":"Kalmar","lat":56.6798,"lon":16.3822},{"id":"SE0220480000006199","name":"SvΓ€rdsklova","municipality":"NykΓΆping","lat":58.7264,"lon":17.2436},{"id":"SE0A11380000001292","name":"SvΓ€rjarehΓ₯lan","municipality":"Halmstad","lat":56.6433,"lon":12.7452},{"id":"SE0622039000004527","name":"SΓ₯gdammen, Blyberg","municipality":"Γlvdalen","lat":61.14835,"lon":14.20922},{"id":"SE0110182000001219","name":"SΓ₯gsjΓΆbadet","municipality":"Nacka","lat":59.3447,"lon":18.27916},{"id":"SE0A21460000001405","name":"SΓ₯gudden LelΓ₯ng","municipality":"Bengtsfors","lat":59.03305,"lon":12.21911},{"id":"SE0230561000001214","name":"SΓ₯ken","municipality":"Γ
tvidaberg","lat":58.2415,"lon":16.1926},{"id":"SE0622029000002441","name":"SΓ₯ngen","municipality":"Leksand","lat":60.5848,"lon":14.787},{"id":"SE0722361000003975","name":"SΓ₯ngsjΓΆn, Ytterhogdal","municipality":"HΓ€rjedalen","lat":62.13695,"lon":14.89251},{"id":"SE0622085000002466","name":"SΓ€fssjΓΆn","municipality":"Ludvika","lat":60.14936,"lon":14.41258},{"id":"SE0632104000002789","name":"SΓ€lgsjΓΆn, KalvsnΓ€s","municipality":"Hofors","lat":60.47842,"lon":16.41338},{"id":"SE0A21452000001883","name":"SΓ€msjΓΆn","municipality":"Tranemo","lat":57.62213,"lon":13.36074},{"id":"SE0A21491000001023","name":"SΓ€msjΓΆn Vegby","municipality":"Ulricehamn","lat":57.67787,"lon":13.38427},{"id":"SE0622039000003670","name":"SΓ€rna badplats, SΓ€rnasjΓΆn","municipality":"Γlvdalen","lat":61.69786,"lon":13.14943},{"id":"SE0A11384000000447","name":"SΓ€rΓΆ VΓ€sterskog - SnΓ€ckestrand","municipality":"Kungsbacka","lat":57.5015,"lon":11.927},{"id":"SE0A21401000000055","name":"SΓ€teriet/HΓ€stviken","municipality":"HΓ€rryda","lat":57.65911,"lon":12.08815},{"id":"SE0A21463000000958","name":"SΓ€tila Lygnern","municipality":"Mark","lat":57.535,"lon":12.42902},{"id":"SE0A21463000006360","name":"SΓ€tila, Flohult","municipality":"Mark","lat":57.5221,"lon":12.4417},{"id":"SE0110180000001849","name":"SΓ€trastrandsbadet N, MΓ€laren","municipality":"Stockholm","lat":59.2877,"lon":17.8919},{"id":"SE0110180000001848","name":"SΓ€trastrandsbadet S, MΓ€laren","municipality":"Stockholm","lat":59.2865,"lon":17.891},{"id":"SE0110117000002088","name":"SΓ€tterfjΓ€rden 1, Badholmen","municipality":"ΓsterΓ₯ker","lat":59.4679,"lon":18.3158},{"id":"SE0110117000002089","name":"SΓ€tterfjΓ€rden 2, Fiskartorpet","municipality":"ΓsterΓ₯ker","lat":59.4687,"lon":18.318},{"id":"SE0110114000001602","name":"SΓ€ttrabadet","municipality":"Upplands-VΓ€sby","lat":59.5296,"lon":17.8315},{"id":"SE0611715000002405","name":"SΓ€veln, Boda","municipality":"Kil","lat":59.584,"lon":13.0383},{"id":"SE0611715000003967","name":"SΓ€veln, Γrtetan","municipality":"Kil","lat":59.5934,"lon":13.0132},{"id":"SE0A21441000001040","name":"SΓ€velΓ₯ngen, DrΓ€ngsered","municipality":"Lerum","lat":57.81564,"lon":12.37084},{"id":"SE0A21489000001692","name":"SΓ€velΓ₯ngen, Ingareds badplats","municipality":"AlingsΓ₯s","lat":57.84921,"lon":12.44979},{"id":"SE0A21442000001338","name":"SΓ€ven","municipality":"VΓ₯rgΓ₯rda","lat":57.92501,"lon":12.85847},{"id":"SE0A21452000001884","name":"SΓ€vsjΓΆn","municipality":"Tranemo","lat":57.5805,"lon":13.254},{"id":"SE0920760000007080","name":"SΓ€vsjΓΆstrΓΆms strandbad","municipality":"Uppvidinge","lat":56.9991,"lon":15.4069},{"id":"SE0110188000002163","name":"SΓΆderbykarl, RΓ₯dasjΓΆn","municipality":"NorrtΓ€lje","lat":59.90557,"lon":18.79638},{"id":"SE0110182000006440","name":"SΓΆderbysjΓΆns badplats","municipality":"Nacka","lat":59.2827,"lon":18.1474},{"id":"SE0110186000001464","name":"SΓΆdergarn","municipality":"LidingΓΆ","lat":59.37965,"lon":18.2113},{"id":"SE0210380000001178","name":"SΓΆdersjΓΆbadet","municipality":"Uppsala","lat":59.84826,"lon":18.11922},{"id":"SE0411060000000274","name":"SΓΆdersjΓΆn","municipality":"OlofstrΓΆm","lat":56.35179,"lon":14.59486},{"id":"SE0110120000001676","name":"SΓΆdersved","municipality":"VΓ€rmdΓΆ","lat":59.29368,"lon":18.4224},{"id":"SE0812480000004419","name":"SΓΆdervik","municipality":"UmeΓ₯","lat":64.04301,"lon":20.2501},{"id":"SE0A21481000000399","name":"SΓΆdra BarnsjΓΆns badplats","municipality":"MΓΆlndal","lat":57.57033,"lon":12.12144},{"id":"SE0920780000006481","name":"SΓΆdra BergundasjΓΆn, TorparΓ€ngen","municipality":"VΓ€xjΓΆ","lat":56.8551,"lon":14.8005},{"id":"SE0251980000003949","name":"SΓΆdra BjΓΆrnΓΆn, ABB","municipality":"VΓ€sterΓ₯s","lat":59.54724,"lon":16.64496},{"id":"SE0822582000003210","name":"SΓΆdra BredΓ₯ker","municipality":"Boden","lat":65.90831,"lon":21.30274},{"id":"SE0910662000001099","name":"SΓΆdra GussjΓΆn","municipality":"Gislaved","lat":57.38529,"lon":13.59526},{"id":"SE0722380000004214","name":"SΓΆdra MinnesgΓ€rde","municipality":"Γstersund","lat":63.1584,"lon":14.6578},{"id":"SE0611780000002387","name":"SΓΆdra Mosaren","municipality":"Karlstad","lat":59.50909,"lon":13.96128},{"id":"SE0A11383000000630","name":"SΓΆdra NΓ€s","municipality":"Varberg","lat":57.06614,"lon":12.25891},{"id":"SE0910686000001609","name":"SΓΆdra Rokalven","municipality":"EksjΓΆ","lat":57.67707,"lon":14.9588},{"id":"SE0622080000002942","name":"SΓΆdra VΓ€llan","municipality":"Falun","lat":60.57924,"lon":15.58713},{"id":"SE0241880000002650","name":"SΓΆdra Γ
nnabosjΓΆn","municipality":"Γrebro","lat":59.33784,"lon":14.93515},{"id":"SE0712262000002725","name":"SΓΆdra Γ
stΓΆn","municipality":"TimrΓ₯","lat":62.4126,"lon":17.674},{"id":"SE0110125000000962","name":"SΓΆdran","municipality":"EkerΓΆ","lat":59.26833,"lon":17.78245},{"id":"SE0910604000000978","name":"SΓΆljen","municipality":"Aneby","lat":57.93034,"lon":14.9022},{"id":"SE0910685000000843","name":"SΓΆrasjΓΆn, Korsberga","municipality":"Vetlanda","lat":57.30287,"lon":15.10164},{"id":"SE0822582000003223","name":"SΓΆrbyn","municipality":"Boden","lat":66.05345,"lon":21.78295},{"id":"SE0241863000003564","name":"SΓΆrelgen, Kaffesumpen","municipality":"HΓ€llefors","lat":59.79635,"lon":14.57074},{"id":"SE0632132000003613","name":"SΓΆrfjΓ€rden","municipality":"Nordanstig","lat":62.0261,"lon":17.4223},{"id":"SE0220486000001897","name":"SΓΆrfjΓ€rden, HelgarΓΆ klΓ€pp","municipality":"StrΓ€ngnΓ€s","lat":59.43086,"lon":16.79238},{"id":"SE0220486000001896","name":"SΓΆrfjΓ€rden, HΓ€radsbadet","municipality":"StrΓ€ngnΓ€s","lat":59.37014,"lon":16.89792},{"id":"SE0632132000004774","name":"SΓΆrfjΓ€rden, Varpsand","municipality":"Nordanstig","lat":62.018,"lon":17.4148},{"id":"SE0A21421000000129","name":"SΓΆrkilen, EllΓΆs badplats","municipality":"Orust","lat":58.18186,"lon":11.46531},{"id":"SE0241885000002497","name":"SΓΆrmogen, BraxnΓ€sbadet","municipality":"Lindesberg","lat":59.72186,"lon":15.44437},{"id":"SE0230581000001753","name":"SΓΆrsjΓΆbadet","municipality":"NorrkΓΆping","lat":58.7236,"lon":16.084},{"id":"SE0920764000001593","name":"SΓΆrsjΓΆn","municipality":"Alvesta","lat":57.02334,"lon":14.48286},{"id":"SE0241885000002508","name":"SΓΆrsjΓΆn, MΓ€ssingsviken","municipality":"Lindesberg","lat":59.7706,"lon":15.18132},{"id":"SE0632132000003035","name":"SΓΆrsjΓΆn, StrΓΆmsbruk","municipality":"Nordanstig","lat":61.8856,"lon":17.3005},{"id":"SE0632181000002985","name":"SΓΆrtuttsbadet","municipality":"Sandviken","lat":60.57634,"lon":16.76497},{"id":"SE0A11384000000461","name":"SΓΆrvik","municipality":"Kungsbacka","lat":57.3453,"lon":12.1203},{"id":"SE0241863000004859","name":"SΓΆrΓ€lgens camping","municipality":"HΓ€llefors","lat":59.79103,"lon":14.57255},{"id":"SE0632183000002926","name":"SΓΆrΓ€ng","municipality":"BollnΓ€s","lat":61.33714,"lon":16.21418},{"id":"SE0A21415000000109","name":"SΓΆskΓ€r","municipality":"Stenungsund","lat":58.1118,"lon":11.8332},{"id":"SE0441265000000041","name":"SΓΆvdesjΓΆn","municipality":"SjΓΆbo","lat":55.5887,"lon":13.6667},{"id":"SE0910680000003491","name":"Tahebadet","municipality":"JΓΆnkΓΆping","lat":57.67332,"lon":14.1159},{"id":"SE0A21415000000112","name":"Talbo, HΓ€llungen","municipality":"Stenungsund","lat":58.1159,"lon":11.9154},{"id":"SE0441284000000492","name":"Tallbacken ","municipality":"HΓΆganΓ€s","lat":56.22619,"lon":12.53864},{"id":"SE0812460000003082","name":"Tallbackens stugby","municipality":"VΓ€nnΓ€s","lat":64.0588,"lon":19.6508},{"id":"SE0930880000000790","name":"Tallhagen","municipality":"Kalmar","lat":56.6783,"lon":16.3632},{"id":"SE0411080000000207","name":"Tallholmen","municipality":"Karlskrona","lat":56.18285,"lon":15.5388},{"id":"SE0210382000004508","name":"Tallparksbadet","municipality":"Γsthammar","lat":60.33578,"lon":18.427},{"id":"SE0220482000000747","name":"TallsjΓΆbadet","municipality":"Flen","lat":59.14387,"lon":16.48132},{"id":"SE0611782000002356","name":"Talludden","municipality":"Filipstad","lat":59.72594,"lon":14.15914},{"id":"SE0622026000002617","name":"Tansen","municipality":"Gagnef","lat":60.4164,"lon":14.995},{"id":"SE0622080000004244","name":"Tansen Grycksbo","municipality":"Falun","lat":60.68866,"lon":15.466},{"id":"SE0110180000004021","name":"Tanto, brygga","municipality":"Stockholm","lat":59.3121,"lon":18.037},{"id":"SE0110180000007141","name":"Tanto, strand 1","municipality":"Stockholm","lat":59.3119,"lon":18.0383},{"id":"SE0110180000007142","name":"Tanto, strand 2","municipality":"Stockholm","lat":59.3114,"lon":18.0397},{"id":"SE0A21435000000117","name":"Tanum Strand","municipality":"Tanum","lat":58.67045,"lon":11.26269},{"id":"SE0110140000001344","name":"Taxingebadet","municipality":"Nykvarn","lat":59.24551,"lon":17.30613},{"id":"SE0920780000001542","name":"TegnabysjΓΆn, Sandvik","municipality":"VΓ€xjΓΆ","lat":56.8022,"lon":14.956},{"id":"SE0812404000004574","name":"TegsnΓ€set Avan","municipality":"Vindeln","lat":64.2293,"lon":19.35377},{"id":"SE0910680000003490","name":"Tenhultsbadet","municipality":"JΓΆnkΓΆping","lat":57.69604,"lon":14.33189},{"id":"SE0110187000001351","name":"TenΓΆbadet","municipality":"Vaxholm","lat":59.3936,"lon":18.3311},{"id":"SE0632180000002888","name":"TesteboΓ₯n, Forsby","municipality":"GΓ€vle","lat":60.71699,"lon":17.14208},{"id":"SE0632180000002887","name":"TesteboΓ₯n, SmΓΆrnΓ€s","municipality":"GΓ€vle","lat":60.7694,"lon":16.9281},{"id":"SE0632180000004060","name":"TesteboΓ₯n, StrΓΆmsbrobadet","municipality":"GΓ€vle","lat":60.70428,"lon":17.1562},{"id":"SE0210380000001179","name":"Testenbadet","municipality":"Uppsala","lat":59.93977,"lon":18.07191},{"id":"SE0622021000002826","name":"TicknΓ€svikens badplats","municipality":"Vansbro","lat":60.50873,"lon":13.9903},{"id":"SE0A21498000000892","name":"Tidan, FrΓΆjered","municipality":"Tidaholm","lat":58.24845,"lon":14.01632},{"id":"SE0A21498000000893","name":"Tidan, MadΓ€ngsholm","municipality":"Tidaholm","lat":58.13879,"lon":13.92776},{"id":"SE0930861000000856","name":"Timmernabben, Danska varvet","municipality":"MΓΆnsterΓ₯s","lat":56.96243,"lon":16.44752},{"id":"SE0632121000005760","name":"Timsbo, TimssjΓΆn","municipality":"OvanΓ₯ker","lat":61.42519,"lon":15.71976},{"id":"SE0622062000003929","name":"TingsnΓ€sbadet, Saxviken Siljan","municipality":"Mora","lat":61.00591,"lon":14.56135},{"id":"SE0940980000000230","name":"TingstΓ€de trΓ€sk","municipality":"Gotland","lat":57.7382,"lon":18.626},{"id":"SE0241861000002532","name":"Tisarstrand, Campingen","municipality":"Hallsberg","lat":59.00253,"lon":15.17754},{"id":"SE0230562000000890","name":"Tisnaren, BrenΓ€s","municipality":"FinspΓ₯ng","lat":58.91733,"lon":16.02734},{"id":"SE0230562000000886","name":"Tisnaren, Byle","municipality":"FinspΓ₯ng","lat":58.98126,"lon":15.81473},{"id":"SE0230562000000889","name":"Tisnaren, HΓ€vla","municipality":"FinspΓ₯ng","lat":58.9184,"lon":15.86852},{"id":"SE0241860000003270","name":"Tivedsbadet","municipality":"LaxΓ₯","lat":58.83958,"lon":14.45949},{"id":"SE0230563000001306","name":"Tjen, Ringarum","municipality":"Valdemarsvik","lat":58.33667,"lon":16.39382},{"id":"SE0A11384000000458","name":"TjolΓΆholm","municipality":"Kungsbacka","lat":57.396,"lon":12.1019},{"id":"SE0241880000002655","name":"TjugestasjΓΆn","municipality":"Γrebro","lat":59.37081,"lon":15.38951},{"id":"SE0910685000000844","name":"Tjurken, Bjurvik","municipality":"Vetlanda","lat":57.45314,"lon":14.90516},{"id":"SE0A11380000000372","name":"TjuvahΓ₯lan","municipality":"Halmstad","lat":56.6416,"lon":12.733},{"id":"SE0230583000001861","name":"TjΓ€llmo, SmΓ₯Γ€ngsbadet","municipality":"Motala","lat":58.75055,"lon":15.47006},{"id":"SE0930880000000787","name":"TjΓ€rhovsbadet","municipality":"Kalmar","lat":56.6571,"lon":16.3601},{"id":"SE0622084000002484","name":"TjΓ€rnsjΓΆn","municipality":"Avesta","lat":60.12537,"lon":16.26745},{"id":"SE0622026000002620","name":"TjΓ€rnsjΓΆn","municipality":"Gagnef","lat":60.5926,"lon":15.0725},{"id":"SE0441267000000407","name":"TjΓΆrnarpssjΓΆn","municipality":"HΓΆΓΆr","lat":55.9957,"lon":13.6244},{"id":"SE0441291000000272","name":"Tobisvik, Stranden","municipality":"Simrishamn","lat":55.56489,"lon":14.34236},{"id":"SE0940980000000262","name":"Tofta","municipality":"Gotland","lat":57.4872,"lon":18.1266},{"id":"SE0940980000004794","name":"Toftagropen","municipality":"Gotland","lat":57.53414,"lon":18.19578},{"id":"SE0A11380000000383","name":"ToftasjΓΆn","municipality":"Halmstad","lat":56.70055,"lon":13.00197},{"id":"SE0920780000001561","name":"ToftasjΓΆn, Sandsbro","municipality":"VΓ€xjΓΆ","lat":56.9104,"lon":14.866},{"id":"SE0910682000002043","name":"ToftaΓ₯n, Forsabadet","municipality":"NΓ€ssjΓΆ","lat":57.44977,"lon":14.64181},{"id":"SE0210331000002562","name":"Toften, Ettingsandsbadet","municipality":"Heby","lat":60.25779,"lon":17.21762},{"id":"SE0110115000001994","name":"Toftesta","municipality":"Vallentuna","lat":59.55788,"lon":18.26116},{"id":"SE0910662000001101","name":"Tokarp","municipality":"Gislaved","lat":57.27738,"lon":13.67138},{"id":"SE0A21490000001800","name":"Tolkens badplats","municipality":"BorΓ₯s","lat":57.7577,"lon":13.21776},{"id":"SE0632104000002785","name":"Tolven","municipality":"Hofors","lat":60.57281,"lon":16.26024},{"id":"SE0622061000002663","name":"Tolvsbo, VΓ€stra Sveten","municipality":"Smedjebacken","lat":60.0474,"lon":15.5137},{"id":"SE0812480000004519","name":"Tomtebobadet, Nydala","municipality":"UmeΓ₯","lat":63.81213,"lon":20.34748},{"id":"SE0441278000000136","name":"Torekovs strand","municipality":"BΓ₯stad","lat":56.4296,"lon":12.6308},{"id":"SE0920764000001586","name":"Torne","municipality":"Alvesta","lat":56.6948,"lon":14.60476},{"id":"SE0110120000001663","name":"Torpesand","municipality":"VΓ€rmdΓΆ","lat":59.22151,"lon":18.54985},{"id":"SE0241863000003429","name":"Torrvarpen, Grythyttans badpla","municipality":"HΓ€llefors","lat":59.70158,"lon":14.51739},{"id":"SE0241863000003424","name":"Torrvarpen, Storsand","municipality":"HΓ€llefors","lat":59.71739,"lon":14.48213},{"id":"SE0632104000002790","name":"Torshytte-DammsjΓΆn","municipality":"Hofors","lat":60.43205,"lon":16.39967},{"id":"SE0920780000001551","name":"TorsjΓΆn, Sikabacken (Ingelstad)","municipality":"VΓ€xjΓΆ","lat":56.7563,"lon":14.9046},{"id":"SE0A21441000001045","name":"Torskabotten","municipality":"Lerum","lat":57.81467,"lon":12.43309},{"id":"SE0A11384000000465","name":"Torstensvik","municipality":"Kungsbacka","lat":57.3187,"lon":12.1535},{"id":"SE0632180000004279","name":"Torsved -TesteboΓ₯n","municipality":"GΓ€vle","lat":60.7348,"lon":17.1052},{"id":"SE0411083000004101","name":"TorsΓΆ V.","municipality":"SΓΆlvesborg","lat":56.0048,"lon":14.6364},{"id":"SE0A11380000000380","name":"TostarpasjΓΆn","municipality":"Halmstad","lat":56.6362,"lon":13.076},{"id":"SE0930883000001638","name":"Totebo badplats (NΓ€ssjΓΆn)","municipality":"VΓ€stervik","lat":57.6253,"lon":16.1918},{"id":"SE0632180000002885","name":"Totra","municipality":"GΓ€vle","lat":60.9133,"lon":17.09517},{"id":"SE0920767000000684","name":"Tovhultsbadet","municipality":"Markaryd","lat":56.58909,"lon":13.76424},{"id":"SE0A21484000007540","name":"Trampen","municipality":"Lysekil","lat":58.2681,"lon":11.4366},{"id":"SE0230513000006600","name":"TransjΓΆn","municipality":"Kinda","lat":58.1888,"lon":15.8296},{"id":"SE0A21490000001807","name":"TransΓ₯ssjΓΆns badplats, BorΓ₯s","municipality":"BorΓ₯s","lat":57.68601,"lon":12.94649},{"id":"SE0712281000003474","name":"Tranviken AlnΓΆ","municipality":"Sundsvall","lat":62.3629,"lon":17.5095},{"id":"SE0411083000000664","name":"Tredenborg V","municipality":"SΓΆlvesborg","lat":56.0263,"lon":14.5641},{"id":"SE0611784000003294","name":"Treen","municipality":"Arvika","lat":59.93054,"lon":12.65415},{"id":"SE0230509000001422","name":"TrehΓΆrnabadet","municipality":"ΓdeshΓΆg","lat":58.15302,"lon":14.84221},{"id":"SE0411082000000175","name":"TrehΓΆrnan","municipality":"Karlshamn","lat":56.2596,"lon":15.0214},{"id":"SE0110180000001871","name":"Trekantens strandbad","municipality":"Stockholm","lat":59.3124,"lon":18.0108},{"id":"SE0110138000002112","name":"Trinntorpsbadet","municipality":"TyresΓΆ","lat":59.22779,"lon":18.35742},{"id":"SE0910687000000866","name":"TrollsjΓΆn","municipality":"TranΓ₯s","lat":58.04069,"lon":14.95529},{"id":"SE0210360000004441","name":"TrollsjΓΆn","municipality":"Tierp","lat":60.3751,"lon":17.4715},{"id":"SE0822580000003234","name":"TrolltjΓ€rn","municipality":"LuleΓ₯","lat":65.56642,"lon":22.12199},{"id":"SE0822583000003174","name":"TromsΓΆviken","municipality":"Haparanda","lat":65.7116,"lon":23.7618},{"id":"SE0220488000002073","name":"Trosa Havsbad","municipality":"Trosa","lat":58.87068,"lon":17.57722},{"id":"SE0920780000001559","name":"Trummen","municipality":"VΓ€xjΓΆ","lat":56.8595,"lon":14.8256},{"id":"SE0411080000000216","name":"TrummenΓ€s","municipality":"Karlskrona","lat":56.16036,"lon":15.7269},{"id":"SE0A21490000005979","name":"TrummesjΓΆns badplats","municipality":"BorΓ₯s","lat":57.78241,"lon":12.79617},{"id":"SE0940980000004795","name":"TrΓ€kumlagropen","municipality":"Gotland","lat":57.5818,"lon":18.2976},{"id":"SE0110117000002102","name":"TrΓ€lhavet 3, TrΓ€lhavsbadet","municipality":"ΓsterΓ₯ker","lat":59.4544,"lon":18.3272},{"id":"SE0110117000002090","name":"TrΓ€lhavet, Breviksbadet","municipality":"ΓsterΓ₯ker","lat":59.4555,"lon":18.3594},{"id":"SE0110117000002087","name":"TrΓ€lhavet, ΓsterskΓ€rs havsbad","municipality":"ΓsterΓ₯ker","lat":59.4552,"lon":18.3101},{"id":"SE0A11383000000629","name":"TrΓ€slΓΆvslΓ€ge","municipality":"Varberg","lat":57.06369,"lon":12.27696},{"id":"SE0A21481000000401","name":"TulebosjΓΆns badplats","municipality":"MΓΆlndal","lat":57.60877,"lon":12.08188},{"id":"SE0110127000001387","name":"TullingesjΓΆn, Stendalsbadet","municipality":"Botkyrka","lat":59.22285,"lon":17.87791},{"id":"SE0A21480000000506","name":"Tumlehed","municipality":"GΓΆteborg","lat":57.72827,"lon":11.72035},{"id":"SE0220486000001908","name":"Tunaviksbadet, privat","municipality":"StrΓ€ngnΓ€s","lat":59.36763,"lon":17.26491},{"id":"SE0622083000002605","name":"Turbobadet","municipality":"Hedemora","lat":60.24428,"lon":15.85682},{"id":"SE0910685000000845","name":"Tureforsbadet, Kvillsfors","municipality":"Vetlanda","lat":57.4061,"lon":15.4934},{"id":"SE0920781000001326","name":"TuvesjΓΆn, Stighult","municipality":"Ljungby","lat":56.62623,"lon":13.81177},{"id":"SE0812404000003682","name":"TvΓ€rΓ₯lunds badplats","municipality":"Vindeln","lat":64.1008,"lon":19.68121},{"id":"SE0230584000000724","name":"Tycklinge","municipality":"Vadstena","lat":58.463,"lon":14.8956},{"id":"SE0441256000000292","name":"Tydingen","municipality":"Γstra GΓΆinge","lat":56.25472,"lon":14.03059},{"id":"SE0441256000000291","name":"TydingesjΓΆn, SjΓΆhagen","municipality":"Γstra GΓΆinge","lat":56.27457,"lon":14.00594},{"id":"SE0930883000001636","name":"Tyllinge badplats (Man)","municipality":"VΓ€stervik","lat":58.0237,"lon":16.0718},{"id":"SE0A11380000003942","name":"TylΓΆbΓ€ck","municipality":"Halmstad","lat":56.6537,"lon":12.73011},{"id":"SE0A11380000000371","name":"TylΓΆsand","municipality":"Halmstad","lat":56.6487,"lon":12.7286},{"id":"SE0110187000001352","name":"TynningΓΆ, Myrholmsmaren","municipality":"Vaxholm","lat":59.38405,"lon":18.36719},{"id":"SE0230582000001087","name":"TyrislΓΆt","municipality":"SΓΆderkΓΆping","lat":58.32714,"lon":16.89067},{"id":"SE0A21486000005861","name":"TΓ₯ngen","municipality":"StrΓΆmstad","lat":58.9466,"lon":11.2014},{"id":"SE0A21427000000029","name":"TΓ₯ngen, Kungshamn,","municipality":"SotenΓ€s","lat":58.3537,"lon":11.274},{"id":"SE0A21442000001330","name":"TΓ₯sjΓΆn, Bergstena","municipality":"VΓ₯rgΓ₯rda","lat":58.04873,"lon":12.64979},{"id":"SE0110163000001447","name":"TΓ€bybadet","municipality":"Sollentuna","lat":59.44127,"lon":18.00145},{"id":"SE0812480000002810","name":"TΓ€fteΓ₯ nΓ€rbad","municipality":"UmeΓ₯","lat":63.83109,"lon":20.50099},{"id":"SE0622029000002422","name":"TΓ€llberg","municipality":"Leksand","lat":60.8272,"lon":14.977},{"id":"SE0822583000007062","name":"TΓ€mmilahti, SeskarΓΆ","municipality":"Haparanda","lat":65.7174,"lon":23.6853},{"id":"SE0A21490000001796","name":"TΓ€mta badplats, SΓ€ven","municipality":"BorΓ₯s","lat":57.90143,"lon":12.89432},{"id":"SE0722361000004139","name":"TΓ€ndalssjΓΆn","municipality":"HΓ€rjedalen","lat":62.50866,"lon":12.41103},{"id":"SE0910687000000867","name":"TΓ€renΓ€s","municipality":"TranΓ₯s","lat":57.99599,"lon":14.74159},{"id":"SE0812421000004450","name":"TΓ€rnaby badplats/Solberg","municipality":"Storuman","lat":65.74976,"lon":15.33654},{"id":"SE0930883000001646","name":"TΓ€ttΓΆ Havsbad","municipality":"VΓ€stervik","lat":57.88936,"lon":16.70285},{"id":"SE0920780000001550","name":"TΓ€velsΓ₯ssjΓΆn","municipality":"VΓ€xjΓΆ","lat":56.7752,"lon":14.8253},{"id":"SE0A21443000003858","name":"TΓΆllsjΓΆn","municipality":"Bollebygd","lat":57.7844,"lon":12.6109},{"id":"SE0632183000002927","name":"TΓΆnsen","municipality":"BollnΓ€s","lat":61.1334,"lon":16.5579},{"id":"SE0822514000003914","name":"TΓΆre Γ€lv","municipality":"Kalix","lat":65.91434,"lon":22.63787},{"id":"SE0930862000000731","name":"TΓΆrestorps badplats","municipality":"Emmaboda","lat":56.53836,"lon":15.56793},{"id":"SE0930862000000737","name":"UbbemΓ₯lasjΓΆn, Loafallet","municipality":"Emmaboda","lat":56.64969,"lon":15.55745},{"id":"SE0210360000000985","name":"Ubblixbo","municipality":"Tierp","lat":60.21028,"lon":17.44648},{"id":"SE0822582000003224","name":"Ubbyn","municipality":"Boden","lat":65.88852,"lon":21.94891},{"id":"SE0A21442000001333","name":"UddabosjΓΆn","municipality":"VΓ₯rgΓ₯rda","lat":57.8952,"lon":12.71654},{"id":"SE0910680000003569","name":"Uddebobadet","municipality":"JΓΆnkΓΆping","lat":57.76209,"lon":14.42156},{"id":"SE0230562000000873","name":"Udden, Lotorp","municipality":"FinspΓ₯ng","lat":58.73977,"lon":15.82762},{"id":"SE0A11382000003933","name":"Ugglarp S Vassvik","municipality":"Falkenberg","lat":56.8176,"lon":12.6096},{"id":"SE0A11382000000545","name":"Ugglarps N camping","municipality":"Falkenberg","lat":56.8221,"lon":12.6076},{"id":"SE0210360000000986","name":"Ullfors","municipality":"Tierp","lat":60.2733,"lon":17.43156},{"id":"SE0220486000001902","name":"UlvhΓ€llsfjΓ€rden, LΓΆt strandbad","municipality":"StrΓ€ngnΓ€s","lat":59.35914,"lon":17.06232},{"id":"SE0920764000001584","name":"UlvΓΆ","municipality":"Alvesta","lat":56.59389,"lon":14.64703},{"id":"SE0822582000003225","name":"Unbyn","municipality":"Boden","lat":65.71101,"lon":21.77888},{"id":"SE0A21485000000598","name":"Unda camping","municipality":"Uddevalla","lat":58.32672,"lon":11.82226},{"id":"SE0632183000002928","name":"Undersvik","municipality":"BollnΓ€s","lat":61.61493,"lon":16.33091},{"id":"SE0110181000001341","name":"UnderΓ₯sbadet","municipality":"SΓΆdertΓ€lje","lat":59.26466,"lon":17.53583},{"id":"SE0A11315000000355","name":"Unnen, Unnaryd","municipality":"Hylte","lat":56.95074,"lon":13.53147},{"id":"SE0712262000002724","name":"Unnviken","municipality":"TimrΓ₯","lat":62.4315,"lon":17.6972},{"id":"SE0910682000002014","name":"UppsjΓΆn, SandsjΓΆbadet","municipality":"NΓ€ssjΓΆ","lat":57.47982,"lon":14.77841},{"id":"SE0611783000002338","name":"UppΓ€mten, Risfallet","municipality":"Hagfors","lat":60.14878,"lon":13.80671},{"id":"SE0920763000001132","name":"UrsΓΆbadet","municipality":"Tingsryd","lat":56.63002,"lon":14.85965},{"id":"SE0241884000004954","name":"UskavigΓ₯rden","municipality":"Nora","lat":59.6362,"lon":15.0434},{"id":"SE0241885000002513","name":"Usken, Γskeviksbadet","municipality":"Lindesberg","lat":59.61619,"lon":15.02668},{"id":"SE0930880000007021","name":"Utrustningskajen","municipality":"Kalmar","lat":56.671,"lon":16.376},{"id":"SE0622084000002483","name":"Utsundsbadet","municipality":"Avesta","lat":60.13006,"lon":16.33319},{"id":"SE0411080000000213","name":"Uttorp, SturkΓΆ","municipality":"Karlskrona","lat":56.08633,"lon":15.7042},{"id":"SE0441283000000320","name":"UtvΓ€linge","municipality":"Helsingborg","lat":56.224,"lon":12.78},{"id":"SE0920760000001293","name":"UvasjΓΆn, FrΓΆseke","municipality":"Uppvidinge","lat":56.94966,"lon":15.79825},{"id":"SE0622081000002410","name":"Uvbergsviken","municipality":"BorlΓ€nge","lat":60.47778,"lon":15.56145},{"id":"SE0611783000002340","name":"UvΓ₯n, Gatabadet","municipality":"Hagfors","lat":60.04609,"lon":13.69803},{"id":"SE0812425000004001","name":"V OrmsjΓΆ badplats","municipality":"Dorotea","lat":64.42528,"lon":15.99172},{"id":"SE0A21443000003854","name":"V:a NedsjΓΆn, Nedflo","municipality":"Bollebygd","lat":57.7264,"lon":12.4648},{"id":"SE0622061000002661","name":"Vad, SΓΆdra Barken","municipality":"Smedjebacken","lat":60.0338,"lon":15.628},{"id":"SE0A21482000000092","name":"Vadholmen","municipality":"KungΓ€lv","lat":57.952,"lon":11.7504},{"id":"SE0611730000003519","name":"Vadjungen, Oset","municipality":"Eda","lat":59.70585,"lon":12.08702},{"id":"SE0220482000000746","name":"Vadsbrobadet","municipality":"Flen","lat":58.95954,"lon":16.59728},{"id":"SE0230509000003994","name":"VagnsjΓΆn, Klintens badplats","municipality":"ΓdeshΓΆg","lat":58.11805,"lon":14.79055},{"id":"SE0A21484000000568","name":"Valboholmen","municipality":"Lysekil","lat":58.289,"lon":11.4335},{"id":"SE0910662000001096","name":"Valdshultsbadet","municipality":"Gislaved","lat":57.49465,"lon":13.7578},{"id":"SE0441272000000300","name":"Valjebadet","municipality":"BromΓΆlla","lat":56.06177,"lon":14.54056},{"id":"SE0A11384000000448","name":"Vallda SandΓΆ - Stensvik","municipality":"Kungsbacka","lat":57.4886,"lon":11.9256},{"id":"SE0110115000001985","name":"VallentunasjΓΆn, Gustavsudde","municipality":"Vallentuna","lat":59.52103,"lon":18.06319},{"id":"SE0A11384000000464","name":"Vallersvik","municipality":"Kungsbacka","lat":57.32073,"lon":12.15515},{"id":"SE0210330000003551","name":"Valloxen, Brunnbybadet","municipality":"Knivsta","lat":59.74213,"lon":17.83081},{"id":"SE0210330000001181","name":"Valloxen, SΓ€rstabadet","municipality":"Knivsta","lat":59.7228,"lon":17.80524},{"id":"SE0910684000002002","name":"VallsjΓΆbaden","municipality":"SΓ€vsjΓΆ","lat":57.40291,"lon":14.74449},{"id":"SE0632182000002695","name":"Vallviks havsbad","municipality":"SΓΆderhamn","lat":61.17822,"lon":17.18906},{"id":"SE0110117000002091","name":"ValsjΓΆn","municipality":"ΓsterΓ₯ker","lat":59.4669,"lon":18.3533},{"id":"SE0822582000003226","name":"ValvtrΓ€sk","municipality":"Boden","lat":66.14981,"lon":21.68509},{"id":"SE0A21443000003909","name":"VannasjΓΆn","municipality":"Bollebygd","lat":57.6722,"lon":12.6865},{"id":"SE0230583000001852","name":"Varamobaden 1 sΓΆder","municipality":"Motala","lat":58.54919,"lon":15.00483},{"id":"SE0230583000001854","name":"Varamobaden 2 norr (Skogsborg)","municipality":"Motala","lat":58.54919,"lon":15.00483},{"id":"SE0622080000004246","name":"Varpan Falun","municipality":"Falun","lat":60.62896,"lon":15.614},{"id":"SE0632132000003032","name":"VattlΓ₯ngsjΓΆn, Liabadet","municipality":"Nordanstig","lat":61.90678,"lon":17.10938},{"id":"SE0441267000003910","name":"VaxsjΓΆn","municipality":"HΓΆΓΆr","lat":55.9702,"lon":13.5218},{"id":"SE0220483000001140","name":"Veckeln","municipality":"Katrineholm","lat":59.00938,"lon":16.5161},{"id":"SE0241882000002522","name":"Veden","municipality":"Askersund","lat":58.95148,"lon":14.88258},{"id":"SE0241885000002500","name":"VedevΓ₯gssjΓΆn, HerrgΓ₯rdsbadet","municipality":"Lindesberg","lat":59.52371,"lon":15.27458},{"id":"SE0441292000000338","name":"Vejbystrand N","municipality":"Γngelholm","lat":56.31556,"lon":12.76254},{"id":"SE0441292000000339","name":"Vejbystrand S","municipality":"Γngelholm","lat":56.3097,"lon":12.76188},{"id":"SE0110182000001170","name":"Velamsundsbadet","municipality":"Nacka","lat":59.34512,"lon":18.32754},{"id":"SE0611737000003317","name":"Velen, Pankland","municipality":"Torsby","lat":60.19108,"lon":12.88762},{"id":"SE0722361000004307","name":"Veman, VemhΓ₯n","municipality":"HΓ€rjedalen","lat":62.30999,"lon":14.02605},{"id":"SE0722361000004232","name":"Vemdalscampen, vemdalen","municipality":"HΓ€rjedalen","lat":62.51451,"lon":13.82491},{"id":"SE0441257000000432","name":"VemmentorpssjΓΆns badplats","municipality":"Γrkelljunga","lat":56.33599,"lon":13.32802},{"id":"SE0611737000003320","name":"Verkogos","municipality":"Torsby","lat":60.38705,"lon":12.61078},{"id":"SE0930860000000991","name":"VersjΓΆn, Vena","municipality":"Hultsfred","lat":57.4906,"lon":16.0313},{"id":"SE0441273000000001","name":"VesljungasjΓΆn","municipality":"Osby","lat":56.4214,"lon":13.766},{"id":"SE0632184000004437","name":"Vesslabadet, Friggesund","municipality":"Hudiksvall","lat":61.8972,"lon":16.5636},{"id":"SE0632183000002929","name":"Vevlinge","municipality":"BollnΓ€s","lat":61.34709,"lon":16.4292},{"id":"SE0712282000003447","name":"VeΓ₯sand","municipality":"Kramfors","lat":63.06493,"lon":18.3646},{"id":"SE0220428000001527","name":"Vialabadet","municipality":"VingΓ₯ker","lat":59.03796,"lon":15.98439},{"id":"SE0A11383000004398","name":"Videberg","municipality":"Varberg","lat":57.2501,"lon":12.1063},{"id":"SE0920781000001323","name":"VidΓΆstern, Sundet","municipality":"Ljungby","lat":56.97968,"lon":13.98636},{"id":"SE0712281000003469","name":"Vigge StΓΆde","municipality":"Sundsvall","lat":62.3037,"lon":16.6886},{"id":"SE0230580000001765","name":"Viggebysand, JΓ€rnlunden Viggeby","municipality":"LinkΓΆping","lat":58.20211,"lon":15.6416},{"id":"SE0251981000003260","name":"Viggenbadet","municipality":"Sala","lat":59.9982,"lon":16.3462},{"id":"SE0622080000002944","name":"Vika Strand","municipality":"Falun","lat":60.48427,"lon":15.7451},{"id":"SE0910617000000937","name":"VikaresjΓΆn","municipality":"GnosjΓΆ","lat":57.41592,"lon":13.6267},{"id":"SE0A21485000000588","name":"Viken","municipality":"Uddevalla","lat":58.38119,"lon":11.63095},{"id":"SE0A21473000001978","name":"Viken, Beatebergs badplats","municipality":"TΓΆreboda","lat":58.59287,"lon":14.26226},{"id":"SE0441284000000484","name":"Viken, GrΓ€sa brygga","municipality":"HΓΆganΓ€s","lat":56.13957,"lon":12.58382},{"id":"SE0A21446000001392","name":"Viken, Skarven","municipality":"Karlsborg","lat":58.67223,"lon":14.34529},{"id":"SE0A21473000001979","name":"Viken, Γ
sens badplats","municipality":"TΓΆreboda","lat":58.63895,"lon":14.24145},{"id":"SE0441284000000485","name":"Vikens havsbad","municipality":"HΓΆganΓ€s","lat":56.1518,"lon":12.5667},{"id":"SE0241884000003206","name":"Vikern, KΓ€ppsta","municipality":"Nora","lat":59.49959,"lon":14.95698},{"id":"SE0A21443000003857","name":"Vikhult, ViaredssjΓΆn","municipality":"Bollebygd","lat":57.6957,"lon":12.7398},{"id":"SE0441261000000074","name":"VikhΓΆgs hamn","municipality":"KΓ€vlinge","lat":55.72858,"lon":12.95778},{"id":"SE0441283000000316","name":"Vikingstrand","municipality":"Helsingborg","lat":56.0713,"lon":12.66825},{"id":"SE0622083000002603","name":"Vikmanshyttebadet","municipality":"Hedemora","lat":60.29514,"lon":15.81637},{"id":"SE0441291000004311","name":"Viks fiskelΓ€ge","municipality":"Simrishamn","lat":55.60951,"lon":14.29836},{"id":"SE0712280000003734","name":"ViksjΓΆ badplats","municipality":"HΓ€rnΓΆsand","lat":62.7902,"lon":17.4214},{"id":"SE0241882000002517","name":"ViksjΓΆn","municipality":"Askersund","lat":58.80742,"lon":15.10097},{"id":"SE0611785000005134","name":"Vildmarken (FinnsjΓΆn)","municipality":"SΓ€ffle","lat":59.3261,"lon":12.6355},{"id":"SE0A11380000000369","name":"VilshΓ€rad","municipality":"Halmstad","lat":56.6966,"lon":12.6838},{"id":"SE0A21440000001826","name":"VimmersjΓΆn","municipality":"Ale","lat":57.88789,"lon":12.07745},{"id":"SE0812422000003147","name":"VindelΓ€lven, Skibbekudden","municipality":"Sorsele","lat":65.57839,"lon":17.49724},{"id":"SE0A21495000000807","name":"VingsjΓΆbadet","municipality":"Skara","lat":58.38323,"lon":13.58032},{"id":"SE0110180000006741","name":"Vinterviksbadet","municipality":"Stockholm","lat":59.3113,"lon":17.9878},{"id":"SE0910604000000979","name":"ViredaholmssjΓΆn","municipality":"Aneby","lat":57.9113,"lon":14.65721},{"id":"SE0920765000001662","name":"VirestadsjΓΆn, VΓ€lje badplats","municipality":"Γlmhult","lat":56.60566,"lon":14.32586},{"id":"SE0251907000002867","name":"Virsbobadet","municipality":"Surahammar","lat":59.87013,"lon":16.05862},{"id":"SE0930860000001003","name":"VirserumssjΓΆn Kaffeberget","municipality":"Hultsfred","lat":57.3132,"lon":15.5791},{"id":"SE0940980000000226","name":"Visby Havsbad","municipality":"Gotland","lat":57.6409,"lon":18.2868},{"id":"SE0A21452000001886","name":"Visen","municipality":"Tranemo","lat":57.43603,"lon":13.26157},{"id":"SE0230509000001418","name":"VisjΓΆbadet","municipality":"ΓdeshΓΆg","lat":58.19517,"lon":14.67272},{"id":"SE0812463000003143","name":"Viska","municipality":"Γ
sele","lat":64.07634,"lon":18.41831},{"id":"SE0611781000002298","name":"Vismen, GrΓ€sviksbadet","municipality":"Kristinehamn","lat":59.2482,"lon":14.2634},{"id":"SE0220486000004554","name":"Visnaren, Forsabadet","municipality":"StrΓ€ngnΓ€s","lat":59.24947,"lon":17.10355},{"id":"SE0611763000002284","name":"Visten","municipality":"Forshaga","lat":59.6267,"lon":13.4225},{"id":"SE0611766000002370","name":"Visten, Lilla BonΓ€s","municipality":"Sunne","lat":59.6709,"lon":13.32045},{"id":"SE0110120000001664","name":"Vita grindarna, DjurΓΆ Havsbad","municipality":"VΓ€rmdΓΆ","lat":59.3113,"lon":18.6924},{"id":"SE0930880000000781","name":"Vita Sand","municipality":"Kalmar","lat":56.5683,"lon":16.2285},{"id":"SE0920765000001659","name":"VitasjΓΆn, VΓ€gla badplats","municipality":"Γlmhult","lat":56.48044,"lon":13.81699},{"id":"SE0441291000000284","name":"VitemΓΆlla","municipality":"Simrishamn","lat":55.70218,"lon":14.20504},{"id":"SE0622080000002943","name":"Vitsand Sundborn","municipality":"Falun","lat":60.65566,"lon":15.80026},{"id":"SE0822584000003334","name":"Vittangi","municipality":"Kiruna","lat":67.67734,"lon":21.6606},{"id":"SE0210331000002560","name":"Vittinge, Tenabadet","municipality":"Heby","lat":59.88564,"lon":17.09196},{"id":"SE0110182000001220","name":"VittjΓ€rnsbadet","municipality":"Nacka","lat":59.3416,"lon":18.25344},{"id":"SE0822514000003193","name":"VittrΓ€sket","municipality":"Kalix","lat":65.83458,"lon":22.64232},{"id":"SE0220481000001267","name":"Vivesta","municipality":"OxelΓΆsund","lat":58.69069,"lon":17.09424},{"id":"SE0712262000002726","name":"VivstavarvstjΓ€rn","municipality":"TimrΓ₯","lat":62.4988,"lon":17.3452},{"id":"SE0822584000003689","name":"Vivungi","municipality":"Kiruna","lat":67.74265,"lon":22.16254},{"id":"SE0712260000003015","name":"Vojen, Γ
nge","municipality":"Γ
nge","lat":62.51763,"lon":15.65943},{"id":"SE0632121000002719","name":"Voxnan, Kvarnsveden","municipality":"OvanΓ₯ker","lat":61.34376,"lon":16.05313},{"id":"SE0632121000004695","name":"Voxnan, Runemo","municipality":"OvanΓ₯ker","lat":61.35811,"lon":16.14491},{"id":"SE0A21496000001684","name":"Vristulven, Meybobadet","municipality":"SkΓΆvde","lat":58.56526,"lon":13.7465},{"id":"SE0A21480000004516","name":"VrΓ₯ngΓΆ Mittvik","municipality":"GΓΆteborg","lat":57.57112,"lon":11.79239},{"id":"SE0822584000003333","name":"VuontisjΓ€rvi, Karesuando","municipality":"Kiruna","lat":68.37926,"lon":22.84305},{"id":"SE0930880000004488","name":"VΓ₯gbrytarbadet","municipality":"Kalmar","lat":56.6637,"lon":16.3734},{"id":"SE0622062000002669","name":"VΓ₯mΓ₯badet, OrsasjΓΆn","municipality":"Mora","lat":61.11558,"lon":14.49247},{"id":"SE0930882000001968","name":"VΓ₯nevik","municipality":"Oskarshamn","lat":57.1795,"lon":16.4606},{"id":"SE0712281000003470","name":"VΓ₯ngen StΓΆde","municipality":"Sundsvall","lat":62.4157,"lon":16.5755},{"id":"SE0241862000002272","name":"VΓ₯rbobadet","municipality":"Degerfors","lat":59.0773,"lon":14.3382},{"id":"SE0110126000002126","name":"VΓ₯rbyfjΓ€rden, VΓ₯rbybadet","municipality":"Huddinge","lat":59.2577,"lon":17.8793},{"id":"SE0210380000001183","name":"VΓ₯rdsΓ€trabadet","municipality":"Uppsala","lat":59.79216,"lon":17.61369},{"id":"SE0110182000001229","name":"VΓ₯rgΓ€rdsbadet","municipality":"Nacka","lat":59.26483,"lon":18.29967},{"id":"SE0632180000002894","name":"VΓ₯rvik","municipality":"GΓ€vle","lat":60.66582,"lon":17.3035},{"id":"SE0110188000002174","name":"VΓ€ddΓΆ, Sandvikens Camping","municipality":"NorrtΓ€lje","lat":59.98001,"lon":18.87927},{"id":"SE0411082000006260","name":"VΓ€gga havsbad","municipality":"Karlshamn","lat":56.1608,"lon":14.8787},{"id":"SE0930821000001939","name":"VΓ€lenbadet","municipality":"HΓΆgsby","lat":57.15544,"lon":15.6689},{"id":"SE0611730000003521","name":"VΓ€llen, HΓ€ljeboda","municipality":"Eda","lat":59.96737,"lon":12.40236},{"id":"SE0210380000001184","name":"VΓ€llen, MΓ₯viksbadet","municipality":"Uppsala","lat":60.01672,"lon":18.33458},{"id":"SE0210382000004507","name":"VΓ€llenbadet","municipality":"Γsthammar","lat":60.0853,"lon":18.28996},{"id":"SE0A21401000000054","name":"VΓ€llsjΓΆn","municipality":"HΓ€rryda","lat":57.6479,"lon":12.0877},{"id":"SE0A21441000001046","name":"VΓ€llsjΓΆn","municipality":"Lerum","lat":57.76039,"lon":12.34575},{"id":"SE0822582000003229","name":"VΓ€ndtrΓ€sk","municipality":"Boden","lat":65.76004,"lon":21.49026},{"id":"SE0611780000002384","name":"VΓ€nern Alster","municipality":"Karlstad","lat":59.39333,"lon":13.61724},{"id":"SE0611780000002377","name":"VΓ€nern Bomstad","municipality":"Karlstad","lat":59.3617,"lon":13.35366},{"id":"SE0611780000002381","name":"VΓ€nern Orrleken","municipality":"Karlstad","lat":59.3733,"lon":13.49712},{"id":"SE0611780000002386","name":"VΓ€nern S Bottenviken","municipality":"Karlstad","lat":59.3601,"lon":13.8408},{"id":"SE0611780000002378","name":"VΓ€nern Skutberget","municipality":"Karlstad","lat":59.37049,"lon":13.39174},{"id":"SE0611780000002376","name":"VΓ€nern Torsviken","municipality":"Karlstad","lat":59.33298,"lon":13.26082},{"id":"SE0611780000002385","name":"VΓ€nern KungsnΓ€s","municipality":"Karlstad","lat":59.41572,"lon":13.69507},{"id":"SE0A21492000004614","name":"VΓ€nern vid Kulturmagasinet (Gamla kyrkan)","municipality":"Γ
mΓ₯l","lat":59.0528,"lon":12.71091},{"id":"SE0A21471000001027","name":"VΓ€nern, Γ
rnΓ€s Jutaviken","municipality":"GΓΆtene","lat":58.68758,"lon":13.62183},{"id":"SE0A21493000001924","name":"VΓ€nern, Askevik","municipality":"Mariestad","lat":58.88709,"lon":14.01014},{"id":"SE0611781000002288","name":"VΓ€nern, BengtsgΓ₯rd","municipality":"Kristinehamn","lat":59.0943,"lon":14.1027},{"id":"SE0611780000003824","name":"VΓ€nern, BjΓΆrnrukan","municipality":"Karlstad","lat":59.32815,"lon":13.80769},{"id":"SE0A21471000001029","name":"VΓ€nern, Blomberg","municipality":"GΓΆtene","lat":58.54795,"lon":13.31234},{"id":"SE0611761000006640","name":"VΓ€nern, BΓ€rstad","municipality":"HammarΓΆ","lat":59.3371,"lon":13.5425},{"id":"SE0A21493000001925","name":"VΓ€nern, Ekudden","municipality":"Mariestad","lat":58.71605,"lon":13.79888},{"id":"SE0A21494000000916","name":"VΓ€nern, FilsbΓ€ck","municipality":"LidkΓΆping","lat":58.49588,"lon":13.24586},{"id":"SE0611761000004373","name":"VΓ€nern, FjΓ€rdestrand","municipality":"HammarΓΆ","lat":59.34052,"lon":13.42918},{"id":"SE0A21494000002854","name":"VΓ€nern, FramnΓ€s camping","municipality":"LidkΓΆping","lat":58.51529,"lon":13.14394},{"id":"SE0A21487000001890","name":"VΓ€nern, Gaddesanna","municipality":"VΓ€nersborg","lat":58.3921,"lon":12.4939},{"id":"SE0611761000002328","name":"VΓ€nern, Getingberget","municipality":"HammarΓΆ","lat":59.29635,"lon":13.56262},{"id":"SE0611781000007300","name":"VΓ€nern, HerrgΓ₯rdscampingen","municipality":"Kristinehamn","lat":59.3069,"lon":14.0631},{"id":"SE0611781000002289","name":"VΓ€nern, Hult","municipality":"Kristinehamn","lat":59.2107,"lon":14.0709},{"id":"SE0611781000002290","name":"VΓ€nern, Jutviken","municipality":"Kristinehamn","lat":59.2717,"lon":14.0692},{"id":"SE0A21471000001026","name":"VΓ€nern, Kinnekulle Camping","municipality":"GΓΆtene","lat":58.62384,"lon":13.38252},{"id":"SE0A21471000001030","name":"VΓ€nern, KΓ€llby","municipality":"GΓΆtene","lat":58.51985,"lon":13.28996},{"id":"SE0611761000004374","name":"VΓ€nern, LillΓ€ngsviken","municipality":"HammarΓΆ","lat":59.31741,"lon":13.4598},{"id":"SE0A21494000000921","name":"VΓ€nern, LΓ€ckΓΆ","municipality":"LidkΓΆping","lat":58.67286,"lon":13.21831},{"id":"SE0611761000004376","name":"VΓ€nern, LΓΆvnΓ€s","municipality":"HammarΓΆ","lat":59.3379,"lon":13.5366},{"id":"SE0611761000002325","name":"VΓ€nern, MΓΆrudden","municipality":"HammarΓΆ","lat":59.31093,"lon":13.49505},{"id":"SE0A21487000001889","name":"VΓ€nern, Nordkroken","municipality":"VΓ€nersborg","lat":58.3864,"lon":12.4092},{"id":"SE0A21461000001048","name":"VΓ€nern, NΓ€s sandar","municipality":"Mellerud","lat":58.65799,"lon":12.56032},{"id":"SE0A21447000000819","name":"VΓ€nern, OtterbΓ€cken, Barfoten","municipality":"GullspΓ₯ng","lat":58.93355,"lon":14.02597},{"id":"SE0611781000002292","name":"VΓ€nern, Revsand","municipality":"Kristinehamn","lat":59.223,"lon":14.0822},{"id":"SE0611761000002329","name":"VΓ€nern, Rud","municipality":"HammarΓΆ","lat":59.3117,"lon":13.5944},{"id":"SE0611761000002327","name":"VΓ€nern, RΓ€ggΓ₯rdsviken","municipality":"HammarΓΆ","lat":59.27863,"lon":13.57081},{"id":"SE0A21494000000922","name":"VΓ€nern, SandbΓ€cken","municipality":"LidkΓΆping","lat":58.50085,"lon":13.19471},{"id":"SE0611781000002293","name":"VΓ€nern, Sandvikarna norra","municipality":"Kristinehamn","lat":59.2682,"lon":14.0435},{"id":"SE0611781000007220","name":"VΓ€nern, Sandvikarna sΓΆdra","municipality":"Kristinehamn","lat":59.2671,"lon":14.0411},{"id":"SE0A21494000000923","name":"VΓ€nern, Sandviken","municipality":"LidkΓΆping","lat":58.56877,"lon":12.97915},{"id":"SE0A21493000001927","name":"VΓ€nern, Sandviken","municipality":"Mariestad","lat":58.69939,"lon":13.75302},{"id":"SE0A21487000001887","name":"VΓ€nern, Sikhall","municipality":"VΓ€nersborg","lat":58.4913,"lon":12.4356},{"id":"SE0A21493000001928","name":"VΓ€nern, SjΓΆtorp GΓ€sthamnen","municipality":"Mariestad","lat":58.83861,"lon":13.97263},{"id":"SE0611781000002295","name":"VΓ€nern, Skymningen","municipality":"Kristinehamn","lat":59.3028,"lon":14.0592},{"id":"SE0A21493000001929","name":"VΓ€nern, Snapen","municipality":"Mariestad","lat":58.7404,"lon":13.86398},{"id":"SE0611780000003825","name":"VΓ€nern, Strandvik","municipality":"Karlstad","lat":59.38491,"lon":13.73395},{"id":"SE0A21494000000926","name":"VΓ€nern, SvalnΓ€s","municipality":"LidkΓΆping","lat":58.55934,"lon":12.93924},{"id":"SE0A21493000001930","name":"VΓ€nern, TorsΓΆ Hattarevik","municipality":"Mariestad","lat":58.787,"lon":13.7396},{"id":"SE0A21493000001931","name":"VΓ€nern, TorsΓΆ Sandvik","municipality":"Mariestad","lat":58.8299,"lon":13.78624},{"id":"SE0A21494000003901","name":"VΓ€nern, Truve","municipality":"LidkΓΆping","lat":58.49692,"lon":13.26079},{"id":"SE0611761000004375","name":"VΓ€nern, TynΓ€sudden","municipality":"HammarΓΆ","lat":59.35689,"lon":13.56559},{"id":"SE0A21492000000941","name":"VΓ€nern, TΓΆsse badplats","municipality":"Γ
mΓ₯l","lat":58.96384,"lon":12.66726},{"id":"SE0A21487000001888","name":"VΓ€nern, Ursand","municipality":"VΓ€nersborg","lat":58.4175,"lon":12.3247},{"id":"SE0A21494000000947","name":"VΓ€nern, Villabadet","municipality":"LidkΓΆping","lat":58.52896,"lon":13.13346},{"id":"SE0A21461000001047","name":"VΓ€nern, Vita sannar","municipality":"Mellerud","lat":58.68665,"lon":12.52142},{"id":"SE0A21492000000940","name":"VΓ€nern, VΓ€nersvik","municipality":"Γ
mΓ₯l","lat":59.07036,"lon":12.70725},{"id":"SE0611761000002326","name":"VΓ€nern, VΓ€stra Skagene","municipality":"HammarΓΆ","lat":59.29658,"lon":13.50056},{"id":"SE0611761000004371","name":"VΓ€nern, VΓ€stra SΓΆΓΆn","municipality":"HammarΓΆ","lat":59.29719,"lon":13.47946},{"id":"SE0A21492000000938","name":"VΓ€nern, ΓrnΓ€s vid Simstadion","municipality":"Γ
mΓ₯l","lat":59.04329,"lon":12.72103},{"id":"SE0110188000002173","name":"VΓ€ngsjΓΆn","municipality":"NorrtΓ€lje","lat":59.77365,"lon":18.12385},{"id":"SE0241885000002511","name":"VΓ€ringen, PrΓ€stryggen","municipality":"Lindesberg","lat":59.45676,"lon":15.3742},{"id":"SE0611784000003295","name":"VΓ€rmeln Edane","municipality":"Arvika","lat":59.62485,"lon":12.83449},{"id":"SE0611784000003301","name":"VΓ€rmeln Γ
rnΓ€s","municipality":"Arvika","lat":59.6583,"lon":12.92471},{"id":"SE0611764000002877","name":"VΓ€rmeln, LΓ₯ngeudde","municipality":"Grums","lat":59.44594,"lon":12.90665},{"id":"SE0611785000003581","name":"VΓ€rmlands NysΓ€ter","municipality":"SΓ€ffle","lat":59.28598,"lon":12.7834},{"id":"SE0910685000000846","name":"VΓ€rnen, Trollebo","municipality":"Vetlanda","lat":57.30892,"lon":15.28761},{"id":"SE0441257000004734","name":"VΓ€rsjΓΆ badplats","municipality":"Γrkelljunga","lat":56.32138,"lon":13.48894},{"id":"SE0930880000000794","name":"VΓ€rsnΓ€s","municipality":"Kalmar","lat":56.7199,"lon":16.3713},{"id":"SE0110162000002332","name":"VΓ€rtan, Germaniaviken","municipality":"Danderyd","lat":59.40051,"lon":18.10067},{"id":"SE0110162000001093","name":"VΓ€rtan, Svanholmen","municipality":"Danderyd","lat":59.37952,"lon":18.08248},{"id":"SE0622085000002447","name":"VΓ€sman, HΓ€llarna","municipality":"Ludvika","lat":60.14616,"lon":15.1643},{"id":"SE0622029000002436","name":"VΓ€stannorstjΓ€rn","municipality":"Leksand","lat":60.6869,"lon":15.0575},{"id":"SE0712281000003460","name":"VΓ€stbyn Indals-Liden","municipality":"Sundsvall","lat":62.6636,"lon":16.6165},{"id":"SE0441292000000335","name":"VΓ€stersjΓΆn","municipality":"Γngelholm","lat":56.32988,"lon":12.99964},{"id":"SE0910680000003486","name":"VΓ€stersjΓΆn","municipality":"JΓΆnkΓΆping","lat":57.76426,"lon":14.07916},{"id":"SE0251983000002582","name":"VΓ€stlandasjΓΆn, SundΓ€nge","municipality":"KΓΆping","lat":59.54633,"lon":15.76625},{"id":"SE0A21481000000400","name":"VΓ€stra IngsjΓΆns badplats","municipality":"MΓΆlndal","lat":57.60773,"lon":12.26561},{"id":"SE0622083000002611","name":"VΓ€stra LΓ₯ngen, GustavsnΓ€sbadet","municipality":"Hedemora","lat":60.45802,"lon":16.05459},{"id":"SE0712281000003453","name":"VΓ€stra LΓΆvsjΓΆn","municipality":"Sundsvall","lat":62.4703,"lon":16.9674},{"id":"SE0A21401000000061","name":"VΓ€stra NedsjΓΆn","municipality":"HΓ€rryda","lat":57.70832,"lon":12.45681},{"id":"SE0611765000002302","name":"VΓ€stra Silen, Badbarna","municipality":"Γ
rjΓ€ng","lat":59.35219,"lon":12.11244},{"id":"SE0611763000002285","name":"VΓ€stra Γrten","municipality":"Forshaga","lat":59.6398,"lon":13.5603},{"id":"SE0A21446000001383","name":"VΓ€ttern 7, VΓ€ttersvallen","municipality":"Karlsborg","lat":58.42869,"lon":14.40674},{"id":"SE0910643000003989","name":"VΓ€ttern Baskarp","municipality":"Habo","lat":58.0209,"lon":14.1783},{"id":"SE0A21497000001191","name":"VΓ€ttern, Campingen","municipality":"Hjo","lat":58.3088,"lon":14.3068},{"id":"SE0A21446000001378","name":"VΓ€ttern, DjΓ€knasundet","municipality":"Karlsborg","lat":58.63009,"lon":14.56484},{"id":"SE0910643000000717","name":"VΓ€ttern, Domsand","municipality":"Habo","lat":57.8786,"lon":14.1173},{"id":"SE0A21446000001379","name":"VΓ€ttern, Granvik","municipality":"Karlsborg","lat":58.63037,"lon":14.54761},{"id":"SE0A21497000001190","name":"VΓ€ttern, Guldkroken","municipality":"Hjo","lat":58.304,"lon":14.2951},{"id":"SE0A21446000001380","name":"VΓ€ttern, Hanken","municipality":"Karlsborg","lat":58.55546,"lon":14.50821},{"id":"SE0230509000001419","name":"VΓ€ttern, HΓ€stholmens badplats","municipality":"ΓdeshΓΆg","lat":58.27877,"lon":14.6334},{"id":"SE0A21446000001381","name":"VΓ€ttern, Pingstkyrkan","municipality":"Karlsborg","lat":58.53708,"lon":14.5106},{"id":"SE0A21497000006280","name":"VΓ€ttern, Strandpromenaden","municipality":"Hjo","lat":58.2963,"lon":14.2846},{"id":"SE0910680000003483","name":"VΓ€tterstranden","municipality":"JΓΆnkΓΆping","lat":57.78269,"lon":14.20614},{"id":"SE0230584000004340","name":"VΓ€tterviksbadet, udden","municipality":"Vadstena","lat":58.4664,"lon":14.9336},{"id":"SE0110188000002176","name":"VΓ€tΓΆ, HΓ₯knΓ€s (Sandviken)","municipality":"NorrtΓ€lje","lat":59.79008,"lon":18.93483},{"id":"SE0920780000004390","name":"VΓ€xjΓΆsjΓΆn Kampen","municipality":"VΓ€xjΓΆ","lat":56.86961,"lon":14.81464},{"id":"SE0920780000004391","name":"VΓ€xjΓΆsjΓΆn StrandbjΓΆrket","municipality":"VΓ€xjΓΆ","lat":56.87,"lon":14.8073},{"id":"SE0632101000002770","name":"Wijdammen","municipality":"Ockelbo","lat":60.88607,"lon":16.70012},{"id":"SE0210380000001182","name":"Wiksbadet","municipality":"Uppsala","lat":59.73564,"lon":17.46052},{"id":"SE0441278000004501","name":"Ydrehall Torekov","municipality":"BΓ₯stad","lat":56.4234,"lon":12.62},{"id":"SE0220482000000745","name":"Yngarebadet","municipality":"Flen","lat":58.89488,"lon":16.60049},{"id":"SE0220480000002082","name":"Yngaren, HarhΓ₯lets badplats","municipality":"NykΓΆping","lat":58.8522,"lon":16.6289},{"id":"SE0441290000000679","name":"YngsjΓΆ havsbad","municipality":"Kristianstad","lat":55.8819,"lon":14.2563},{"id":"SE0712280000003741","name":"YtterfΓ€ll, Svartviks badplats","municipality":"HΓ€rnΓΆsand","lat":62.5753,"lon":17.91471},{"id":"SE0822514000003511","name":"YttertrΓ€sket","municipality":"Kalix","lat":66.04435,"lon":22.71278},{"id":"SE0441280000006160","name":"Yttre Barnviken, Sibbarp","municipality":"MalmΓΆ","lat":55.5717,"lon":12.9001},{"id":"SE0930884000001439","name":"Yxern, GrΓΆpplesand","municipality":"Vimmerby","lat":57.69039,"lon":16.07682},{"id":"SE0230563000001300","name":"Yxningen, Gusum","municipality":"Valdemarsvik","lat":58.28158,"lon":16.46006},{"id":"SE0241864000007521","name":"YxsjΓΆberg","municipality":"Ljusnarsberg","lat":60.0465,"lon":14.7747},{"id":"SE0920764000001594","name":"Γ
boda","municipality":"Alvesta","lat":57.08397,"lon":14.4751},{"id":"SE0241860000003271","name":"Γ
bydammen","municipality":"LaxΓ₯","lat":58.91628,"lon":14.4539},{"id":"SE0110181000001362","name":"Γ
bynΓ€sbadet","municipality":"SΓΆdertΓ€lje","lat":59.01838,"lon":17.62009},{"id":"SE0230581000003968","name":"Γ
gelsjΓΆn","municipality":"NorrkΓΆping","lat":58.6758,"lon":16.1191},{"id":"SE0441290000000676","name":"Γ
husbryggans badplats","municipality":"Kristianstad","lat":55.9369,"lon":14.3213},{"id":"SE0230561000001215","name":"Γ
kervristen","municipality":"Γ
tvidaberg","lat":58.137,"lon":16.2283},{"id":"SE0712262000006342","name":"Γ
kerΓΆviken","municipality":"TimrΓ₯","lat":62.4064,"lon":17.7105},{"id":"SE0A21461000001053","name":"Γ
klΓ₯ngen, Sundserud","municipality":"Mellerud","lat":58.80841,"lon":12.40081},{"id":"SE0622029000002439","name":"Γ
l-Kilen","municipality":"Leksand","lat":60.631,"lon":15.0299},{"id":"SE0441282000000414","name":"Γ
labodarna ","municipality":"Landskrona","lat":55.9431,"lon":12.7701},{"id":"SE0441290000004257","name":"Γ
lakustvΓ€gens badplats/hund och naturistbad","municipality":"Kristianstad","lat":55.8634,"lon":14.2417},{"id":"SE0A21484000000566","name":"Γ
levik","municipality":"Lysekil","lat":58.28169,"lon":11.4297},{"id":"SE0940980000000239","name":"Γ
minne","municipality":"Gotland","lat":57.618,"lon":18.7655},{"id":"SE0241882000007320","name":"Γ
mmebergs badplats","municipality":"Askersund","lat":58.8771,"lon":15.0072},{"id":"SE0812404000003684","name":"Γ
msele badplats","municipality":"Vindeln","lat":64.529,"lon":19.33516},{"id":"SE0622062000002676","name":"Γ
mΓ₯sΓ€ngsbadet, Siljan","municipality":"Mora","lat":60.98014,"lon":14.56953},{"id":"SE0712283000003008","name":"Γ
ngermanΓ€lven, Undroms strandb","municipality":"SollefteΓ₯","lat":63.11457,"lon":17.74284},{"id":"SE0622080000004384","name":"Γ
rbosjΓΆn, SΓ₯gmyra","municipality":"Falun","lat":60.71928,"lon":15.27748},{"id":"SE0920780000001541","name":"Γ
redasjΓΆn, Nabben","municipality":"VΓ€xjΓΆ","lat":56.8941,"lon":14.9754},{"id":"SE0110136000002145","name":"Γ
rsta Havsbad","municipality":"Haninge","lat":59.0811,"lon":18.1658},{"id":"SE0920780000001537","name":"Γ
rydsjΓΆn Γ
ryd","municipality":"VΓ€xjΓΆ","lat":56.828,"lon":14.9883},{"id":"SE0920780000001538","name":"Γ
rydsjΓΆn, Furuby","municipality":"VΓ€xjΓΆ","lat":56.8484,"lon":15.0217},{"id":"SE0910680000003485","name":"Γ
sabadet","municipality":"JΓΆnkΓΆping","lat":57.69297,"lon":14.0906},{"id":"SE0622039000004494","name":"Γ
sen Klippbadet","municipality":"Γlvdalen","lat":61.27189,"lon":13.81345},{"id":"SE0622084000002490","name":"Γ
sgarn","municipality":"Avesta","lat":60.23935,"lon":16.26905},{"id":"SE0411081000000163","name":"Γ
sjΓΆn","municipality":"Ronneby","lat":56.45469,"lon":15.37886},{"id":"SE0441257000000433","name":"Γ
sljungasjΓΆns badplats","municipality":"Γrkelljunga","lat":56.30256,"lon":13.36412},{"id":"SE0622061000004017","name":"Γ
smansbo, Bersen","municipality":"Smedjebacken","lat":60.20475,"lon":15.47693},{"id":"SE0920780000001553","name":"Γ
snen, JΓ€t","municipality":"VΓ€xjΓΆ","lat":56.6474,"lon":14.838},{"id":"SE0920780000001552","name":"Γ
snen, Kalvsvik","municipality":"VΓ€xjΓΆ","lat":56.7069,"lon":14.7229},{"id":"SE0A21419000007400","name":"Γ
stol","municipality":"TjΓΆrn","lat":57.9215,"lon":11.5816},{"id":"SE0230513000001196","name":"Γ
sundabadet","municipality":"Kinda","lat":58.131,"lon":15.69},{"id":"SE0A21491000001034","name":"Γ
sunden PrΓ₯ngen","municipality":"Ulricehamn","lat":57.80213,"lon":13.40602},{"id":"SE0A21491000001036","name":"Γ
sunden Skottek","municipality":"Ulricehamn","lat":57.76982,"lon":13.39818},{"id":"SE0A21491000001035","name":"Γ
sunden Sturebadet","municipality":"Ulricehamn","lat":57.79783,"lon":13.4086},{"id":"SE0A21491000001037","name":"Γ
sunden, AlnΓ€set MarbΓ€ck","municipality":"Ulricehamn","lat":57.7387,"lon":13.39912},{"id":"SE0230513000001199","name":"Γ
sunden, Hycklinge","municipality":"Kinda","lat":57.9284,"lon":15.9115},{"id":"SE0230513000001198","name":"Γ
sunden, RΓ₯sΓΆ bro","municipality":"Kinda","lat":58.0076,"lon":15.765},{"id":"SE0230513000003556","name":"Γ
sunden, TrossbonΓ€s","municipality":"Kinda","lat":57.9567,"lon":15.836},{"id":"SE0930882000003601","name":"Γ
svallesund","municipality":"Oskarshamn","lat":57.2265,"lon":16.4848},{"id":"SE0110120000001678","name":"Γ
tervallstrΓ€sk","municipality":"VΓ€rmdΓΆ","lat":59.2684,"lon":18.4706},{"id":"SE0920760000001294","name":"ΓlgasjΓΆn, Γlghult","municipality":"Uppvidinge","lat":57.01251,"lon":15.60201},{"id":"SE0622085000002467","name":"ΓlgsjΓΆn","municipality":"Ludvika","lat":60.12961,"lon":14.37023},{"id":"SE0230562000000885","name":"ΓlgsjΓΆn, GrytgΓΆl","municipality":"FinspΓ₯ng","lat":58.85086,"lon":15.56264},{"id":"SE0110182000004478","name":"ΓlgΓΆ bergbad","municipality":"Nacka","lat":59.2627,"lon":18.3476},{"id":"SE0110182000001232","name":"ΓlgΓΆbadet","municipality":"Nacka","lat":59.26202,"lon":18.35209},{"id":"SE0411082000000176","name":"ΓllhΓΆlen","municipality":"Karlshamn","lat":56.3168,"lon":14.8083},{"id":"SE0910682000002035","name":"ΓlmeshultasjΓΆn,Γlmeshultabadet","municipality":"NΓ€ssjΓΆ","lat":57.71292,"lon":14.86132},{"id":"SE0110182000001234","name":"Γltabadet","municipality":"Nacka","lat":59.26307,"lon":18.17764},{"id":"SE0622062000002685","name":"Γlvbadet, Mora Camping","municipality":"Mora","lat":61.011,"lon":14.53737},{"id":"SE0210319000000775","name":"ΓlvkarleΓΆ badplats","municipality":"Γlvkarleby","lat":60.53907,"lon":17.42756},{"id":"SE0622026000002625","name":"ΓlvsjΓΆn","municipality":"Gagnef","lat":60.5451,"lon":15.1897},{"id":"SE0210331000002561","name":"ΓlvΓ₯sa, FΓ€rnΓΆsandsbadet","municipality":"Heby","lat":60.15562,"lon":16.80447},{"id":"SE0930860000000988","name":"Γngahultsbadet, Silverdalen","municipality":"Hultsfred","lat":57.5464,"lon":15.7346},{"id":"SE0110180000001850","name":"Γngbybadet V, MΓ€laren","municipality":"Stockholm","lat":59.33622,"lon":17.89592},{"id":"SE0110180000001851","name":"Γngbybadet Γ, MΓ€laren","municipality":"Stockholm","lat":59.3365,"lon":17.8983},{"id":"SE0251982000002537","name":"Γngelsbergsbadet","municipality":"Fagersta","lat":59.96178,"lon":15.99846},{"id":"SE0920760000001298","name":"ΓnghultasjΓΆn, KlavrestrΓΆm","municipality":"Uppvidinge","lat":57.1399,"lon":15.1413},{"id":"SE0110160000001445","name":"Γngsholmsbadet","municipality":"TΓ€by","lat":59.459,"lon":18.10199},{"id":"SE0210360000000987","name":"ΓngskΓ€r","municipality":"Tierp","lat":60.47929,"lon":18.07851},{"id":"SE0230561000001216","name":"ΓrlΓ₯ngen","municipality":"Γ
tvidaberg","lat":58.3005,"lon":15.8546},{"id":"SE0622023000002472","name":"ΓrΓ₯badet, Dammen","municipality":"Malung","lat":60.90556,"lon":13.36867},{"id":"SE0441287000000444","name":"ΓspΓΆ","municipality":"Trelleborg","lat":55.34982,"lon":13.39032},{"id":"SE0A11382000000611","name":"Γtraforsdammen","municipality":"Falkenberg","lat":57.0355,"lon":12.6715},{"id":"SE0A21491000001033","name":"Γtran TrΓ€det","municipality":"Ulricehamn","lat":57.98861,"lon":13.53206},{"id":"SE0A21443000003855","name":"Γ:a NedsjΓΆn, Tubbared","municipality":"Bollebygd","lat":57.7336,"lon":12.523},{"id":"SE0441267000003768","name":"Γ:a RingsjΓΆn, SΓ€tofta bad- och bΓ₯tplats","municipality":"HΓΆΓΆr","lat":55.9041,"lon":13.5549},{"id":"SE0A21463000000950","name":"Γ:a ΓresjΓΆn, Hanatorp","municipality":"Mark","lat":57.4725,"lon":12.7059},{"id":"SE0A21463000000952","name":"Γ:a ΓresjΓΆn, Mos strand","municipality":"Mark","lat":57.46076,"lon":12.70476},{"id":"SE0110117000002094","name":"Γ. SaxarfjΓ€rden, LinanΓ€sbadet","municipality":"ΓsterΓ₯ker","lat":59.4742,"lon":18.5163},{"id":"SE0A21407000003885","name":"ΓckerΓΆ, HjΓ€lvik","municipality":"ΓckerΓΆ","lat":57.71922,"lon":11.63486},{"id":"SE0A21407000003886","name":"ΓckerΓΆ, Saltasviken","municipality":"ΓckerΓΆ","lat":57.70762,"lon":11.63534},{"id":"SE0A21460000001415","name":"ΓdskΓΆlt IvΓ€g","municipality":"Bengtsfors","lat":58.87209,"lon":12.1985},{"id":"SE0632121000002717","name":"ΓjebΓ€cken, Edsbyn","municipality":"OvanΓ₯ker","lat":61.37606,"lon":15.82158},{"id":"SE0622023000002475","name":"ΓjesjΓΆn","municipality":"Malung","lat":60.80787,"lon":13.87598},{"id":"SE0712260000003019","name":"ΓjesjΓΆn, KΓ€rrhuset","municipality":"Γ
nge","lat":62.40243,"lon":15.01982},{"id":"SE0220428000001524","name":"Γljaren, Hullabadet","municipality":"VingΓ₯ker","lat":59.1115,"lon":16.0211},{"id":"SE0241885000002512","name":"ΓlsjΓΆn","municipality":"Lindesberg","lat":59.78117,"lon":15.27904},{"id":"SE0230561000001217","name":"Γnn","municipality":"Γ
tvidaberg","lat":58.1948,"lon":16.3692},{"id":"SE0441283000000311","name":"Γrby Γngar","municipality":"Helsingborg","lat":55.98504,"lon":12.75107},{"id":"SE0441283000004056","name":"Γrby Γ€ngar norr","municipality":"Helsingborg","lat":55.98856,"lon":12.74806},{"id":"SE0210382000001728","name":"Γregrund, SunnanΓΆbadet","municipality":"Γsthammar","lat":60.31917,"lon":18.45842},{"id":"SE0930884000002768","name":"Γren, Bosand","municipality":"Vimmerby","lat":57.82443,"lon":15.7118},{"id":"SE0910662000001098","name":"Γreryd, GusjΓΆ Strand","municipality":"Gislaved","lat":57.47773,"lon":13.66904},{"id":"SE0A21488000001010","name":"ΓresjΓΆ, Munkeboviken","municipality":"TrollhΓ€ttan","lat":58.274,"lon":12.1914},{"id":"SE0A21488000001011","name":"ΓresjΓΆ, Phoenix","municipality":"TrollhΓ€ttan","lat":58.2768,"lon":12.1888},{"id":"SE0441280000006161","name":"Γresunds Funkis","municipality":"MalmΓΆ","lat":55.599,"lon":12.9489},{"id":"SE0110138000002116","name":"Γringebadet","municipality":"TyresΓΆ","lat":59.25082,"lon":18.25744},{"id":"SE0920780000001540","name":"Γrken, BraΓ₯s","municipality":"VΓ€xjΓΆ","lat":57.0674,"lon":15.054},{"id":"SE0920780000001539","name":"Γrken, BΓΆksholm","municipality":"VΓ€xjΓΆ","lat":57.0873,"lon":15.0195},{"id":"SE0A21472000000757","name":"Γrlen, Campingplatsen","municipality":"Tibro","lat":58.4633,"lon":14.25458},{"id":"SE0A21472000000752","name":"Γrlen, Fagersanna","municipality":"Tibro","lat":58.47103,"lon":14.30051},{"id":"SE0241863000003425","name":"Γrlingen, Silvergruvans badpla","municipality":"HΓ€llefors","lat":59.86694,"lon":14.44673},{"id":"SE0110180000004459","name":"Γrnberget (HΓ€gerstenshamnen), MΓ€laren","municipality":"Stockholm","lat":59.3098,"lon":17.98245},{"id":"SE0910680000003484","name":"Γrserumsbadet","municipality":"JΓΆnkΓΆping","lat":58.0066,"lon":14.56838},{"id":"SE0611780000002383","name":"ΓrsholmstjΓ€rn","municipality":"Karlstad","lat":59.38274,"lon":13.54928},{"id":"SE0A21461000001050","name":"ΓrsjΓΆn, StrΓΆmmersvik","municipality":"Mellerud","lat":58.68756,"lon":12.31684},{"id":"SE0930881000000798","name":"ΓrsjΓΆsjΓΆn","municipality":"Nybro","lat":56.69663,"lon":15.753},{"id":"SE0812401000003543","name":"Γrstensbadet","municipality":"Nordmaling","lat":63.5246,"lon":19.7465},{"id":"SE0A11384000004534","name":"Γrsviken","municipality":"Kungsbacka","lat":57.561,"lon":11.926},{"id":"SE0A21465000001078","name":"ΓrsΓ₯s, ΓrsΓ₯ssjΓΆn","municipality":"Svenljunga","lat":57.44282,"lon":13.12904},{"id":"SE0A21401000006380","name":"ΓrtjΓ€rnen","municipality":"HΓ€rryda","lat":57.6473,"lon":12.1429},{"id":"SE0110120000001679","name":"Γsby trΓ€sk","municipality":"VΓ€rmdΓΆ","lat":59.32724,"lon":18.41496},{"id":"SE0110162000001090","name":"ΓsbysjΓΆn","municipality":"Danderyd","lat":59.40251,"lon":18.06319},{"id":"SE0A21452000001880","name":"ΓsjΓΆn","municipality":"Tranemo","lat":57.54075,"lon":13.37956},{"id":"SE0920781000001328","name":"ΓssjΓΆasjΓΆn","municipality":"Ljungby","lat":56.62886,"lon":13.4409},{"id":"SE0A21435000004327","name":"Γstad","municipality":"Tanum","lat":58.77261,"lon":11.56916},{"id":"SE0611765000002317","name":"Γsten, BjΓΆrkenΓ€set","municipality":"Γ
rjΓ€ng","lat":59.64645,"lon":11.89548},{"id":"SE0210382000001722","name":"Γsterbybruk, Aspbobadet","municipality":"Γsthammar","lat":60.2111,"lon":17.95438},{"id":"SE0210382000001723","name":"Γsterbybruk, Simbadet","municipality":"Γsthammar","lat":60.1955,"lon":17.9137},{"id":"SE0241882000002525","name":"ΓstersjΓΆn","municipality":"Askersund","lat":58.95423,"lon":14.95635},{"id":"SE0A21439000001360","name":"ΓstersjΓΆn, EllenΓΆbadet","municipality":"FΓ€rgelanda","lat":58.51454,"lon":11.99917},{"id":"SE0220480000004213","name":"ΓstersjΓΆn, NΓ€sudden","municipality":"NykΓΆping","lat":58.7248,"lon":17.1145},{"id":"SE0220480000002216","name":"ΓstersjΓΆn, VΓ€stra Koviks badpl","municipality":"NykΓΆping","lat":58.6177,"lon":16.939},{"id":"SE0220480000005819","name":"ΓstersjΓΆn. Byggningssand (Atomic Beach)","municipality":"NykΓΆping","lat":58.7779,"lon":17.3899},{"id":"SE0441290000000666","name":"ΓsterslΓΆvs badplats","municipality":"Kristianstad","lat":56.1024,"lon":14.2444},{"id":"SE0110188000004934","name":"Γsterviken","municipality":"NorrtΓ€lje","lat":59.66124,"lon":18.94242},{"id":"SE0622080000004382","name":"ΓsterΓ₯","municipality":"Falun","lat":60.66186,"lon":15.62406},{"id":"SE0722380000003168","name":"ΓsterΓ₯sens bad och camping","municipality":"Γstersund","lat":63.40109,"lon":14.98019},{"id":"SE0210382000001721","name":"Γsthammar, Krutudden","municipality":"Γsthammar","lat":60.2617,"lon":18.3853},{"id":"SE0110136000002146","name":"Γstnora","municipality":"Haninge","lat":59.05144,"lon":18.06019},{"id":"SE0622039000004495","name":"ΓstomsjΓΆns badplats, SΓ€rnasjΓΆn","municipality":"Γlvdalen","lat":61.67822,"lon":13.21964},{"id":"SE0622029000002434","name":"Γstra RΓΆnnΓ€s","municipality":"Leksand","lat":60.6993,"lon":15.1358},{"id":"SE0A21465000001073","name":"Γstra FrΓΆlunda, VitasjΓΆn","municipality":"Svenljunga","lat":57.30907,"lon":12.9897},{"id":"SE0632184000002999","name":"Γstra havsbadet, HΓΆlick","municipality":"Hudiksvall","lat":61.6225,"lon":17.4457},{"id":"SE0251980000002546","name":"Γstra Holmen, Badviken","municipality":"VΓ€sterΓ₯s","lat":59.58608,"lon":16.5769},{"id":"SE0230512000001918","name":"Γstra LΓ€gern, RydsnΓ€s","municipality":"Ydre","lat":57.80391,"lon":15.15009},{"id":"SE0220486000001913","name":"Γstra MagsjΓΆn, Porsuddebadet","municipality":"StrΓ€ngnΓ€s","lat":59.21452,"lon":16.93462},{"id":"SE0611765000002309","name":"Γstra Silen, Signebyn","municipality":"Γ
rjΓ€ng","lat":59.2787,"lon":12.29774},{"id":"SE0A21490000001781","name":"Γstra ValsjΓΆns badplats, Hedared","municipality":"BorΓ₯s","lat":57.81718,"lon":12.78361},{"id":"SE0441265000000040","name":"Γstra VombsjΓΆn","municipality":"SjΓΆbo","lat":55.6802,"lon":13.6241},{"id":"SE0611780000002392","name":"Γstra Γrten, Γlvsbacka","municipality":"Karlstad","lat":59.71652,"lon":13.66726},{"id":"SE0712281000003466","name":"ΓsttjΓ€rn Matfors","municipality":"Sundsvall","lat":62.327,"lon":17.2562},{"id":"SE0920764000001583","name":"Γvden","municipality":"Alvesta","lat":56.62087,"lon":14.50529},{"id":"SE0A21465000001058","name":"Γverlida, Stora HallΓ₯ngen","municipality":"Svenljunga","lat":57.34699,"lon":12.90397},{"id":"SE0930883000001643","name":"Γverums badplats (SΓ₯duggen)","municipality":"VΓ€stervik","lat":57.9979,"lon":16.3024},{"id":"SE0930821000001940","name":"ΓvrasjΓΆn, FΓ₯gelfors","municipality":"HΓΆgsby","lat":57.21208,"lon":15.84742},{"id":"SE0910685000000847","name":"ΓvrasjΓΆn, SkirΓΆ","municipality":"Vetlanda","lat":57.379,"lon":15.407},{"id":"SE0611737000003316","name":"Γvre BadasjΓΆn, BadasjΓΆberg","municipality":"Torsby","lat":60.16636,"lon":13.12068},{"id":"SE0611737000003329","name":"Γvre Brocken LΓ₯ngnΓ€s","municipality":"Torsby","lat":60.31013,"lon":13.03013},{"id":"SE0611737000003330","name":"Γvre Brocken NΓΆtΓΆn","municipality":"Torsby","lat":60.30479,"lon":13.04102},{"id":"SE0611737000003315","name":"Γvre Fryken Bada","municipality":"Torsby","lat":60.07017,"lon":13.07871},{"id":"SE0611737000003306","name":"Γvre Fryken Bredviken","municipality":"Torsby","lat":60.09139,"lon":13.03302},{"id":"SE0611737000003307","name":"Γvre Fryken KallnΓ€s","municipality":"Torsby","lat":60.09579,"lon":13.03695},{"id":"SE0611737000003305","name":"Γvre Fryken Kollsberg","municipality":"Torsby","lat":60.12998,"lon":13.01891},{"id":"SE0611737000005879","name":"Γvre Fryken Tuva","municipality":"Torsby","lat":60.13037,"lon":13.03291},{"id":"SE0230513000005315","name":"Γvre FΓΆlingen, Sommarro","municipality":"Kinda","lat":57.9848,"lon":15.5397},{"id":"SE0230513000001205","name":"Γvre FΓΆllingen, Pinnarpsbadet","municipality":"Kinda","lat":57.992,"lon":15.5174},{"id":"SE0632121000002716","name":"Γvre Hammaren, Voxnabruk","municipality":"OvanΓ₯ker","lat":61.36649,"lon":15.48375},{"id":"SE0622082000002418","name":"Γvre Klingen","municipality":"SΓ€ter","lat":60.47306,"lon":15.84239},{"id":"SE0110136000002143","name":"Γvre Rudan","municipality":"Haninge","lat":59.16645,"lon":18.13247},{"id":"SE0A21463000000953","name":"ΓxasjΓΆn, ΓxabΓ€ck","municipality":"Mark","lat":57.39235,"lon":12.80788}]
\ No newline at end of file
diff --git a/frontend/public/icons/bada-icon-dark.svg b/frontend/public/icons/bada-icon-dark.svg
new file mode 100644
index 0000000000..aff5bc226b
--- /dev/null
+++ b/frontend/public/icons/bada-icon-dark.svg
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/public/icons/bada-icon-light.svg b/frontend/public/icons/bada-icon-light.svg
new file mode 100644
index 0000000000..42bbb873b7
--- /dev/null
+++ b/frontend/public/icons/bada-icon-light.svg
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/public/og-image.png b/frontend/public/og-image.png
new file mode 100644
index 0000000000..88b434d1d9
Binary files /dev/null and b/frontend/public/og-image.png differ
diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt
new file mode 100644
index 0000000000..5beea58009
--- /dev/null
+++ b/frontend/public/robots.txt
@@ -0,0 +1,5 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Allow: /
+
+Sitemap: https://badaweb.netlify.app/sitemap.xml
diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml
new file mode 100644
index 0000000000..d582193494
--- /dev/null
+++ b/frontend/public/sitemap.xml
@@ -0,0 +1,10551 @@
+
+
+
+ https://badaweb.netlify.app/
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/what-is-eu-beach
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/about
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/terms
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/privacy
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/contact
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411060000000277
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611763000002279
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812404000003683
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812418000003416
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000005597
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001592
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001318
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000928
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002104
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000004055
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003209
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001246
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001910
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002575
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001531
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000003594
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001614
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000150
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000655
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002982
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000001372
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000007260
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002429
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001819
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000005057
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002062
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001421
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000605
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002767
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000002991
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001634
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001516
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001038
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000884
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001706
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000633
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000634
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002372
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251984000004049
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000498
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002154
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000915
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000004446
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001747
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000004447
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622021000002825
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000005697
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001533
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001532
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001327
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000532
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001801
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002516
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002496
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001039
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001120
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000521
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002358
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910604000000972
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000004043
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812401000003539
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002817
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003506
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003023
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000004425
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000998
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000004172
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000004714
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000946
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920761000001398
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812422000003146
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611760000002394
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930834000001944
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000004280
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001223
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001233
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003853
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000428
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000673
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004238
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441276000000868
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000636
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002105
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001558
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003028
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441261000000076
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441261000003868
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000330
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002736
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000110
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001167
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812417000003845
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003475
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001363
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000791
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000005195
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002766
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003055
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002164
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000388
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110115000001995
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002805
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000519
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441281000000305
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441264000000308
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004418
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003187
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002933
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000582
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001624
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812417000003745
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000825
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812409000003122
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000422
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003029
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003454
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002455
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000793
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001202
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000260
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002148
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001933
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000001717
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000003777
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000417
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001296
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000627
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003876
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002181
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001951
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000004475
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000004474
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000002543
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002657
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000186
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220461000001065
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001615
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000651
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002175
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002312
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000004397
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000003648
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001126
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000808
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001322
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001949
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002708
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000003986
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21462000000726
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000599
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000003649
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000001353
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003325
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001809
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001582
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001752
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110114000001600
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001304
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001110
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001302
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001307
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001308
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001303
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001305
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001189
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003272
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000473
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000471
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000004392
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220488000005595
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003020
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611764000002874
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001106
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000408
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000409
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001423
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21462000000729
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000424
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001299
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002641
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000004223
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251960000003014
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001386
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002510
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000004517
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21427000000034
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21439000001361
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000003904
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003353
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002360
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251907000002865
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000289
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000381
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000001365
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003476
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003428
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001950
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004412
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000004854
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002106
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000267
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000004316
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110127000001394
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001206
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003455
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21498000000899
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000007380
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003736
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004654
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001864
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000004318
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001737
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001732
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002086
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001317
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000779
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003230
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000004288
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002742
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004420
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000524
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001810
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000624
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000625
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000004378
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000002473
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001919
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003504
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002755
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000004500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002453
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003378
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003724
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002485
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003435
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003017
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000005056
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000483
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003061
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230561000001210
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003520
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003571
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003357
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001018
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001787
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000482
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003990
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003410
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251962000002555
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002085
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230584000000723
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000002416
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241861000002534
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000412
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441275000000043
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003471
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000468
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000004480
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000469
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000553
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001748
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002802
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000004059
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000004191
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001407
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000869
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000004048
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000004535
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000203
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000000105
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441267000000405
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441267000004429
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441287000000442
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003696
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000005959
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000005498
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001803
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930834000001945
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001877
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001440
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002788
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000881
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002450
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002498
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002791
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000004263
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003358
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001598
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241862000002254
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241862000002253
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003705
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001141
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000635
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001128
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000479
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000980
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000003964
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000319
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000511
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002823
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002801
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001952
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000198
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004525
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002122
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002139
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002119
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002120
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000004343
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002121
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002117
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002138
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002093
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003059
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002065
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003412
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003413
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001142
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003477
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003437
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003467
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000295
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001641
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002783
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000944
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002621
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000003905
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002989
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000004528
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000003635
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110162000004210
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000000818
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110162000001113
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000003636
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000151
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000780
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000826
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000474
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000672
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000231
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001863
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210305000000759
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000003789
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251960000003012
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000782
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230563000001315
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003411
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000164
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21471000007500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000827
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000796
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002890
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002891
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002974
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001740
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210331000002565
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000001354
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000018
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002159
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001231
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000002536
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000141
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000644
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002519
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000003669
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001325
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000981
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001155
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003360
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002478
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000000024
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000501
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001866
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001355
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441257000000430
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001112
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000002853
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000007600
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000419
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000007480
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000006720
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003237
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001118
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000516
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000573
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001227
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001175
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001180
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003572
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001665
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001019
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000929
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001109
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001867
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001868
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000002413
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000004405
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000004255
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002614
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001320
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21495000000805
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003048
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004413
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003450
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003480
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004526
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000005677
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001954
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002352
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001143
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002313
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000004013
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003873
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002307
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000002541
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251961000002656
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004457
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002022
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210330000001161
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000004075
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003262
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000003890
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000561
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000003945
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000003595
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002361
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000003826
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002365
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002364
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000004293
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004383
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000004442
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000004443
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000003940
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000002975
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220461000005175
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000004184
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930834000001947
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004138
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001483
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000671
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910643000000719
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000004510
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000003908
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001177
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000003866
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000005939
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110114000001599
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110186000001466
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001953
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812401000003541
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000020
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000005476
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002706
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000398
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000005575
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000002996
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000004395
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001579
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003047
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241861000002535
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000158
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002903
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002714
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441267000001340
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000004814
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21472000000753
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001629
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002892
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000004250
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002388
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004512
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812422000004171
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002092
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000188
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003214
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622021000002829
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001709
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000003911
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000007200
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000007180
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000007181
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001426
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003856
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110127000001391
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003582
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920767000000690
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000107
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000638
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000639
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000640
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000641
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003070
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000004894
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000001724
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000063
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002184
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441287000000443
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001427
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002032
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003022
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000353
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003303
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003871
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003284
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003285
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003286
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001730
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001222
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622021000002827
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002507
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000144
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001204
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000005258
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000543
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002217
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000189
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001102
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002763
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004142
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000990
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001860
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000454
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000574
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003071
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002819
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812404000003681
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21488000001013
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000001975
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000003645
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001666
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110192000002133
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001905
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002180
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001667
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000871
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000828
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000829
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611764000002881
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611764000002882
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000006340
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000001996
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000118
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002904
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002610
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241862000002273
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001878
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002762
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002166
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002720
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003509
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003030
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230563000001314
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001428
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001616
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001617
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001618
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001619
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000004346
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002375
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001528
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002334
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000003790
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002499
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000006039
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000006840
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000003938
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001934
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000004296
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003739
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003872
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004914
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004896
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001620
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000003936
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000560
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000004313
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003038
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000003847
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001956
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001948
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001639
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000654
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003281
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003287
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000005094
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000004444
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003735
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000329
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000382
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003236
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002142
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000459
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000004754
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003037
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000777
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21430000000178
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002757
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004513
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000004455
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000004635
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000875
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003445
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000003659
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251962000002554
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000152
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002735
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001912
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002127
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001935
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000930
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001955
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441262000000325
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004523
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000597
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000013
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002518
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001020
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002658
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002664
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000002976
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411060000000276
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000005496
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000212
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002081
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001621
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000003557
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002479
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000124
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001622
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000004254
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003288
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000006680
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441262000000321
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230584000007022
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110192000002132
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002761
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002638
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002727
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000004403
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002010
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000457
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920767000000692
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000003782
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000004491
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003216
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002514
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000001726
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230586000001276
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002895
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002740
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003472
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003837
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000007360
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003073
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000368
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002140
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001957
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000003646
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002458
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000113
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004183
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004310
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004239
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000002540
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002984
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001571
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000004504
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001573
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000003302
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001508
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001565
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001572
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001568
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001562
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001566
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001569
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001564
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001567
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001570
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002438
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001657
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000006220
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000290
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000294
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001557
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000004385
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000004321
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003730
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002627
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002765
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001115
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002161
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000006740
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004140
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000248
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000999
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000932
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001068
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002886
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000680
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003256
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002445
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110127000001414
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920767000000689
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004422
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002440
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000318
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001623
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001590
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910665000001259
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003434
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812460000003080
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000004440
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000004026
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002636
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002637
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002635
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002639
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441257000000434
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000830
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001581
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000612
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441273000000003
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000191
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003976
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000004193
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000831
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002315
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002494
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000251
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003018
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002704
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001060
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000004481
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000004185
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000060
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003478
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001936
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000948
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000616
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000396
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000744
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000004356
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002952
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000533
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210331000002566
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003518
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003577
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001585
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000994
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21440000001827
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441257000000431
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000811
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001816
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21402000000093
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000429
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000165
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000879
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003501
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001603
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002524
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230582000001082
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000003660
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110184000006560
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000004073
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003880
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000956
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003036
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001076
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000000102
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002978
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000007120
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21440000001824
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000832
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003083
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003615
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110160000001441
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003740
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000214
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000000734
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251984000002586
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411083000000659
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000001716
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003267
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003881
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000003969
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000933
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000005657
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000005658
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004309
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004435
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003973
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000392
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632161000002738
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003742
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003738
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000159
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001834
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004489
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21427000001495
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000505
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000005921
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002107
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241881000003227
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002055
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611762000004428
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001885
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000861
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000489
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000491
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002935
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002883
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002616
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002980
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241864000007520
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000003823
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002316
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251984000002590
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001869
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110140000001346
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000003948
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002598
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003882
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003884
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003883
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003255
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241864000002529
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003439
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411083000000662
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001814
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003049
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000003661
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000003666
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930834000001941
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000270
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002379
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000863
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000288
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000002993
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001604
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001605
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000090
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001545
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001543
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000006660
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000007560
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001194
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002437
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001958
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000017
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000665
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000296
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000297
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000298
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000301
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000005054
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230584000000722
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001031
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220481000001263
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004020
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001836
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000003562
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611760000002395
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000682
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002977
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000003586
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822521000003344
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000004431
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001430
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002443
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002653
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110127000001410
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000153
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000003860
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001200
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001763
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000003836
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002459
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000004404
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000004338
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000004317
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003697
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000004046
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000628
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110114000001601
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000004161
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000006159
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002893
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000314
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000416
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000562
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000426
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210305000000758
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000786
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001071
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000011
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003874
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001837
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001838
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000000027
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001859
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000833
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000000968
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002907
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000006179
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000003173
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000834
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001686
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000154
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000788
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000006980
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822521000003339
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000007061
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000476
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000670
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000004066
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812409000003114
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000520
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002389
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002908
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000789
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000007201
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003327
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001203
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003031
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251962000002553
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002407
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000000064
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002050
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230582000001083
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000012
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001937
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000740
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920767000000691
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000005435
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004240
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000835
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002505
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000005276
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003233
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000551
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000652
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003319
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220461000001069
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000945
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000217
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441281000004058
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002444
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001169
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000001366
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001627
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000005415
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000006520
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000002538
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002754
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000166
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003289
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000836
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21430000000177
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251983000002580
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001645
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003273
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003039
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220488000002074
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241864000002528
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003062
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812401000003540
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000006240
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000004406
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21473000001980
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110186000001454
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000004432
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000004461
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000209
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004555
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001097
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000003350
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000462
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002382
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000002668
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001431
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000155
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000001371
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002487
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000005215
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002108
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000004715
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002609
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001136
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21439000001358
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002739
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003323
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251984000002584
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000006780
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000006019
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002123
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632161000004471
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000002552
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001336
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000327
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001802
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000411
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632161000002743
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611764000002878
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000004069
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001100
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001376
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611760000002393
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000004479
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002713
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002463
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000006540
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000000735
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000738
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000653
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21402000000095
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230582000001084
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000108
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001237
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000004088
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002936
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003877
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000000022
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003231
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110186000001448
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002601
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002172
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000007000
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000645
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000646
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001133
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632161000004201
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000004300
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000167
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000377
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000470
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003054
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001319
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001016
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002896
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001166
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001875
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000000106
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000183
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000181
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000182
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822584000004004
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003218
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002344
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000056
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000058
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000059
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000674
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000587
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241814000004068
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002165
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000003664
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000317
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003698
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000004045
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002821
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001137
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003704
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220461000001067
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241864000004858
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000378
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003066
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000002673
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000004070
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241814000003852
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002430
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001409
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001408
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002339
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001295
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000487
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000494
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000669
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000449
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002576
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002373
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002353
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000299
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001922
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001595
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002080
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002345
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002741
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251904000002593
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002079
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004242
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000415
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000632
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000656
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002728
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001606
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21498000000898
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000809
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21438000000765
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001766
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001767
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002662
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001041
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622082000002420
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001749
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000168
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21462000000730
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000508
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000001005
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000351
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003972
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812404000003685
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001750
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003084
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000001714
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002480
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001820
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002374
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003085
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002937
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000004426
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000989
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002506
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000601
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622021000004016
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000002252
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000004994
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000342
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000812
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000813
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001547
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910665000001262
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000657
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003970
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000002997
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251907000002869
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000006079
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000245
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002806
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003021
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000797
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000615
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001813
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000000023
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622082000002417
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004237
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001651
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920767000000688
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004493
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003086
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441262000000326
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002156
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003042
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230509000001420
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004141
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003461
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003242
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003235
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002171
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000742
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000000963
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251984000002588
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611760000002397
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000427
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000606
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000006940
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000002852
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000003827
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001633
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001632
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001171
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001522
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000005455
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000658
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441262000000324
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001607
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000549
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910665000001266
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000004198
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000169
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002702
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001417
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000814
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000123
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21438000000761
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001331
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000567
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003258
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003259
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001831
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001832
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001105
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002913
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000003619
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000003009
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001162
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001872
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000003962
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000002415
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110115000001987
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003737
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611764000002873
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000005174
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001172
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251904000002595
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000783
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003077
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000004344
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004436
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000201
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001536
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920761000001395
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004415
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001907
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001188
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001523
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000345
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001635
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002648
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000004297
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002730
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000089
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000016
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000003351
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002718
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001173
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000007280
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003702
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110140000001347
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002784
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001839
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000983
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000002550
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000004381
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003694
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000004857
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002807
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001297
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001546
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001555
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002125
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000004054
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000340
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251907000002871
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001103
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000139
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001123
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000007420
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002786
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251983000002579
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000003026
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000005999
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002157
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001840
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001841
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003290
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002391
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000531
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000004053
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000007140
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000007340
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001873
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000774
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001525
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001413
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001799
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110192000002136
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000081
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002213
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000002411
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000003175
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002600
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000002192
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004414
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000984
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002402
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003584
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11315000000361
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001689
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000751
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822584000003688
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003695
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003045
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001138
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000004260
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000156
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001057
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000667
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001698
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001699
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003383
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001021
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002707
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000006400
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002619
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002390
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000160
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000137
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210331000002564
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003050
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000004531
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441264000000306
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441264000000307
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001432
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001856
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001855
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001857
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001858
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000001969
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000001970
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21497000001187
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002520
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241814000003849
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003498
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003024
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002599
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110191000004834
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001416
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003043
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001608
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001221
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000003620
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230586000001277
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003088
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001122
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001746
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000004079
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001348
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001321
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220484000000698
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001342
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110123000001470
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110123000004306
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110123000003608
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110123000001471
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110127000001375
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110123000001468
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000328
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001842
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001843
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002178
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000882
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000619
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000004258
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002569
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002570
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002571
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002568
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002567
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001654
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001655
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241883000002572
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001653
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000002330
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000002331
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000495
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110128000002185
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241862000002256
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000421
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000014
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000853
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000000965
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001963
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003732
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003733
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000004416
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001130
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003025
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000004433
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002333
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21447000005515
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000004430
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002400
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002398
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000003966
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001754
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002951
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000942
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000004380
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110192000002129
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000004119
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004036
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000864
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000255
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000002474
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910604000000974
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241884000003201
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241884000003202
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241884000003205
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000004027
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004514
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000004453
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004308
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000086
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000005698
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632161000002747
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251962000002556
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000003667
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411083000000661
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000007460
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000007040
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000006500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000004518
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000007541
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002366
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000003560
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21466000001496
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000004472
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000004203
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001135
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000006821
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000410
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003033
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000003621
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003462
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000792
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002803
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000002000
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002509
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000007620
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210330000001160
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000004694
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002753
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000000816
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000003632
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812417000003743
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001433
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003241
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001339
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000005940
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000681
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000347
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002811
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001879
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000002851
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000493
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002357
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210381000001370
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003522
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000003622
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000171
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003040
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000000964
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622021000002828
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110160000001442
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21495000000806
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000008
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000530
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001127
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002916
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003691
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000004287
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000001977
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002622
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000003668
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004492
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003701
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001511
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002751
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000003007
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002481
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001042
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000837
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002012
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000001713
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001964
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001628
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000620
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000849
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000851
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000852
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001904
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000541
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002427
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003441
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002425
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002917
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002955
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000005255
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411060000000273
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000006020
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002124
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004458
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002918
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001335
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000815
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000750
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003089
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441273000000002
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441273000004182
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000004439
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001517
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21498000000900
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002792
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004188
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002723
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000838
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822521000003982
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003463
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000120
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000004057
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000004042
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812460000003079
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822521000003338
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000007121
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000002476
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002348
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000211
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000420
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000006420
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000564
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002750
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001700
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002758
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000003634
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001503
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002623
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002659
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001043
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002919
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21402000000097
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003442
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441263000000099
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000003937
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002607
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000315
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003321
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441287000004634
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000795
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21439000001356
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002824
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001917
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002110
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000062
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001806
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000839
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002144
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002652
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110192000002135
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910665000001281
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000004174
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003693
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000003637
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000004341
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812418000005114
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000001349
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000003006
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003274
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001063
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004522
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000000072
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000003900
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000005617
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000370
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000003773
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441285000000004
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441266000000045
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002403
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000205
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002083
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000565
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230560000001274
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002404
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003291
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000003665
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000801
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002822
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230561000001212
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001061
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003496
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910665000001282
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000287
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000088
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000546
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110191000001488
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002059
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000006099
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001435
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000006059
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000006620
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920761000001396
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001548
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001549
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000003999
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001757
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001758
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002939
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000778
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001436
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000739
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000310
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000004855
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000006860
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000005759
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000004226
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001324
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000348
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001640
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000334
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002342
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002341
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002343
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000394
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002167
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000463
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000550
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001022
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002177
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001920
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001965
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002504
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000312
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000313
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002442
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002604
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003573
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812417000003744
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003464
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000006880
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001134
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003468
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000872
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000161
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812401000003537
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002731
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001658
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003232
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000085
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000331
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003878
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000000817
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110162000001091
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001560
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000840
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000004122
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002884
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000004452
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000534
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21430000000180
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000349
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000003348
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000199
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000004467
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002800
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002799
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000005799
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001779
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000771
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002155
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000623
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230582000001085
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230563000001312
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000344
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000003923
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000001976
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000003944
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002940
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003459
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001334
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910665000004347
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000954
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21466000001498
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002897
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003974
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003220
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003046
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000478
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000004417
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000003899
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000224
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002460
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002482
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000242
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411083000000660
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210330000006800
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21439000001359
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002764
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21427000000039
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001660
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003495
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21430000000179
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000121
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000003176
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000147
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000149
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002461
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000603
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000015
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001967
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441261000005638
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000004366
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000145
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003452
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251907000002866
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000000101
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003091
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000215
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000841
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001066
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003034
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000000066
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001822
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000333
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001238
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000004229
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003391
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001176
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000266
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003700
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000004469
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002964
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002966
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002963
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002428
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220488000002075
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000507
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002314
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002666
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003261
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000005375
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002812
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001682
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001681
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001683
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002170
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622031000002972
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003313
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000397
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110191000001490
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000004029
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002033
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000195
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001596
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000935
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632101000002769
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000003596
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000003398
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000004377
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230560000001272
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11315000000357
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001526
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000003561
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21488000001012
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000006480
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000003593
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000002412
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000610
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003583
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000004655
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001690
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002486
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000007461
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002848
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001652
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001644
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003269
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002296
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21447000000822
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21447000000821
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002921
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001798
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001805
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000859
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000138
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812462000003703
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000000026
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000003765
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000004448
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441287000000440
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110160000001444
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000748
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001649
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251983000002581
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000955
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000341
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000281
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000002682
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002922
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000586
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002923
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002924
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002722
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251960000003013
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000860
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000874
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230582000001086
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000003844
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611763000002282
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230586000001275
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21499000000925
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210305000004074
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003056
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21438000000764
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000537
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000002849
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004456
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000004208
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000004209
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000000103
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002446
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000770
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000769
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001403
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441287000000439
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000591
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000003578
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000617
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000005355
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000000650
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000004077
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11315000000359
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000220
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000800
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000002003
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002468
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251904000002596
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910604000000977
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000936
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001870
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000204
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000162
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411060000000275
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000007020
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000238
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000003943
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000006222
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21421000000135
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000004207
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003473
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812418000003417
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001743
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000157
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000002001
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000004081
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000450
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001845
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001846
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000529
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21421000000131
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000677
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712284000003052
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812460000003078
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000229
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003479
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001437
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001610
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001611
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000842
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001438
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622082000002421
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000005495
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812418000003415
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001847
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002182
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001916
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230560000001270
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230560000001271
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001915
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000862
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000007060
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241861000002531
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002640
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000749
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441293000000423
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000003597
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000007122
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001612
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002168
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001589
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004243
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000538
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001909
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000000736
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812463000003133
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002084
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001095
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000366
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110191000001489
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441233000005497
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000003680
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000332
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000799
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000395
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000004065
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000785
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000006219
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001243
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220481000001264
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930840000000007
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002699
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110186000001458
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110139000001715
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002212
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000173
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220461000001062
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220461000001075
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000000966
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000002833
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001876
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002406
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003431
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000518
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001015
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000390
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001707
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910683000001513
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000880
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000993
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000810
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001429
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21402000000098
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003293
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230509000004536
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001044
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000386
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000005699
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411060000000278
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001768
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001770
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930885000000475
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002465
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001914
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21438000000762
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000959
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000146
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002818
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001425
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11381000000196
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002005
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002006
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002787
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822581000002737
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003238
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003443
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000004281
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812482000004342
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000002998
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812409000003115
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001144
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001332
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003444
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002697
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002054
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003092
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003093
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000004361
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000004204
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003186
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000003679
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003188
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000005779
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000005781
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000005780
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002987
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002988
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002111
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001862
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622034000002692
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000006960
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000006341
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21438000000767
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002078
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910642000001971
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003492
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001236
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000865
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002701
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000642
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001903
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001892
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001894
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001898
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000005719
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000202
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000200
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002435
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000510
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000526
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002698
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000006119
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002941
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001760
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000003563
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001761
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001762
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000004533
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001207
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241864000002530
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000172
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003254
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002804
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822505000004044
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000232
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000078
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000119
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002347
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003276
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000004499
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002380
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110140000001345
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000002551
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21461000004470
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001165
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000385
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21440000001828
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000002820
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21421000000132
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822584000003332
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000174
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441286000000350
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003222
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241862000002259
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230586000001278
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230560000001273
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812418000004270
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003971
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001074
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000111
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000001999
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001815
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000004434
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000006199
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000001292
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004527
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001219
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001405
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230561000001214
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002441
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000003975
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002466
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002789
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001883
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001023
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000003670
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000447
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000055
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000958
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000006360
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001849
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001848
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002088
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002089
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110114000001602
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000002405
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611715000003967
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001040
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21489000001692
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001338
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001884
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000007080
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002163
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000006440
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110186000001464
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001178
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411060000000274
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001676
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004419
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000399
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000006481
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000003949
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003210
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001099
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000004214
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002387
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000630
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910686000001609
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002942
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002650
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002725
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110125000000962
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910604000000978
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000843
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003223
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003564
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003613
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001897
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001896
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000004774
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21421000000129
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002497
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000001753
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001593
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002508
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003035
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632181000002985
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000461
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000004859
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002926
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000109
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441265000000041
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003491
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21415000000112
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000492
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812460000003082
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000790
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000207
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000004508
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000747
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611782000002356
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002617
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004244
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004021
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000007141
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000007142
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000000117
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110140000001344
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001542
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812404000004574
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003490
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000001351
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002888
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002887
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000004060
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001179
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622021000002826
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21498000000892
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21498000000893
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930861000000856
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000005760
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000003929
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000230
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241861000002532
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000890
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000886
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000889
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003270
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230563000001306
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000458
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241880000002655
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000844
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000372
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001861
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000787
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002484
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002620
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441267000000407
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000272
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000262
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000004794
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000383
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001561
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002043
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210331000002562
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110115000001994
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001101
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001800
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002785
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002663
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000004519
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000000136
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001586
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001663
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003429
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003424
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632104000002790
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001551
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001045
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000465
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000004279
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411083000004101
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000380
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001638
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002885
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920767000000684
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000007540
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000006600
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001807
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003474
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411083000000664
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003294
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230509000001422
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000175
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001871
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002112
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000866
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000004441
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822580000003234
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000003174
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220488000002073
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001559
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000216
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000005979
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000004795
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002102
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002090
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002087
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000000629
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000401
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110127000001387
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000000506
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001908
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002605
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000845
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001326
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812404000003682
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230584000000724
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000292
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441256000000291
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001636
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000003942
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000371
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110187000001352
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230582000001087
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21486000005861
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21427000000029
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001330
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110163000001447
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812480000002810
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002422
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822583000007062
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001796
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004139
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910687000000867
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812421000004450
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001646
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001550
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003858
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002927
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003914
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000000731
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930862000000737
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000985
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003224
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21442000001333
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003569
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000873
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000003933
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000545
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000986
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001902
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001584
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003225
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000598
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002928
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001341
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11315000000355
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002724
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002014
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002338
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920763000001132
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241884000004954
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002513
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000007021
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002483
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411080000000213
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000320
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001293
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622081000002410
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611783000002340
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812425000004001
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003854
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000002661
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21482000000092
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003519
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000746
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230509000003994
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000568
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001096
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441272000000300
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000448
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110115000001985
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000000464
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210330000003551
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210330000001181
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910684000002002
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632182000002695
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002091
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003226
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003909
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001852
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230583000001854
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004246
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632132000003032
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441267000003910
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220483000001140
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002522
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002500
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000338
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000339
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001170
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003317
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004307
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722361000004232
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441257000000432
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003320
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000991
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441273000000001
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000004437
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632183000002929
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712282000003447
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001527
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11383000004398
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001323
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003469
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230580000001765
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251981000003260
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002944
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910617000000937
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21485000000588
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21473000001978
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000484
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001392
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21473000001979
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441284000000485
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241884000003206
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003857
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441261000000074
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000316
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002603
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000004311
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003734
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002517
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000005134
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11380000000369
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21440000001826
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812422000003147
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21495000000807
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000006741
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910604000000979
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001662
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251907000002867
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000001003
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000226
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001886
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230509000001418
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812463000003143
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002298
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000004554
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611763000002284
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611766000002370
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001664
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000781
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920765000001659
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441291000000284
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000002943
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822584000003334
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210331000002560
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001220
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003193
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220481000001267
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000002726
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822584000003689
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003015
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002719
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000004695
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21496000001684
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21480000004516
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822584000003333
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000004488
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000002669
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000001968
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003470
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241862000002272
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110126000002126
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001183
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001229
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632180000002894
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002174
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000006260
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001939
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611730000003521
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001184
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000004507
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000054
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21441000001046
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822582000003229
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002384
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002377
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002381
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002386
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002378
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002376
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002385
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000004614
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21471000001027
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001924
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002288
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000003824
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21471000001029
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000006640
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001925
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000916
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000004373
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000002854
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000001890
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000002328
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000007300
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002289
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002290
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21471000001026
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21471000001030
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000004374
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000921
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000004376
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000002325
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000001889
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21461000001048
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21447000000819
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002292
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000002329
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000002327
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000922
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002293
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000007220
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000923
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001927
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000001887
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001928
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611781000002295
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001929
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000003825
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000926
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001930
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21493000001931
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000003901
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000004375
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000941
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21487000001888
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21494000000947
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21461000001047
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000940
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000002326
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611761000004371
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21492000000938
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002173
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002511
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003295
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611784000003301
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611764000002877
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611785000003581
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000846
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441257000004734
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930880000000794
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110162000002332
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110162000001093
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002447
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002436
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003460
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441292000000335
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003486
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251983000002582
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21481000000400
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622083000002611
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003453
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000000061
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002302
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611763000002285
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001383
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910643000003989
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21497000001191
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001378
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910643000000717
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001379
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21497000001190
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001380
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230509000001419
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21446000001381
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21497000006280
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003483
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230584000004340
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000002176
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000004390
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000004391
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632101000002770
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210380000001182
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441278000004501
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220482000000745
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002082
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000679
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712280000003741
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0822514000003511
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000006160
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000001439
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230563000001300
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241864000007521
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001594
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241860000003271
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110181000001362
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230581000003968
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000676
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230561000001215
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712262000006342
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21461000001053
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002439
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441282000000414
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000004257
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21484000000566
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0940980000000239
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000007320
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812404000003684
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000002676
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712283000003008
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004384
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001541
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002145
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001537
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001538
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003485
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004494
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622084000002490
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411081000000163
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441257000000433
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622061000004017
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001553
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001552
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21419000007400
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001196
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001034
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001036
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001035
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001037
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001199
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001198
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000003556
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930882000003601
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001678
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001294
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622085000002467
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230562000000885
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000004478
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001232
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0411082000000176
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910682000002035
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110182000001234
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622062000002685
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210319000000775
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622026000002625
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210331000002561
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930860000000988
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001850
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000001851
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251982000002537
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920760000001298
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110160000001445
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210360000000987
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230561000001216
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000002472
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441287000000444
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11382000000611
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21491000001033
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21443000003855
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441267000003768
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000950
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000952
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110117000002094
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003885
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21407000003886
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21460000001415
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002717
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622023000002475
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712260000003019
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220428000001524
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241885000002512
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230561000001217
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000000311
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441283000004056
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000001728
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930884000002768
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910662000001098
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21488000001010
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21488000001011
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441280000006161
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110138000002116
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001540
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920780000001539
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21472000000757
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21472000000752
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241863000003425
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110180000004459
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910680000003484
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002383
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21461000001050
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930881000000798
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0812401000003543
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A11384000004534
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001078
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21401000006380
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110120000001679
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110162000001090
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21452000001880
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920781000001328
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21435000004327
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002317
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000001722
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000001723
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0241882000002525
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21439000001360
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000004213
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000002216
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220480000005819
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441290000000666
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110188000004934
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622080000004382
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0722380000003168
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0210382000001721
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002146
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622039000004495
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622029000002434
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001073
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632184000002999
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0251980000002546
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230512000001918
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0220486000001913
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611765000002309
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21490000001781
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0441265000000040
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611780000002392
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0712281000003466
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0920764000001583
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21465000001058
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930883000001643
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0930821000001940
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0910685000000847
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003316
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003329
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003330
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003315
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003306
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003307
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000003305
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0611737000005879
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000005315
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0230513000001205
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0632121000002716
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0622082000002418
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0110136000002143
+ 2026-07-11
+
+
+ https://badaweb.netlify.app/beach/SE0A21463000000953
+ 2026-07-11
+
+
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
deleted file mode 100644
index 0a24275e6e..0000000000
--- a/frontend/src/App.jsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export const App = () => {
-
- return (
- <>
- Welcome to Final Project!
- >
- );
-};
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000000..00c64a4ae3
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,66 @@
+import { useEffect } from "react";
+import { Routes, Route } from "react-router";
+import { useAuth } from "./store/auth";
+import Header from "./components/Header";
+import LanguageSwitcher from "./components/LanguageSwitcher";
+import ErrorBoundary from "./components/ErrorBoundary";
+import ProtectedRoute from "./routes/ProtectedRoute";
+import AmbientBackground from "./components/AmbientBackground";
+
+import HomePage from "./pages/HomePage";
+import BeachDetailPage from "./pages/BeachDetailPage";
+import LoginPage from "./pages/LoginPage";
+import RegisterPage from "./pages/RegisterPage";
+import FavoritesPage from "./pages/FavoritesPage";
+import AboutPage from "./pages/AboutPage";
+import WhatIsEUBeachPage from "./pages/WhatIsEUBeachPage";
+import TermsPage from "./pages/TermsPage";
+import PrivacyPage from "./pages/PrivacyPage";
+import ContactPage from "./pages/ContactPage";
+import ProfilePage from "./pages/ProfilePage";
+import SettingsPage from "./pages/SettingsPage";
+import NotFoundPage from "./pages/NotFoundPage";
+
+export default function App() {
+ // Resolve auth state once on load by checking the session cookie via /auth/me.
+ const refresh = useAuth((s) => s.refresh);
+ useEffect(() => {
+ refresh();
+ }, [refresh]);
+
+ return (
+
+ {/* Three.js ambient background (water/sand) */}
+
+
+
+ } />
+
+
+ } />
+ } />
+
+ {/* Public */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Private */}
+ }>
+ } />
+ } />
+ } />
+
+
+ {/* 404 */}
+ } />
+
+
+
+
+ );
+}
diff --git a/frontend/src/api/account.ts b/frontend/src/api/account.ts
new file mode 100644
index 0000000000..f4a12418c2
--- /dev/null
+++ b/frontend/src/api/account.ts
@@ -0,0 +1,9 @@
+import { apiFetch } from "./client";
+
+/**
+ * Permanently delete the authenticated user's account and all their data.
+ * The auth token is attached automatically by apiFetch.
+ */
+export function deleteAccount(): Promise<{ ok: true }> {
+ return apiFetch<{ ok: true }>("/auth/me", { method: "DELETE" });
+}
diff --git a/frontend/src/api/beaches.ts b/frontend/src/api/beaches.ts
new file mode 100644
index 0000000000..0d31942758
--- /dev/null
+++ b/frontend/src/api/beaches.ts
@@ -0,0 +1,37 @@
+import { apiFetch } from "./client";
+import {
+ BeachSummary,
+ BeachDetail,
+ BeachFeatureCollection,
+ featureToSummary,
+} from "@/types/beaches";
+
+// List all beaches β BeachSummary[]
+//
+// Primary source is a static snapshot (frontend/public/beaches.json) built at
+// deploy time and served from the CDN β no serverless cold start, no large
+// GeoJSON round trip. Falls back to the live HaV proxy if the snapshot is
+// missing (e.g. local dev before the file is generated).
+export async function fetchBeaches(): Promise {
+ try {
+ const res = await fetch("/beaches.json", {
+ headers: { Accept: "application/json" },
+ });
+ if (res.ok) {
+ const data = (await res.json()) as BeachSummary[];
+ if (Array.isArray(data) && data.length > 0) return data;
+ }
+ } catch {
+ // ignore and fall back to the live proxy
+ }
+
+ const data = await apiFetch("/beaches");
+ return data.features
+ .map(featureToSummary)
+ .filter((x): x is BeachSummary => x !== null);
+}
+
+// One beach detail
+export async function fetchBeach(id: string): Promise {
+ return apiFetch(`/beaches/${id}`);
+}
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
new file mode 100644
index 0000000000..e99c36ac11
--- /dev/null
+++ b/frontend/src/api/client.ts
@@ -0,0 +1,78 @@
+import { logApi } from "@/utils/logger";
+
+// Topology A requires the app to be same-origin with its API: in production we
+// ALWAYS use the relative "/api" path, which Netlify proxies to the backend so
+// the session cookie stays first-party. This is hardcoded for prod on purpose β
+// a stale VITE_API_BASE pointing at the cross-site backend would silently break
+// cookie auth. In dev, VITE_API_BASE points at a local backend.
+const API_BASE = import.meta.env.PROD
+ ? "/api"
+ : import.meta.env.VITE_API_BASE ?? "/api";
+
+type FetchOptions = Omit & {
+ headers?: Record;
+};
+
+export async function apiFetch(
+ path: string,
+ options: FetchOptions = {}
+): Promise {
+ const url = path.startsWith("http") ? path : `${API_BASE}${path}`;
+
+ const headers: Record = {
+ "Content-Type": "application/json",
+ ...(options.headers || {}),
+ };
+
+ logApi("β", options.method ?? "GET", url, {
+ headers,
+ body: options.body,
+ });
+
+ // credentials:"include" sends/receives the httpOnly session cookie, including
+ // on the dev cross-origin (localhost:5173 β localhost:3000) case.
+ const res = await fetch(url, { ...options, headers, credentials: "include" });
+
+ logApi("β", res.status, res.statusText, url);
+
+ if (!res.ok) {
+ let body: any = null;
+ try {
+ body = await res.json();
+ } catch {
+ // non-JSON error response
+ }
+ const error = new Error(body?.error || res.statusText);
+ (error as any).status = res.status;
+ (error as any).details = body;
+ throw error;
+ }
+
+ if (res.status === 204) return undefined as T;
+
+ return (await res.json()) as T;
+}
+
+const API_ERROR_KEYS: Record = {
+ AlreadyFavorited: "apiErrors.alreadyFavorited",
+ BadGateway: "apiErrors.upstream",
+ EmailInUse: "apiErrors.emailInUse",
+ InternalServerError: "apiErrors.internal",
+ InvalidBody: "apiErrors.invalidBody",
+ InvalidCredentials: "apiErrors.invalidCredentials",
+ InvalidToken: "apiErrors.sessionExpired",
+ NoAuthToken: "apiErrors.sessionExpired",
+ NotFound: "apiErrors.notFound",
+ TooManyRequests: "apiErrors.tooManyRequests",
+ UpstreamError: "apiErrors.upstream",
+};
+
+export function apiErrorMessage(
+ error: unknown,
+ t: (key: string) => string,
+ fallback: string
+) {
+ const code = error instanceof Error ? error.message : "";
+ const key = API_ERROR_KEYS[code];
+ return key ? t(key) : fallback;
+}
diff --git a/frontend/src/api/favorites.ts b/frontend/src/api/favorites.ts
new file mode 100644
index 0000000000..8d6322c845
--- /dev/null
+++ b/frontend/src/api/favorites.ts
@@ -0,0 +1,98 @@
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { apiFetch } from "./client";
+import { useAuth } from "@/store/auth";
+
+export type Favorite = {
+ _id: string;
+ userId?: string;
+ beachId: string;
+ note?: string;
+ order?: number;
+ createdAt?: string;
+ updatedAt?: string;
+};
+
+/**
+ * List favorites for the current user.
+ * IMPORTANT: subscribe to auth status via selector so the query refetches on
+ * login/logout. The session cookie identifies the user, so the key is static.
+ */
+export function useFavorites() {
+ const status = useAuth((s) => s.status);
+ return useQuery({
+ queryKey: ["favorites"],
+ enabled: status === "authenticated",
+ queryFn: () => apiFetch("/favorites"),
+ staleTime: 60_000,
+ // keepPreviousData: true, // optional for smoother transitions
+ });
+}
+
+/**
+ * Add a new favorite by beachId.
+ */
+export function useAddFavorite() {
+ const qc = useQueryClient();
+
+ return useMutation({
+ mutationFn: (beachId: string) =>
+ apiFetch("/favorites", {
+ method: "POST",
+ body: JSON.stringify({ beachId }),
+ }),
+ onSuccess: () => {
+ qc.invalidateQueries({ queryKey: ["favorites"] });
+ },
+ });
+}
+
+/**
+ * Remove a favorite (by _id or by beachId) with a small optimistic update.
+ */
+export function useRemoveFavorite() {
+ const qc = useQueryClient();
+
+ return useMutation({
+ mutationFn: (vars: { id?: string; beachId?: string }) => {
+ const url = vars.id
+ ? `/favorites/${vars.id}`
+ : `/favorites/by-beach/${vars.beachId}`;
+ return apiFetch(url, { method: "DELETE" });
+ },
+ onMutate: async (vars) => {
+ await qc.cancelQueries({ queryKey: ["favorites"] });
+ const prev = qc.getQueryData(["favorites"]);
+ if (prev) {
+ const next = prev.filter((f) =>
+ vars.id ? f._id !== vars.id : f.beachId !== vars.beachId
+ );
+ qc.setQueryData(["favorites"], next);
+ }
+ return { prev };
+ },
+ onError: (_err, _vars, ctx) => {
+ if (ctx?.prev) qc.setQueryData(["favorites"], ctx.prev);
+ },
+ onSettled: () => {
+ qc.invalidateQueries({ queryKey: ["favorites"] });
+ },
+ });
+}
+
+/**
+ * Persist custom order of favorites on the server.
+ * Accepts an array of beachIds in the desired order.
+ */
+export function useReorderFavorites() {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: (order: string[]) =>
+ apiFetch("/favorites/reorder", {
+ method: "PATCH",
+ body: JSON.stringify({ order }),
+ }),
+ onSuccess: () => {
+ qc.invalidateQueries({ queryKey: ["favorites"] });
+ },
+ });
+}
diff --git a/frontend/src/api/sun.ts b/frontend/src/api/sun.ts
new file mode 100644
index 0000000000..6a552ef507
--- /dev/null
+++ b/frontend/src/api/sun.ts
@@ -0,0 +1,57 @@
+import type { SunTimes } from '../types/weather';
+
+const SUN_URL = 'https://api.sunrise-sunset.org/json';
+
+const ONE_HOUR_MS = 60 * 60 * 1000;
+
+interface SunriseSunsetResponse {
+ status: string;
+ results: {
+ sunrise: string;
+ sunset: string;
+ solar_noon: string;
+ day_length: number;
+ civil_twilight_begin: string;
+ civil_twilight_end: string;
+ nautical_twilight_begin: string;
+ nautical_twilight_end: string;
+ astronomical_twilight_begin: string;
+ astronomical_twilight_end: string;
+ };
+}
+
+export async function fetchSunTimes(lat: number, lon: number, date?: Date): Promise {
+ const params = new URLSearchParams({
+ lat: String(lat),
+ lng: String(lon),
+ formatted: '0',
+ });
+ if (date) {
+ params.set('date', date.toISOString().split('T')[0]);
+ }
+
+ const res = await fetch(`${SUN_URL}?${params}`);
+ if (!res.ok) throw new Error(`sunrise-sunset.org error: ${res.status}`);
+
+ const data: SunriseSunsetResponse = await res.json();
+ if (data.status !== 'OK') throw new Error(`sunrise-sunset.org status: ${data.status}`);
+
+ const r = data.results;
+ const sunrise = new Date(r.sunrise);
+ const sunset = new Date(r.sunset);
+
+ return {
+ sunrise,
+ sunset,
+ solarNoon: new Date(r.solar_noon),
+ dayLengthSeconds: r.day_length,
+ goldenHourMorningEnd: new Date(sunrise.getTime() + ONE_HOUR_MS),
+ goldenHourEveningStart: new Date(sunset.getTime() - ONE_HOUR_MS),
+ civilTwilightBegin: new Date(r.civil_twilight_begin),
+ civilTwilightEnd: new Date(r.civil_twilight_end),
+ nauticalTwilightBegin: new Date(r.nautical_twilight_begin),
+ nauticalTwilightEnd: new Date(r.nautical_twilight_end),
+ astronomicalTwilightBegin: new Date(r.astronomical_twilight_begin),
+ astronomicalTwilightEnd: new Date(r.astronomical_twilight_end),
+ };
+}
diff --git a/frontend/src/api/useBeachDetails.ts b/frontend/src/api/useBeachDetails.ts
new file mode 100644
index 0000000000..4751fcbb92
--- /dev/null
+++ b/frontend/src/api/useBeachDetails.ts
@@ -0,0 +1,26 @@
+import { useQueries, UseQueryResult } from "@tanstack/react-query";
+import { fetchBeach } from "@/api/beaches";
+import type { BeachDetail } from "@/types/beaches";
+
+export function useBeachDetails(ids: string[] | undefined) {
+ const queries = useQueries({
+ queries: (ids ?? []).map((id) => ({
+ queryKey: ["beach", id],
+ queryFn: () => fetchBeach(id),
+ enabled: !!id,
+ staleTime: 5 * 60 * 1000,
+ })),
+ }) as UseQueryResult[];
+
+ const byId = new Map();
+ queries.forEach((q, i) => {
+ const id = ids?.[i];
+ if (id && q.data) byId.set(id, q.data);
+ });
+
+ return {
+ byId,
+ isLoading: queries.some((q) => q.isLoading),
+ isError: queries.some((q) => q.isError),
+ };
+}
diff --git a/frontend/src/api/watches.ts b/frontend/src/api/watches.ts
new file mode 100644
index 0000000000..b0c9c9c0d3
--- /dev/null
+++ b/frontend/src/api/watches.ts
@@ -0,0 +1,38 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { apiFetch } from "./client";
+
+export interface WatchItem {
+ beachId: string;
+ createdAt: string;
+}
+
+/** The signed-in user's watched beaches (bevakningar). */
+export function useWatches(enabled: boolean) {
+ return useQuery({
+ queryKey: ["watches"],
+ queryFn: () => apiFetch<{ items: WatchItem[] }>("/watches"),
+ select: (d) => d.items,
+ enabled,
+ });
+}
+
+export function useAddWatch() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (beachId: string) =>
+ apiFetch<{ beachId: string }>("/watches", {
+ method: "POST",
+ body: JSON.stringify({ beachId }),
+ }),
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["watches"] }),
+ });
+}
+
+export function useRemoveWatch() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (beachId: string) =>
+ apiFetch(`/watches/${beachId}`, { method: "DELETE" }),
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["watches"] }),
+ });
+}
diff --git a/frontend/src/api/weather.ts b/frontend/src/api/weather.ts
new file mode 100644
index 0000000000..12bd7f1759
--- /dev/null
+++ b/frontend/src/api/weather.ts
@@ -0,0 +1,137 @@
+import type { WeatherData, DailyForecast } from '../types/weather';
+
+const FORECAST_URL = 'https://api.open-meteo.com/v1/forecast';
+const MARINE_URL = 'https://marine-api.open-meteo.com/v1/marine';
+
+const FORECAST_DAYS = 5;
+// Look back a few days so the swim nowcast can weigh recent runoff (the main
+// short-term driver of bacterial spikes) against the last official sample.
+const PAST_DAYS = 3;
+
+interface OpenMeteoForecastResponse {
+ current: {
+ temperature_2m: number;
+ apparent_temperature: number;
+ uv_index: number;
+ weather_code: number;
+ time: string;
+ };
+ daily: {
+ time: string[];
+ weather_code: number[];
+ temperature_2m_max: number[];
+ temperature_2m_min: number[];
+ uv_index_max: number[];
+ precipitation_probability_max: (number | null)[];
+ precipitation_sum: (number | null)[];
+ wind_speed_10m_max: (number | null)[];
+ };
+}
+
+interface OpenMeteoMarineResponse {
+ current: {
+ sea_surface_temperature: number;
+ };
+ daily?: {
+ time: string[];
+ wave_height_max: (number | null)[];
+ };
+}
+
+interface MarineData {
+ waterTemperature: number | null;
+ /** ISO date β max significant wave height (m). Empty inland. */
+ waveMaxByDate: Record;
+}
+
+async function fetchForecast(lat: number, lon: number): Promise {
+ const params = new URLSearchParams({
+ latitude: String(lat),
+ longitude: String(lon),
+ current: 'temperature_2m,apparent_temperature,uv_index,weather_code',
+ daily:
+ 'weather_code,temperature_2m_max,temperature_2m_min,uv_index_max,precipitation_probability_max,precipitation_sum,wind_speed_10m_max',
+ forecast_days: String(FORECAST_DAYS),
+ past_days: String(PAST_DAYS),
+ timezone: 'auto',
+ });
+ const res = await fetch(`${FORECAST_URL}?${params}`);
+ if (!res.ok) throw new Error(`Open-Meteo forecast error: ${res.status}`);
+ return res.json();
+}
+
+function toDailyForecast(
+ daily: OpenMeteoForecastResponse['daily'],
+ waveMaxByDate: Record
+): DailyForecast[] {
+ return daily.time.map((date, i) => ({
+ date,
+ weatherCode: daily.weather_code[i],
+ tempMax: daily.temperature_2m_max[i],
+ tempMin: daily.temperature_2m_min[i],
+ uvMax: daily.uv_index_max[i],
+ precipProbability: daily.precipitation_probability_max[i] ?? null,
+ precipSum: daily.precipitation_sum[i] ?? null,
+ windMax: daily.wind_speed_10m_max?.[i] ?? null,
+ waveMax: waveMaxByDate[date] ?? null,
+ }));
+}
+
+async function fetchMarine(lat: number, lon: number): Promise {
+ const params = new URLSearchParams({
+ latitude: String(lat),
+ longitude: String(lon),
+ current: 'sea_surface_temperature',
+ daily: 'wave_height_max',
+ forecast_days: String(FORECAST_DAYS),
+ timezone: 'auto',
+ });
+ // The marine model only covers sea points; inland lakes 404 or return nulls.
+ // Waves and sea temperature are enrichments, never blockers.
+ const res = await fetch(`${MARINE_URL}?${params}`);
+ if (!res.ok) return { waterTemperature: null, waveMaxByDate: {} };
+ const data: OpenMeteoMarineResponse = await res.json();
+ const waveMaxByDate: Record = {};
+ data.daily?.time.forEach((date, i) => {
+ const wave = data.daily?.wave_height_max[i];
+ if (wave != null) waveMaxByDate[date] = wave;
+ });
+ return {
+ waterTemperature: data.current?.sea_surface_temperature ?? null,
+ waveMaxByDate,
+ };
+}
+
+export async function fetchBeachWeather(lat: number, lon: number): Promise {
+ const [forecast, marine] = await Promise.all([
+ fetchForecast(lat, lon),
+ fetchMarine(lat, lon),
+ ]);
+ const { waterTemperature, waveMaxByDate } = marine;
+
+ const allDays = toDailyForecast(forecast.daily, waveMaxByDate);
+ // `daily` spans PAST_DAYS + today + forecast; split on today's local date so
+ // downstream consumers keep the familiar "today onward" forecast, and the
+ // past days feed the recent-runoff signal.
+ const today = forecast.current.time.slice(0, 10); // YYYY-MM-DD
+ const todayIdx = allDays.findIndex((d) => d.date >= today);
+ const splitIdx = todayIdx === -1 ? 0 : todayIdx;
+
+ const pastDays = allDays.slice(0, splitIdx);
+ const forecastDays = allDays.slice(splitIdx);
+
+ const recentRainMm = pastDays.some((d) => d.precipSum != null)
+ ? pastDays.reduce((sum, d) => sum + (d.precipSum ?? 0), 0)
+ : null;
+
+ return {
+ temperature: forecast.current.temperature_2m,
+ feelsLike: forecast.current.apparent_temperature,
+ uvIndex: forecast.current.uv_index,
+ weatherCode: forecast.current.weather_code,
+ waterTemperature,
+ fetchedAt: forecast.current.time,
+ forecast: forecastDays,
+ recentRainMm,
+ };
+}
diff --git a/frontend/src/assets/menu_icon.svg b/frontend/src/assets/menu_icon.svg
new file mode 100644
index 0000000000..88d22c8ab5
--- /dev/null
+++ b/frontend/src/assets/menu_icon.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/assets/user_icon.svg b/frontend/src/assets/user_icon.svg
new file mode 100644
index 0000000000..fe875ebd31
--- /dev/null
+++ b/frontend/src/assets/user_icon.svg
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/AmbientBackground.tsx b/frontend/src/components/AmbientBackground.tsx
new file mode 100644
index 0000000000..82d4b461a3
--- /dev/null
+++ b/frontend/src/components/AmbientBackground.tsx
@@ -0,0 +1,135 @@
+import { Component, lazy, Suspense, useEffect, useState, type ErrorInfo, type ReactNode } from "react";
+import { usePrefersReducedMotion } from "../hooks/usePrefersReducedMotion";
+import { useDarkModeObserver } from "../hooks/useDarkModeObserver";
+import { canUseWebGL } from "../utils/webgl";
+
+// Lazy load the 3D backgrounds to improve initial load time
+// Use dynamic import with a small delay to prioritize main content rendering
+const WaterBackground = lazy(() =>
+ new Promise(resolve => {
+ // Wait for idle callback or timeout to ensure first paint completes
+ if ("requestIdleCallback" in window) {
+ requestIdleCallback(() => resolve(import("./WaterBackground")), { timeout: 2000 });
+ } else {
+ setTimeout(() => resolve(import("./WaterBackground")), 100);
+ }
+ })
+);
+
+const SandBackground = lazy(() =>
+ new Promise(resolve => {
+ if ("requestIdleCallback" in window) {
+ requestIdleCallback(() => resolve(import("./SandBackground")), { timeout: 2000 });
+ } else {
+ setTimeout(() => resolve(import("./SandBackground")), 100);
+ }
+ })
+);
+
+
+// Fallback gradient backgrounds (CSS-only) for reduced motion or loading
+function FallbackBackground({ isDark }: { isDark: boolean }) {
+ return (
+
+ );
+}
+
+type BackgroundErrorBoundaryProps = {
+ children: ReactNode;
+ fallback: ReactNode;
+ resetKey: string;
+};
+
+type BackgroundErrorBoundaryState = {
+ hasError: boolean;
+};
+
+class BackgroundErrorBoundary extends Component<
+ BackgroundErrorBoundaryProps,
+ BackgroundErrorBoundaryState
+> {
+ state: BackgroundErrorBoundaryState = {
+ hasError: false,
+ };
+
+ static getDerivedStateFromError(): BackgroundErrorBoundaryState {
+ return { hasError: true };
+ }
+
+ componentDidUpdate(prevProps: BackgroundErrorBoundaryProps) {
+ if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
+ this.setState({ hasError: false });
+ }
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ if (import.meta.env.DEV) {
+ console.warn("Ambient background failed; using CSS fallback.", error, errorInfo);
+ }
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return this.props.fallback;
+ }
+
+ return this.props.children;
+ }
+}
+
+export default function AmbientBackground() {
+ const isDark = useDarkModeObserver();
+ const prefersReducedMotion = usePrefersReducedMotion();
+ const [isReady, setIsReady] = useState(false);
+ const [supportsWebGL, setSupportsWebGL] = useState(null);
+
+ // Delay Three.js loading until after first paint to prioritize main content
+ useEffect(() => {
+ let timerId: number | undefined;
+
+ // Use requestAnimationFrame to wait for first paint
+ const rafId = requestAnimationFrame(() => {
+ // Then use a small timeout to ensure content is interactive
+ timerId = window.setTimeout(() => {
+ setSupportsWebGL(canUseWebGL());
+ setIsReady(true);
+ }, 50);
+ });
+
+ return () => {
+ cancelAnimationFrame(rafId);
+
+ if (timerId !== undefined) {
+ window.clearTimeout(timerId);
+ }
+ };
+ }, []);
+
+ // Use static fallback if user prefers reduced motion or not ready yet
+ if (prefersReducedMotion || !isReady || supportsWebGL !== true) {
+ return ;
+ }
+
+ const fallback = ;
+ const backgroundKey = isDark ? "water" : "sand";
+
+ return (
+
+
+ {isDark ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/BeachWeatherPanel.tsx b/frontend/src/components/BeachWeatherPanel.tsx
new file mode 100644
index 0000000000..97de919ece
--- /dev/null
+++ b/frontend/src/components/BeachWeatherPanel.tsx
@@ -0,0 +1,710 @@
+import type { ReactNode } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ Cloud,
+ CloudFog,
+ CloudLightning,
+ CloudRain,
+ CloudSnow,
+ CloudSun,
+ Clock,
+ CaretDown,
+ Drop,
+ Sun,
+ Thermometer,
+ Waves,
+ type Icon,
+} from '@phosphor-icons/react';
+import { useBeaches } from '../hooks/useBeaches';
+import { useWeather } from '../hooks/useWeather';
+import { useSunTimes } from '../hooks/useSunTimes';
+import type { SunTimes, WeatherData, DailyForecast } from '../types/weather';
+
+// The API signals "does not occur" with a 1970 epoch date (Nordic midnight sun)
+function occurs(d: Date): boolean {
+ return d.getFullYear() > 1970;
+}
+
+function formatTime(d: Date): string {
+ return d.toLocaleTimeString('sv-SE', {
+ hour: '2-digit',
+ minute: '2-digit',
+ timeZone: 'Europe/Stockholm',
+ hour12: false,
+ });
+}
+
+function formatDayLength(seconds: number, t: (k: string) => string): string {
+ const h = Math.floor(seconds / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ return `${h}${t('sun.hourShort')} ${m}${t('sun.minuteShort')}`;
+}
+
+function uvColor(uv: number): string {
+ if (uv <= 2) return 'text-[var(--color-quality-good)]';
+ if (uv <= 5) return 'text-[var(--color-quality-sufficient)]';
+ if (uv <= 7) return 'text-[var(--color-uv-high)]';
+ if (uv <= 10) return 'text-[var(--color-quality-poor)]';
+ return 'text-[var(--color-uv-extreme)]';
+}
+
+function uvLabelKey(uv: number): string {
+ if (uv <= 2) return 'weather.uvLow';
+ if (uv <= 5) return 'weather.uvModerate';
+ if (uv <= 7) return 'weather.uvHigh';
+ if (uv <= 10) return 'weather.uvVeryHigh';
+ return 'weather.uvExtreme';
+}
+
+type WeatherPresentation = {
+ Icon: Icon;
+ label: string;
+ className: string;
+};
+
+function weatherPresentation(code: number, t: (k: string) => string): WeatherPresentation {
+ if (code === 0) {
+ return {
+ Icon: Sun,
+ label: t('weather.conditionClear'),
+ className:
+ 'border-amber-300/50 bg-amber-100/70 text-amber-700 dark:border-amber-300/20 dark:bg-amber-400/15 dark:text-amber-200',
+ };
+ }
+ if (code === 1) {
+ return {
+ Icon: CloudSun,
+ label: t('weather.conditionMainlyClear'),
+ className:
+ 'border-sky-300/45 bg-sky-100/60 text-sky-700 dark:border-sky-300/20 dark:bg-sky-400/15 dark:text-sky-200',
+ };
+ }
+ if (code === 2) {
+ return {
+ Icon: CloudSun,
+ label: t('weather.conditionPartlyCloudy'),
+ className:
+ 'border-sky-300/45 bg-sky-100/60 text-sky-700 dark:border-sky-300/20 dark:bg-sky-400/15 dark:text-sky-200',
+ };
+ }
+ if (code === 3) {
+ return {
+ Icon: Cloud,
+ label: t('weather.conditionOvercast'),
+ className:
+ 'border-slate-300/50 bg-slate-100/60 text-slate-600 dark:border-slate-300/20 dark:bg-slate-400/15 dark:text-slate-200',
+ };
+ }
+ if (code === 45 || code === 48) {
+ return {
+ Icon: CloudFog,
+ label: t('weather.conditionFog'),
+ className:
+ 'border-zinc-300/50 bg-zinc-100/60 text-zinc-600 dark:border-zinc-300/20 dark:bg-zinc-400/15 dark:text-zinc-200',
+ };
+ }
+ if ([51, 53, 55, 56, 57].includes(code)) {
+ return {
+ Icon: CloudRain,
+ label: t('weather.conditionDrizzle'),
+ className:
+ 'border-sky-300/50 bg-sky-100/60 text-sky-700 dark:border-sky-300/20 dark:bg-sky-400/15 dark:text-sky-200',
+ };
+ }
+ if ([61, 63, 65, 66, 67].includes(code)) {
+ return {
+ Icon: CloudRain,
+ label: t('weather.conditionRain'),
+ className:
+ 'border-blue-300/50 bg-blue-100/60 text-blue-700 dark:border-blue-300/20 dark:bg-blue-400/15 dark:text-blue-200',
+ };
+ }
+ if ([71, 73, 75, 77, 85, 86].includes(code)) {
+ return {
+ Icon: CloudSnow,
+ label: t('weather.conditionSnow'),
+ className:
+ 'border-cyan-300/50 bg-cyan-100/60 text-cyan-700 dark:border-cyan-300/20 dark:bg-cyan-400/15 dark:text-cyan-100',
+ };
+ }
+ if ([80, 81, 82].includes(code)) {
+ return {
+ Icon: CloudRain,
+ label: t('weather.conditionShowers'),
+ className:
+ 'border-blue-300/50 bg-blue-100/60 text-blue-700 dark:border-blue-300/20 dark:bg-blue-400/15 dark:text-blue-200',
+ };
+ }
+ if ([95, 96, 99].includes(code)) {
+ return {
+ Icon: CloudLightning,
+ label: t('weather.conditionThunderstorm'),
+ className:
+ 'border-orange-300/60 bg-orange-100/70 text-orange-800 dark:border-orange-300/25 dark:bg-orange-400/15 dark:text-orange-200',
+ };
+ }
+ return {
+ Icon: Cloud,
+ label: t('weather.conditionOvercast'),
+ className:
+ 'border-slate-300/50 bg-slate-100/60 text-slate-600 dark:border-slate-300/20 dark:bg-slate-400/15 dark:text-slate-200',
+ };
+}
+
+function ConditionMark({
+ presentation,
+ size = 'md',
+}: {
+ presentation: WeatherPresentation;
+ size?: 'md' | 'lg';
+}) {
+ const WeatherIcon = presentation.Icon;
+ const sizeClass = size === 'lg' ? 'size-14' : 'size-9';
+ const iconSize = size === 'lg' ? 30 : 21;
+
+ return (
+
+
+
+ );
+}
+
+function MetricTile({
+ icon: MetricIcon,
+ label,
+ value,
+ detail,
+ valueClassName = 'text-ink',
+}: {
+ icon: Icon;
+ label: string;
+ value: ReactNode;
+ detail?: ReactNode;
+ valueClassName?: string;
+}) {
+ return (
+
+
+
+ {label}
+
+
+ {value}
+
+ {detail &&
{detail}
}
+
+ );
+}
+
+// Returns whole-hour marks (every 6h for long days, every 3h for short)
+// that fall within [barStart, barEnd], expressed as { pct, label }.
+function buildHourMarks(barStart: number, barEnd: number) {
+ const spanHours = (barEnd - barStart) / 3_600_000;
+ const step = spanHours > 16 ? 6 : spanHours > 10 ? 3 : 2;
+ const fmt = new Intl.DateTimeFormat('en-US', {
+ timeZone: 'Europe/Stockholm',
+ hour: 'numeric',
+ hour12: false,
+ });
+
+ const marks: { pct: number; label: string }[] = [];
+ // Iterate every UTC hour in the window; keep those whose Stockholm hour
+ // is a multiple of `step` and falls comfortably inside the bar.
+ const startH = Math.floor(barStart / 3_600_000);
+ const endH = Math.ceil(barEnd / 3_600_000);
+ for (let h = startH; h <= endH; h++) {
+ const ms = h * 3_600_000;
+ const seHour = Number(fmt.format(new Date(ms))) % 24; // guard against "24"
+ if (seHour % step === 0) {
+ const p = ((ms - barStart) / (barEnd - barStart)) * 100;
+ if (p > 3 && p < 97) {
+ marks.push({ pct: p, label: String(seHour).padStart(2, '0') });
+ }
+ }
+ }
+ return marks;
+}
+
+// --- Sun arc bar ---
+// Gradient strip from civil twilight begin to end with golden-hour zones,
+// fixed hour-reference ticks, and a live "now" dot.
+function SunArc({ sun, t }: { sun: SunTimes; t: (k: string) => string }) {
+ const barStart = sun.civilTwilightBegin.getTime();
+ const barEnd = sun.civilTwilightEnd.getTime();
+ const span = barEnd - barStart;
+ if (span <= 0) return null;
+
+ function pct(d: Date) {
+ return Math.max(0, Math.min(100, ((d.getTime() - barStart) / span) * 100));
+ }
+
+ const srPct = pct(sun.sunrise);
+ const gmPct = pct(sun.goldenHourMorningEnd);
+ const gePct = pct(sun.goldenHourEveningStart);
+ const ssPct = pct(sun.sunset);
+ const nowPct = pct(new Date());
+ const isNow = nowPct > 0 && nowPct < 100;
+
+ const gradient = `linear-gradient(to right,
+ #334155 0%,
+ #334155 ${srPct.toFixed(1)}%,
+ #f59e0b ${srPct.toFixed(1)}%,
+ #fde68a ${gmPct.toFixed(1)}%,
+ #fffbeb ${((gmPct + gePct) / 2).toFixed(1)}%,
+ #fde68a ${gePct.toFixed(1)}%,
+ #f59e0b ${ssPct.toFixed(1)}%,
+ #334155 ${ssPct.toFixed(1)}%,
+ #334155 100%
+ )`;
+
+ const hourMarks = buildHourMarks(barStart, barEnd);
+
+ return (
+
+
+
+ {t('sun.civilTwilightBegin')}
+
+ {formatTime(sun.civilTwilightBegin)}
+
+
+
+ {t('sun.civilTwilightEnd')}
+
+ {formatTime(sun.civilTwilightEnd)}
+
+
+
+
+
+ {hourMarks.map(({ pct: p }) => (
+
+ ))}
+
+
+ {isNow && (
+
+ )}
+
+
+
+ {hourMarks.map(({ pct: p, label }) => (
+
+ {label}
+
+ ))}
+
+
+
+
+
{t('sun.sunrise')}
+
+ {formatTime(sun.sunrise)}
+
+
+
+
{t('sun.sunset')}
+
+ {formatTime(sun.sunset)}
+
+
+
+
+ );
+}
+
+// --- 5-day forecast strip ---
+function ForecastStrip({
+ forecast,
+ lang,
+ t,
+}: {
+ forecast: DailyForecast[];
+ lang: string;
+ t: (k: string) => string;
+}) {
+ if (!forecast.length) return null;
+ const weekdayFmt = new Intl.DateTimeFormat(lang === 'en' ? 'en-GB' : 'sv-SE', {
+ weekday: 'short',
+ timeZone: 'Europe/Stockholm',
+ });
+ const days = forecast.slice(0, 5);
+ const today = days[0];
+ const todayRain =
+ today?.precipProbability == null
+ ? t('weather.notAvailable')
+ : `${Math.round(today.precipProbability)}%`;
+
+ return (
+
+
+
+
+
+ {t('weather.forecast')}
+
+ {today && (
+
+ {t('weather.today')}: {Math.round(today.tempMax)}Β° / {todayRain}
+
+ )}
+
+
+
+
+
+ {days.map((d, i) => (
+
+ ))}
+
+
+
+
+
+
{t('weather.forecast')}
+
+ {t('weather.high')}
+ {t('weather.low')}
+ {t('weather.rain')}
+
+
+
+ {days.map((d, i) => (
+
+ ))}
+
+
+
+ );
+}
+
+function ForecastDay({
+ day: d,
+ index,
+ weekdayFmt,
+ t,
+ compact = false,
+}: {
+ day: DailyForecast;
+ index: number;
+ weekdayFmt: Intl.DateTimeFormat;
+ t: (k: string) => string;
+ compact?: boolean;
+}) {
+ const day = new Date(`${d.date}T12:00:00`);
+ const condition = weatherPresentation(d.weatherCode, t);
+ const rain =
+ d.precipProbability == null
+ ? t('weather.notAvailable')
+ : `${Math.round(d.precipProbability)}%`;
+
+ if (compact) {
+ return (
+
+
+
+
+
+ {index === 0 ? t('weather.today') : weekdayFmt.format(day)}
+
+
{condition.label}
+
+
+
+ {Math.round(d.tempMax)}Β° / {Math.round(d.tempMin)}Β°
+
+
+
+ {rain}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {index === 0 ? t('weather.today') : weekdayFmt.format(day)}
+
+
+
+
+
+
+
+
+
{t('weather.high')}
+
+ {Math.round(d.tempMax)}Β°
+
+
+
+
{t('weather.low')}
+
+ {Math.round(d.tempMin)}Β°
+
+
+
+
{t('weather.rain')}
+
+
+ {rain}
+
+
+
+
+ );
+}
+
+// --- Weather card ---
+function WeatherCard({
+ weather,
+ lang,
+ t,
+}: {
+ weather: WeatherData;
+ lang: string;
+ t: (k: string) => string;
+}) {
+ const uv = weather.uvIndex;
+ const condition = weatherPresentation(weather.weatherCode, t);
+
+ return (
+
+
+
+
+
+
+
{t('weather.title')}
+
{condition.label}
+
+
+
+
+ {Math.round(weather.temperature)}Β°
+
+
{t('weather.temperature')}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+// --- Sun times card ---
+function SunCard({ sun, t }: { sun: SunTimes; t: (k: string) => string }) {
+ return (
+
+
+
{t('sun.title')}
+
+
+ {t('sun.dayLength')}
+
+ {formatDayLength(sun.dayLengthSeconds, t)}
+
+
+
+
+
+
+
+
+
+
+ {t('sun.goldenHour')}
+
+
+
+
{t('sun.morning')}
+
+ {formatTime(sun.sunrise)} - {formatTime(sun.goldenHourMorningEnd)}
+
+
+
+
{t('sun.evening')}
+
+ {formatTime(sun.goldenHourEveningStart)} - {formatTime(sun.sunset)}
+
+
+
+
+
+
+
+
+
{t('sun.civilTwilightBegin')}
+
{formatTime(sun.civilTwilightBegin)}
+
+
+
{t('sun.civilTwilightEnd')}
+
{formatTime(sun.civilTwilightEnd)}
+
+
+ {occurs(sun.nauticalTwilightBegin) && (
+ <>
+
+
{t('sun.nauticalTwilightBegin')}
+
{formatTime(sun.nauticalTwilightBegin)}
+
+
+
{t('sun.nauticalTwilightEnd')}
+
{formatTime(sun.nauticalTwilightEnd)}
+
+ >
+ )}
+
+
+
+
+ );
+}
+
+// --- Panel (entry point used by BeachDetailPage) ---
+interface Props {
+ beachId: string;
+ lat?: number;
+ lon?: number;
+}
+
+export default function BeachWeatherPanel({ beachId, lat, lon }: Props) {
+ const { t, i18n } = useTranslation();
+ const lang = i18n.language;
+ const { data: beaches, isLoading: beachesLoading } = useBeaches();
+ const beach = beaches?.find((b) => b.id === beachId);
+ const resolvedLat = beach?.lat ?? lat;
+ const resolvedLon = beach?.lon ?? lon;
+
+ const { data: weather, isLoading: wLoading, isError: wError } = useWeather(
+ resolvedLat,
+ resolvedLon
+ );
+ const { data: sun, isLoading: sLoading, isError: sError } = useSunTimes(
+ resolvedLat,
+ resolvedLon
+ );
+
+ const hasCoords = resolvedLat != null && resolvedLon != null;
+
+ return (
+
+ {/* Weather */}
+ {(wLoading || (beachesLoading && !hasCoords)) && (
+
+ )}
+ {!beachesLoading && !hasCoords && (
+
+
{t('weather.locationUnavailable')}
+
+ )}
+ {wError && (
+
+ )}
+ {weather &&
}
+
+ {/* Sun times */}
+ {sLoading && (
+
+ )}
+ {sError && (
+
+ )}
+ {sun &&
}
+
+ );
+}
diff --git a/frontend/src/components/BeachesList.tsx b/frontend/src/components/BeachesList.tsx
new file mode 100644
index 0000000000..0071846103
--- /dev/null
+++ b/frontend/src/components/BeachesList.tsx
@@ -0,0 +1,396 @@
+// frontend/src/components/BeachesList.tsx
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import {
+ ArrowRight,
+ Crosshair,
+ MapPin,
+ NavigationArrow,
+ XCircle,
+ Waves,
+} from "@phosphor-icons/react";
+import MapView from "./MapView";
+
+import { fetchBeaches } from "../api/beaches";
+import { BeachSummary } from "../types/beaches";
+import { useGeolocation } from "../hooks/useGeolocation";
+import { distanceKm, formatKm } from "../utils/geo";
+import { useUI } from "../store/ui";
+
+// Defaults
+const SERGELS_TORG = { lat: 59.3326, lon: 18.0649 };
+const DEFAULT_RADIUS_KM = 10;
+const NEARBY_RADIUS_KM = 5;
+
+type Mode = "default" | "nearby" | "viewport";
+
+export default function BeachesList() {
+ const { t } = useTranslation();
+ const { data, isLoading, isError, error, refetch } = useQuery({
+ queryKey: ["beaches"],
+ queryFn: fetchBeaches,
+ staleTime: 5 * 60 * 1000,
+ });
+
+ // Global search text
+ const search = useUI((s) => s.search);
+ const setSearch = useUI((s) => s.setSearch);
+ const homeResetVersion = useUI((s) => s.homeResetVersion);
+ const q = search.trim().toLowerCase();
+
+ // Geo hook
+ const {
+ coords,
+ loading: geoLoading,
+ error: geoError,
+ request,
+ } = useGeolocation();
+
+ // View state (center/radius or viewport bounds)
+ const [mode, setMode] = useState("default");
+ const [center, setCenter] = useState(SERGELS_TORG);
+ const [radiusKm, setRadiusKm] = useState(DEFAULT_RADIUS_KM);
+ const [bounds, setBounds] = useState<{
+ west: number;
+ south: number;
+ east: number;
+ north: number;
+ } | null>(null);
+
+ // Distance-annotated list (stable type)
+ const items = useMemo<(BeachSummary & { _distanceKm?: number })[]>(() => {
+ if (!data) return [];
+ const refPoint = mode === "viewport" ? null : center;
+ const withDist = data.map((b) => {
+ const km = refPoint
+ ? distanceKm(
+ { lat: refPoint.lat, lon: refPoint.lon },
+ { lat: b.lat, lon: b.lon }
+ )
+ : undefined;
+ return { ...b, _distanceKm: km };
+ });
+ return refPoint
+ ? withDist.sort((a, b) => (a._distanceKm ?? 0) - (b._distanceKm ?? 0))
+ : withDist;
+ }, [data, center, mode]);
+
+ // Text filter
+ const filteredBySearch = useMemo(() => {
+ if (!q) return items;
+ return items.filter((b) => {
+ const hay = `${b.name} ${b.municipality ?? ""}`.toLowerCase();
+ return hay.includes(q);
+ });
+ }, [items, q]);
+
+ // Radius or viewport filter (skip if user is searching)
+ const filtered = useMemo(() => {
+ // If user is searching, don't filter by map bounds
+ if (q) {
+ return filteredBySearch;
+ }
+
+ if (mode === "viewport" && bounds) {
+ return filteredBySearch.filter(
+ (b) =>
+ b.lon >= bounds.west &&
+ b.lon <= bounds.east &&
+ b.lat >= bounds.south &&
+ b.lat <= bounds.north
+ );
+ }
+ return filteredBySearch.filter((b) => {
+ if (mode === "default" || mode === "nearby") {
+ const km = distanceKm(center, { lat: b.lat, lon: b.lon });
+ return km <= radiusKm;
+ }
+ return true;
+ });
+ }, [filteredBySearch, mode, bounds, center, radiusKm, q]);
+
+ const resetToStart = useCallback(() => {
+ setSearch("");
+ setCenter(SERGELS_TORG);
+ setRadiusKm(DEFAULT_RADIUS_KM);
+ setBounds(null);
+ setMode("default");
+ }, [setSearch]);
+
+ const activateNearby = useCallback((location: { lat: number; lon: number }) => {
+ setCenter({ lat: location.lat, lon: location.lon });
+ setRadiusKm(NEARBY_RADIUS_KM);
+ setBounds(null);
+ setMode("nearby");
+ }, []);
+
+ useEffect(() => {
+ resetToStart();
+ }, [homeResetVersion, resetToStart]);
+
+ // Geolocation -> nearby mode
+ useEffect(() => {
+ if (!coords) return;
+ activateNearby(coords);
+ }, [coords, activateNearby]);
+
+ const handleUseLocation = async () => {
+ if (coords) activateNearby(coords);
+ await request();
+ };
+
+ // Map move -> viewport mode
+ const handleMoveEnd = (e: {
+ bounds: { west: number; south: number; east: number; north: number };
+ center: { lon: number; lat: number };
+ zoom: number;
+ }) => {
+ setBounds(e.bounds);
+ setMode("viewport");
+ };
+
+ const mapFocus =
+ mode === "viewport" || q
+ ? undefined
+ : { center: { lon: center.lon, lat: center.lat }, radiusKm };
+
+ const modeLabel =
+ q.length > 0
+ ? t("home.modeSearch")
+ : mode === "nearby"
+ ? t("home.modeNearby")
+ : mode === "viewport"
+ ? t("home.modeViewport")
+ : t("home.modeDefault");
+ const hasNoFilteredResults = (q || mode !== "default") && filtered.length === 0;
+
+ /* ---------- STATES ---------- */
+ if (isLoading) {
+ return (
+
+
{t("loadingBeaches")}
+
+
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+
{t("beachesList.loadError")}
+
+ {(error as Error)?.message ?? t("beachesList.pleaseRetry")}
+
+
refetch()}
+ className="mt-3 w-full px-3 py-3 rounded-2xl border border-border bg-surface hover:bg-surface-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
+ >
+ {t("beachesList.retry")}
+
+
+ );
+ }
+
+ if (!items.length) {
+ return (
+
+
+ {t("beachesList.noBeachesFound")}
+
+
+ {t("beachesList.emptyState")}
+
+
+ );
+ }
+
+ /* ---------- UI ---------- */
+ return (
+
+
+
+
+
+
+ {t("home.badge")}
+
+
+
+ {t("home.title")}
+
+
+ {t("home.subtitle")}
+
+
+
+
+
+
+
{t("home.visible")}
+
+ {filtered.length}
+
+
+
+
{t("home.mode")}
+
+ {modeLabel}
+
+
+
+
+
+
+
({
+ id: b.id,
+ name: b.name,
+ lat: b.lat,
+ lon: b.lon,
+ }))}
+ focus={mapFocus}
+ onMoveEnd={handleMoveEnd}
+ />
+
+
+
+ {geoLoading ? (
+ <>
+
+ {t("beachesList.gettingLocation")}
+ >
+ ) : (
+ <>
+
+ {t("beachesList.requestLocation")}
+ >
+ )}
+
+ {geoError && (
+
+ {t("beachesList.locationError")}
+
+ )}
+
+
+
+
+ {/* Screen reader announcement for filtered results */}
+
+ {filtered.length} {t("beaches")}
+
+
+
+
+
{t("home.resultsTitle")}
+
+ {t("home.resultsMeta", {
+ visible: filtered.length,
+ total: items.length,
+ })}
+
+
+
+
+ {modeLabel}
+
+
+
+ {hasNoFilteredResults ? (
+
+
+
+
+
+ {t("beachesList.noMatches")}
+
+
+ {q
+ ? t("beachesList.emptyState")
+ : mode === "viewport"
+ ? t("beachesList.noMatchesHelp")
+ : t("beachesList.widenRadius")}
+
+
+
+
+ {t("beachesList.resetToStart")}
+
+ {coords && (
+ activateNearby(coords)}
+ >
+ {t("beachesList.showNearby5km")}
+
+ )}
+
+
+
+ ) : (
+
+ {filtered.map((b) => (
+
+
+
+
+
+
+ {t("home.euSite")}
+
+
+
+
+ {b.name}
+
+
+
+ {b.municipality || "β"}
+
+
+
+ {t("home.openDetails")}
+ {typeof b._distanceKm === "number" && (
+
+ {formatKm(b._distanceKm)}
+
+ )}
+
+
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+function CardSkeleton() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx
new file mode 100644
index 0000000000..9f5a9d3f8a
--- /dev/null
+++ b/frontend/src/components/ErrorBoundary.tsx
@@ -0,0 +1,74 @@
+import React, { Component, ErrorInfo, ReactNode } from "react";
+import { Link } from "react-router";
+import i18n from "../i18n";
+
+interface Props {
+ children: ReactNode;
+}
+
+interface State {
+ hasError: boolean;
+ error: Error | null;
+}
+
+export default class ErrorBoundary extends Component {
+ public state: State = {
+ hasError: false,
+ error: null,
+ };
+
+ public static getDerivedStateFromError(error: Error): State {
+ return { hasError: true, error };
+ }
+
+ public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ console.error("ErrorBoundary caught an error:", error, errorInfo);
+ }
+
+ public render() {
+ if (this.state.hasError) {
+ const t = (key: string) => i18n.t(key);
+ return (
+
+
+
+
+ {t("errorBoundary.title")}
+
+
+ {t("errorBoundary.message")}
+
+ {this.state.error && (
+
+
+ {t("errorBoundary.technicalDetails")}
+
+
+ {this.state.error.toString()}
+
+
+ )}
+
+ window.location.reload()}
+ className="btn btn-primary"
+ >
+ {t("errorBoundary.reloadPage")}
+
+
+ {t("errorBoundary.goHome")}
+
+
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx
new file mode 100644
index 0000000000..e589ee61fb
--- /dev/null
+++ b/frontend/src/components/Header.tsx
@@ -0,0 +1,444 @@
+import { useEffect, useRef, useState, useMemo, useCallback } from "react";
+import { Link, useLocation, useNavigate } from "react-router";
+import { useTranslation } from "react-i18next";
+import { useUI } from "../store/ui";
+import { useAuth } from "@/store/auth";
+import { useQueryClient, useQuery } from "@tanstack/react-query";
+import { fetchBeaches } from "../api/beaches";
+import MenuIcon from "../assets/menu_icon.svg?react";
+import SwimmerUserIcon from "../assets/user_icon.svg?react";
+import {
+ GearSix,
+ Heart,
+ Moon,
+ SignIn,
+ SignOut,
+ Sun,
+ UserPlus,
+} from "@phosphor-icons/react";
+import { useOutsideClose } from "../hooks/useOutsideClose";
+import { useToggleDarkMode } from "../hooks/useToggleDarkMode";
+
+type HeaderProps = {
+ languageSwitcher?: React.ReactNode;
+};
+
+export default function Header({ languageSwitcher }: HeaderProps) {
+ const { t } = useTranslation();
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [menuOpen, setMenuOpen] = useState(false);
+ const [userOpen, setUserOpen] = useState(false);
+ const [searchOpen, setSearchOpen] = useState(false);
+ // Index of the keyboard-focused option in the combobox (-1 = none)
+ const [activeIndex, setActiveIndex] = useState(-1);
+
+ // Refs for trigger buttons to return focus when menu closes
+ const menuButtonRef = useRef(null);
+ const userButtonRef = useRef(null);
+
+ // Close handlers that return focus to trigger buttons
+ const closeMenu = useCallback(() => {
+ setMenuOpen(false);
+ menuButtonRef.current?.focus();
+ }, []);
+
+ const closeUser = useCallback(() => {
+ setUserOpen(false);
+ userButtonRef.current?.focus();
+ }, []);
+
+ const menuRef = useOutsideClose(closeMenu, menuOpen);
+ const userRef = useOutsideClose(closeUser, userOpen);
+ const searchRef = useOutsideClose(
+ () => setSearchOpen(false),
+ searchOpen
+ );
+ const { isDark, setIsDark } = useToggleDarkMode();
+
+ const search = useUI((s) => s.search);
+ const setSearch = useUI((s) => s.setSearch);
+ const resetHomeView = useUI((s) => s.resetHomeView);
+
+ const status = useAuth((s) => s.status);
+ const logout = useAuth((s) => s.logout);
+ const authed = status === "authenticated";
+ const qc = useQueryClient();
+
+ // Fetch beaches for search dropdown
+ const { data: beaches } = useQuery({
+ queryKey: ["beaches"],
+ queryFn: fetchBeaches,
+ staleTime: 5 * 60 * 1000,
+ });
+
+ // Filter beaches based on search
+ const filteredBeaches = useMemo(() => {
+ if (!search.trim() || !beaches) return [];
+ const q = search.trim().toLowerCase();
+ return beaches
+ .filter((b) => {
+ const hay = `${b.name} ${b.municipality ?? ""}`.toLowerCase();
+ return hay.includes(q);
+ })
+ .slice(0, 10); // Limit to 10 results
+ }, [search, beaches]);
+
+ async function handleLogout() {
+ await logout();
+ qc.clear(); // clear cached queries (favorites, etc.)
+ setUserOpen(false);
+ }
+
+ // Handle search input change
+ function handleSearchChange(value: string) {
+ setSearch(value);
+ setSearchOpen(!!value); // Show dropdown when there's input
+ setActiveIndex(-1); // reset keyboard selection on new input
+ }
+
+ // Handle beach selection from dropdown
+ function handleBeachSelect(beachId: string) {
+ setSearch(""); // Clear search
+ setSearchOpen(false);
+ setActiveIndex(-1);
+ navigate(`/beach/${beachId}`);
+ }
+
+ // ARIA combobox keyboard interaction (APG combobox-with-listbox pattern)
+ function handleSearchKeyDown(e: React.KeyboardEvent) {
+ const count = filteredBeaches.length;
+ switch (e.key) {
+ case "ArrowDown":
+ if (count === 0) return;
+ e.preventDefault();
+ setSearchOpen(true);
+ setActiveIndex((i) => (i + 1) % count);
+ break;
+ case "ArrowUp":
+ if (count === 0) return;
+ e.preventDefault();
+ setSearchOpen(true);
+ setActiveIndex((i) => (i <= 0 ? count - 1 : i - 1));
+ break;
+ case "Enter":
+ if (searchOpen && activeIndex >= 0 && activeIndex < count) {
+ e.preventDefault();
+ handleBeachSelect(filteredBeaches[activeIndex].id);
+ }
+ break;
+ case "Escape":
+ if (searchOpen) {
+ e.preventDefault();
+ setSearchOpen(false);
+ setActiveIndex(-1);
+ }
+ break;
+ }
+ }
+
+ const listboxId = "beach-search-listbox";
+ const activeOptionId =
+ searchOpen && activeIndex >= 0 && activeIndex < filteredBeaches.length
+ ? `beach-search-option-${filteredBeaches[activeIndex].id}`
+ : undefined;
+
+ return (
+ <>
+ {/* Skip navigation link for accessibility */}
+
+ {t("header.skipToMain")}
+
+
+ >
+ );
+}
+
+function MenuLink({
+ to,
+ children,
+ onClick,
+ icon,
+}: {
+ to: string;
+ children: React.ReactNode;
+ onClick?: () => void;
+ icon?: React.ReactNode;
+}) {
+ return (
+
+ {icon && {icon} }
+ {children}
+
+ );
+}
diff --git a/frontend/src/components/LanguageSwitcher.tsx b/frontend/src/components/LanguageSwitcher.tsx
new file mode 100644
index 0000000000..b6d7638da9
--- /dev/null
+++ b/frontend/src/components/LanguageSwitcher.tsx
@@ -0,0 +1,38 @@
+import { useTranslation } from "react-i18next";
+
+export default function LanguageSwitcher() {
+ const { i18n } = useTranslation();
+ const current = i18n.language.startsWith("en") ? "en" : "sv";
+
+ function setLang(lang: "sv" | "en") {
+ i18n.changeLanguage(lang);
+ localStorage.setItem("lang", lang);
+ }
+
+ return (
+
+ setLang("sv")}
+ aria-pressed={current === "sv"}
+ className={`px-2.5 py-1 rounded-lg text-sm transition-colors ${
+ current === "sv"
+ ? "bg-accent text-white dark:text-[#07202e] font-medium"
+ : "text-ink hover:bg-surface-muted"
+ }`}
+ >
+ SV
+
+ setLang("en")}
+ aria-pressed={current === "en"}
+ className={`px-2.5 py-1 rounded-lg text-sm transition-colors ${
+ current === "en"
+ ? "bg-accent text-white dark:text-[#07202e] font-medium"
+ : "text-ink hover:bg-surface-muted"
+ }`}
+ >
+ EN
+
+
+ );
+}
diff --git a/frontend/src/components/MapView.tsx b/frontend/src/components/MapView.tsx
new file mode 100644
index 0000000000..6860f72005
--- /dev/null
+++ b/frontend/src/components/MapView.tsx
@@ -0,0 +1,525 @@
+// frontend/src/components/MapView.tsx
+import { MapTrifold } from "@phosphor-icons/react";
+import { useEffect, useRef, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router";
+import maplibregl, { Map, LngLatBoundsLike } from "maplibre-gl";
+import "maplibre-gl/dist/maplibre-gl.css";
+import { canUseWebGL } from "../utils/webgl";
+
+type Point = { id: string; name: string; lat: number; lon: number };
+type Props = {
+ points?: Point[];
+ /** If provided, the map fits to this center (+ optional radiusKm) */
+ focus?: { center: { lon: number; lat: number }; radiusKm?: number };
+ /** Fires only on *user*-initiated pan/zoom (not programmatic fits) */
+ onMoveEnd?: (args: {
+ bounds: { west: number; south: number; east: number; north: number };
+ center: { lon: number; lat: number };
+ zoom: number;
+ }) => void;
+ /** Fires after programmatic fits */
+ onFitBounds?: (args: {
+ bounds: { west: number; south: number; east: number; north: number };
+ center: { lon: number; lat: number };
+ zoom: number;
+ }) => void;
+};
+
+// The MapTiler key is a publishable client-side key β keep it in env (not in
+// source) and restrict it by allowed origin in the MapTiler dashboard. The
+// style IDs themselves are not secret.
+const MAPTILER_KEY = import.meta.env.VITE_MAPTILER_KEY;
+if (!MAPTILER_KEY) {
+ console.warn(
+ "VITE_MAPTILER_KEY is not set β the map will fail to load. See frontend/.env.example."
+ );
+}
+const STYLE_LIGHT_ID = "019951a8-6432-7aea-b555-2ac65a59181f";
+const STYLE_DARK_ID = "019951b2-24e8-7a45-8534-0731061b7984";
+const STYLE_LIGHT = `https://api.maptiler.com/maps/${STYLE_LIGHT_ID}/style.json?key=${MAPTILER_KEY}`;
+const STYLE_DARK = `https://api.maptiler.com/maps/${STYLE_DARK_ID}/style.json?key=${MAPTILER_KEY}`;
+
+function isDark() {
+ return document.documentElement.classList.contains("dark");
+}
+function getAccent() {
+ const v = getComputedStyle(document.documentElement)
+ .getPropertyValue("--color-accent")
+ .trim();
+ return v.includes(" ") ? `rgb(${v})` : v || "#0a5a82";
+}
+function styleForTheme() {
+ return isDark() ? STYLE_DARK : STYLE_LIGHT;
+}
+function getPopupStyles() {
+ const dark = isDark();
+ return {
+ background: dark ? "#1f2937" : "#ffffff", // gray-800 : white
+ color: dark ? "#f9fafb" : "#111827", // gray-50 : gray-900
+ borderColor: dark ? "#374151" : "#e5e7eb", // gray-700 : gray-200
+ };
+}
+
+function toGeoJSON(
+ points: Point[]
+): GeoJSON.FeatureCollection {
+ return {
+ type: "FeatureCollection",
+ features: points.map((p) => ({
+ type: "Feature",
+ geometry: { type: "Point", coordinates: [p.lon, p.lat] },
+ properties: { id: p.id, name: p.name },
+ })),
+ };
+}
+
+// compute bounds from center + radius (km)
+function circleBounds(
+ center: { lon: number; lat: number },
+ radiusKm: number
+): LngLatBoundsLike {
+ const lat = center.lat;
+ const dLat = radiusKm / 111; // ~111 km per deg
+ const dLon = radiusKm / (111 * Math.cos((lat * Math.PI) / 180));
+ const west = center.lon - dLon;
+ const east = center.lon + dLon;
+ const south = center.lat - dLat;
+ const north = center.lat + dLat;
+ return [
+ [west, south],
+ [east, north],
+ ];
+}
+
+function MapFallback({ message }: { message: string }) {
+ return (
+
+ );
+}
+
+export default function MapView({
+ points = [],
+ focus,
+ onMoveEnd,
+ onFitBounds,
+}: Props) {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const ref = useRef(null);
+ const mapRef = useRef(null);
+ const [mapUnavailable, setMapUnavailable] = useState(false);
+ const pointsRef = useRef(points);
+ pointsRef.current = points; // keep latest data for (re)builds without re-binding
+ const onMoveEndRef = useRef(onMoveEnd);
+ onMoveEndRef.current = onMoveEnd; // keep latest callback without re-binding listeners
+ const onFitBoundsRef = useRef(onFitBounds);
+ onFitBoundsRef.current = onFitBounds;
+ const navigateRef = useRef(navigate);
+ navigateRef.current = navigate;
+
+ // Init map ONCE
+ useEffect(() => {
+ if (!ref.current || mapUnavailable) return;
+
+ if (!canUseWebGL()) {
+ setMapUnavailable(true);
+ return;
+ }
+
+ let map: Map;
+
+ try {
+ map = new maplibregl.Map({
+ container: ref.current,
+ style: styleForTheme(),
+ center: [15, 62],
+ zoom: 4.3,
+ minZoom: 3,
+ maxZoom: 18, // Increased to allow more detailed zoom
+ dragRotate: false,
+ pitchWithRotate: false,
+ attributionControl: { compact: true },
+ });
+ } catch (error) {
+ if (import.meta.env.DEV) {
+ console.warn("Map failed to initialize; using fallback.", error);
+ }
+ setMapUnavailable(true);
+ return;
+ }
+
+ mapRef.current = map;
+
+ const handleMapError = (event: maplibregl.ErrorEvent) => {
+ const message =
+ event.error instanceof Error
+ ? event.error.message
+ : String(event.error ?? "");
+
+ if (message.toLowerCase().includes("webgl")) {
+ setMapUnavailable(true);
+ }
+ };
+
+ map.on("error", handleMapError);
+
+ // Add the beaches source + layers. Idempotent so it can run on first load
+ // and again after a style swap (setStyle wipes custom sources/layers).
+ function addBeachLayers() {
+ if (!map.isStyleLoaded() || map.getSource("beaches")) return;
+ const accent = getAccent();
+
+ map.addSource("beaches", {
+ type: "geojson",
+ data: toGeoJSON(pointsRef.current),
+ cluster: true,
+ clusterMaxZoom: 17, // Max zoom to cluster points on
+ clusterRadius: 40, // Radius of each cluster
+ });
+
+ // Cluster circle layer
+ map.addLayer({
+ id: "clusters",
+ type: "circle",
+ source: "beaches",
+ filter: ["has", "point_count"],
+ paint: {
+ "circle-color": accent,
+ "circle-radius": [
+ "step",
+ ["get", "point_count"],
+ 15, // radius for clusters with < 10 points
+ 10,
+ 20, // radius for clusters with 10-99 points
+ 30,
+ 25, // radius for clusters with 100+ points
+ ],
+ "circle-stroke-width": 2,
+ "circle-stroke-color": isDark()
+ ? "rgba(0, 0, 0, 0.6)"
+ : "rgba(255, 255, 255, 0.85)",
+ },
+ });
+
+ // Cluster count label
+ map.addLayer({
+ id: "cluster-count",
+ type: "symbol",
+ source: "beaches",
+ filter: ["has", "point_count"],
+ layout: {
+ "text-field": "{point_count_abbreviated}",
+ "text-font": ["Noto Sans Bold", "Noto Sans"],
+ "text-size": 13,
+ },
+ paint: {
+ "text-color": "#ffffff",
+ "text-halo-color": "rgba(0, 0, 0, 0.3)",
+ "text-halo-width": 1,
+ },
+ });
+
+ // Individual point β large transparent hit area (44px touch target)
+ map.addLayer({
+ id: "unclustered-point",
+ type: "circle",
+ source: "beaches",
+ filter: ["!", ["has", "point_count"]],
+ paint: {
+ "circle-color": accent,
+ "circle-radius": 22, // 44px diameter touch target (WCAG)
+ "circle-stroke-width": 2,
+ "circle-stroke-color": isDark()
+ ? "rgba(0, 0, 0, 0.6)"
+ : "rgba(255, 255, 255, 0.85)",
+ "circle-opacity": 0.2,
+ "circle-stroke-opacity": 1,
+ },
+ });
+
+ // Visual marker on top (smaller, fully visible)
+ map.addLayer({
+ id: "unclustered-point-visual",
+ type: "circle",
+ source: "beaches",
+ filter: ["!", ["has", "point_count"]],
+ paint: {
+ "circle-color": accent,
+ "circle-radius": 8,
+ "circle-stroke-width": 2,
+ "circle-stroke-color": isDark()
+ ? "rgba(0, 0, 0, 0.6)"
+ : "rgba(255, 255, 255, 0.85)",
+ },
+ });
+ }
+
+ map.on("load", () => {
+ addBeachLayers();
+ setTimeout(() => map.resize(), 0);
+ });
+
+ // Dark/light swap only when theme class changes. setStyle drops custom
+ // sources/layers, so re-add them once the new style settles.
+ const obs = new MutationObserver(() => {
+ map.setStyle(styleForTheme());
+ map.once("idle", addBeachLayers);
+ });
+ obs.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ["class"],
+ });
+
+ // Only count *user* moves
+ let userMoving = false;
+ map.on("movestart", (e) => {
+ userMoving = !!(e as any).originalEvent; // programmatic fits won't set this
+ });
+ map.on("moveend", () => {
+ const b = map.getBounds();
+ const c = map.getCenter();
+ const boundsObj = {
+ bounds: {
+ west: b.getWest(),
+ south: b.getSouth(),
+ east: b.getEast(),
+ north: b.getNorth(),
+ },
+ center: { lon: c.lng, lat: c.lat },
+ zoom: map.getZoom(),
+ };
+
+ if (userMoving) {
+ // User-initiated move
+ userMoving = false;
+ if (onMoveEndRef.current) {
+ onMoveEndRef.current(boundsObj);
+ }
+ } else if (onFitBoundsRef.current) {
+ // Programmatic fit
+ onFitBoundsRef.current(boundsObj);
+ }
+ });
+
+ // ββ Interaction handlers β registered ONCE. They are layer-id scoped, so
+ // they survive style swaps and data updates without piling up. ββββββββββ
+
+ // Click on a cluster - zoom in or show list if can't expand
+ map.on("click", "clusters", (e) => {
+ const features = map.queryRenderedFeatures(e.point, {
+ layers: ["clusters"],
+ });
+ if (
+ !features.length ||
+ !features[0].geometry ||
+ features[0].geometry.type !== "Point"
+ )
+ return;
+
+ const clusterId = features[0].properties?.cluster_id;
+ const pointCount = features[0].properties?.point_count;
+ const coordinates = features[0].geometry.coordinates.slice() as [
+ number,
+ number
+ ];
+ const source = map.getSource("beaches") as maplibregl.GeoJSONSource;
+
+ const openCluster = async () => {
+ try {
+ const zoom = await source.getClusterExpansionZoom(clusterId);
+
+ // If cluster can't expand further (at max zoom), show popup with all beaches
+ if (zoom >= map.getMaxZoom() || zoom === map.getZoom()) {
+ const leaves = await source.getClusterLeaves(
+ clusterId,
+ pointCount || 100,
+ 0
+ );
+ if (!leaves) return;
+
+ const styles = getPopupStyles();
+ const container = document.createElement("div");
+ container.style.maxHeight = "200px";
+ container.style.overflowY = "auto";
+ for (const leaf of leaves) {
+ const props = leaf.properties as { id: string; name: string };
+ const link = document.createElement("a");
+ link.href = `/beach/${encodeURIComponent(props.id)}`;
+ link.textContent = props.name;
+ link.style.cssText = `display: block; padding: 4px 0; text-decoration: none; color: ${styles.color}; font-weight: 500; border-bottom: 1px solid ${styles.borderColor};`;
+ link.addEventListener("click", (event) => {
+ event.preventDefault();
+ navigateRef.current(`/beach/${props.id}`);
+ });
+ container.appendChild(link);
+ }
+
+ const popup = new maplibregl.Popup({
+ closeButton: true,
+ maxWidth: "300px",
+ })
+ .setLngLat(coordinates)
+ .setDOMContent(container)
+ .addTo(map);
+
+ // Apply dark mode styles to popup container
+ const popupEl = popup.getElement();
+ if (popupEl) {
+ const content = popupEl.querySelector(".maplibregl-popup-content");
+ if (content) {
+ (content as HTMLElement).style.backgroundColor =
+ styles.background;
+ (content as HTMLElement).style.color = styles.color;
+ }
+ }
+ } else {
+ // Otherwise, zoom in
+ map.easeTo({
+ center: coordinates,
+ zoom: zoom ?? map.getZoom() + 2,
+ });
+ }
+ } catch {
+ // Ignore cluster expansion errors
+ }
+ };
+
+ void openCluster();
+ });
+
+ // Click handler for individual points - show popup
+ map.on("click", "unclustered-point", (e) => {
+ if (
+ !e.features?.length ||
+ !e.features[0].geometry ||
+ e.features[0].geometry.type !== "Point"
+ )
+ return;
+
+ const coordinates = e.features[0].geometry.coordinates.slice() as [
+ number,
+ number
+ ];
+ const { name, id } = e.features[0].properties as {
+ name: string;
+ id: string;
+ };
+
+ // Ensure that if the map is zoomed out such that multiple
+ // copies of the feature are visible, the popup appears
+ // over the copy being pointed to.
+ while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
+ coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
+ }
+
+ const styles = getPopupStyles();
+ const link = document.createElement("a");
+ link.href = `/beach/${encodeURIComponent(id)}`;
+ link.textContent = name;
+ link.style.cssText = `text-decoration: none; color: ${styles.color}; font-weight: 500;`;
+ link.addEventListener("click", (event) => {
+ event.preventDefault();
+ navigateRef.current(`/beach/${id}`);
+ });
+ const popup = new maplibregl.Popup({ closeButton: false })
+ .setLngLat(coordinates)
+ .setDOMContent(link)
+ .addTo(map);
+
+ // Apply dark mode styles to popup container
+ const popupEl = popup.getElement();
+ if (popupEl) {
+ const content = popupEl.querySelector(".maplibregl-popup-content");
+ if (content) {
+ (content as HTMLElement).style.backgroundColor = styles.background;
+ (content as HTMLElement).style.color = styles.color;
+ }
+ }
+ });
+
+ // Change cursor on hover
+ map.on("mouseenter", "clusters", () => {
+ map.getCanvas().style.cursor = "pointer";
+ });
+ map.on("mouseleave", "clusters", () => {
+ map.getCanvas().style.cursor = "";
+ });
+ map.on("mouseenter", "unclustered-point", () => {
+ map.getCanvas().style.cursor = "pointer";
+ });
+ map.on("mouseleave", "unclustered-point", () => {
+ map.getCanvas().style.cursor = "";
+ });
+
+ return () => {
+ obs.disconnect();
+ map.off("error", handleMapError);
+ map.remove();
+ mapRef.current = null;
+ };
+ }, [mapUnavailable]);
+
+ // Push new data when points change β just update the source, don't rebuild.
+ useEffect(() => {
+ const map = mapRef.current;
+ if (!map) return;
+ const src = map.getSource("beaches") as
+ | maplibregl.GeoJSONSource
+ | undefined;
+ // If the source isn't there yet, the load/styledata handler will build it
+ // from pointsRef, so there's nothing to do here.
+ if (src) src.setData(toGeoJSON(points));
+ }, [points]);
+
+ // Fit to focus (center + radius) without flashing; allow closer zoom for small radii
+ useEffect(() => {
+ const map = mapRef.current;
+ if (!map || !focus?.center) return;
+
+ map.stop(); // cancel any ongoing camera animation
+
+ if (focus.radiusKm && focus.radiusKm > 0) {
+ const r = focus.radiusKm;
+
+ // π§ TIGHTEN the fit for small radii
+ // - Shrink the radius a bit so bounds aren't too wide
+ // - Reduce padding so more of the view is the actual area of interest
+ // - Let the map zoom in a bit more
+ const isSmall = r <= 5; // the 5 km "nearby" case
+ const effective = isSmall ? r * 0.65 : r; // <β tighten bounds
+ const padding = isSmall ? 12 : 24; // less padding on small areas
+ const maxZoom = isSmall ? 17 : 14; // allow closer zoom on small areas (updated for new max)
+
+ const b = circleBounds(focus.center, effective);
+ map.fitBounds(b, { padding, maxZoom });
+ } else {
+ map.flyTo({ center: [focus.center.lon, focus.center.lat], zoom: 11 });
+ }
+ }, [focus]);
+
+ if (mapUnavailable) {
+ return ;
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/SandBackground.tsx b/frontend/src/components/SandBackground.tsx
new file mode 100644
index 0000000000..91162f5b57
--- /dev/null
+++ b/frontend/src/components/SandBackground.tsx
@@ -0,0 +1,223 @@
+import { useRef, useEffect } from "react";
+import { Canvas, useFrame, useThree, extend } from "@react-three/fiber";
+import * as THREE from "three";
+
+// Procedural Beach Texture Shader
+class BeachTextureMaterial extends THREE.ShaderMaterial {
+ constructor() {
+ super({
+ uniforms: {
+ uTime: { value: 0 },
+ uResolution: { value: new THREE.Vector2(1, 1) },
+ // Warmer, more golden/beige sand
+ uSandColor: { value: new THREE.Color("#ede2ce") },
+ // Clearer, subtle blue tint (less teal/green)
+ uWaterTint: { value: new THREE.Color("#B0D4E8") },
+ },
+ vertexShader: `
+ varying vec2 vUv;
+ void main() {
+ vUv = uv;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+ }
+ `,
+ fragmentShader: `
+ uniform float uTime;
+ uniform vec2 uResolution;
+ uniform vec3 uSandColor;
+ uniform vec3 uWaterTint;
+ varying vec2 vUv;
+
+ // --- Noise Functions ---
+ vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
+ float snoise(vec2 v){
+ const vec4 C = vec4(0.211324865405187, 0.366025403784439,
+ -0.577350269189626, 0.024390243902439);
+ vec2 i = floor(v + dot(v, C.yy));
+ vec2 x0 = v - i + dot(i, C.xx);
+ vec2 i1;
+ i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
+ vec4 x12 = x0.xyxy + C.xxzz;
+ x12.xy -= i1;
+ i = mod(i, 289.0);
+ vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ + i.x + vec3(0.0, i1.x, 1.0 ));
+ vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
+ m = m*m ;
+ m = m*m ;
+ vec3 x = 2.0 * fract(p * C.www) - 1.0;
+ vec3 h = abs(x) - 0.5;
+ vec3 ox = floor(x + 0.5);
+ vec3 a0 = x - ox;
+ m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
+ vec3 g;
+ g.x = a0.x * x0.x + h.x * x0.y;
+ g.yz = a0.yz * x12.xz + h.yz * x12.yw;
+ return 130.0 * dot(m, g);
+ }
+
+ // --- Drops Function ---
+ // (Deleted)
+
+ void main() {
+ // Adjust UV for aspect ratio
+ float aspect = uResolution.x / uResolution.y;
+ vec2 uv = vUv * vec2(aspect, 1.0);
+
+ // --- Sand Generation (with Stones/Debris) ---
+
+ // 1. Base Sand
+ float grain = snoise(uv * 300.0);
+ vec3 sandBase = uSandColor + vec3(grain * 0.04);
+
+ // 2. Small Stones / Debris (Disabled)
+ // float debrisNoise = snoise(uv * 25.0 + vec2(15.0, 5.0));
+ // float stones = smoothstep(0.95, 0.95, debrisNoise);
+
+ // Darker brownish/grey color for stones
+ // vec3 stoneColor = vec3(0.45, 0.4, 0.35);
+ // Mix stones in
+ // sandBase = mix(sandBase, stoneColor, stones * 0.5);
+
+ // --- Shadows (Leaves/Log) ---
+ // Large, slow moving shadows from above
+ float shadowTime = uTime * 0.05;
+ float shadowN = snoise(uv * 1.5 + vec2(shadowTime, 0.0));
+ float shadowMask = smoothstep(0.2, 0.6, shadowN); // Soft shadows
+
+ // Darken sand where there are shadows
+ sandBase = mix(sandBase, sandBase * 0.88, shadowMask);
+
+ // --- Water Hybrid (Silk + Caustics) ---
+ // Hybrid approach:
+ // - "Silk" comes from domain warping (distorting the coordinates)
+ // - "Caustics" comes from the ridge noise (1.0 - abs(noise))
+
+ float t = uTime * 0.10; // Slightly slower for relaxed feel
+
+ // 1. Domain Warping (The "Silk" Part)
+ // We warp the coordinate space slightly before sampling the ripple noise.
+ // This curves the straight lines of the caustics.
+ vec2 warp = vec2(
+ snoise(uv * 2.0 + vec2(0.0, t)),
+ snoise(uv * 2.0 + vec2(5.2, t + 1.3))
+ ) * 0.15; // Strength of the warp
+
+ vec2 flowUV = uv + warp;
+
+ // 2. Caustic Ripples (The "Water" Part) - Moving Up
+ vec2 flow1 = vec2(0.0, -t);
+ float n1 = snoise(flowUV * 6.0 + flow1);
+
+ vec2 flow2 = vec2(0.05, -t * 1.4);
+ float n2 = snoise(flowUV * 10.0 + flow2 + vec2(3.2, 1.5));
+
+ // Combine using "ridge" function
+ float ridges = (
+ (1.0 - abs(n1)) * 0.6 +
+ (1.0 - abs(n2)) * 0.4
+ );
+
+ // 3. Hybrid Softening
+ // Lower power than pure caustics (was 5.0) to make it softer/silkier
+ // but keep enough definition to look like water
+ float caustic = pow(ridges, 4.0);
+
+ // Reduced intensity to avoid "burning" white look (was 0.2)
+ float totalLight = caustic * 0.07;
+
+ // --- Refraction ---
+ // Distort sand based on water pattern
+ vec2 waterGrad = vec2(n1, n2) * 0.005;
+ float refractedGrain = snoise((uv + waterGrad) * 300.0);
+
+ // Refracted stones
+ // float refractedDebris = snoise((uv + waterGrad) * 25.0 + vec2(15.0, 5.0));
+ // float refractedStones = smoothstep(0.75, 0.85, refractedDebris);
+
+ // Refracted Shadows
+ float refractedShadowN = snoise((uv + waterGrad) * 1.5 + vec2(shadowTime, 0.0));
+ float refractedShadowMask = smoothstep(0.2, 0.6, refractedShadowN);
+
+ // Re-compose sand with refraction
+ vec3 finalSand = uSandColor + vec3(refractedGrain * 0.04);
+ // finalSand = mix(finalSand, stoneColor, refractedStones * 0.5);
+ finalSand = mix(finalSand, finalSand * 0.88, refractedShadowMask);
+
+ // --- Composition ---
+ vec3 finalColor = mix(finalSand, uWaterTint, 0.15);
+ finalColor += vec3(1.0, 1.0, 0.95) * totalLight;
+
+ // Vignette
+ float vignette = smoothstep(0.0, 0.15, vUv.y);
+ finalColor = mix(finalColor * 0.9, finalColor, vignette);
+
+ gl_FragColor = vec4(finalColor, 1.0);
+ }
+ `,
+ });
+ }
+}
+
+extend({ BeachTextureMaterial });
+
+declare global {
+ namespace JSX {
+ interface IntrinsicElements {
+ beachTextureMaterial: any;
+ }
+ }
+}
+
+function BeachScene() {
+ const materialRef = useRef(null);
+ const invalidate = useThree((s) => s.invalidate);
+
+ // Drive the on-demand render loop at ~30fps. RAF pauses on hidden tabs.
+ useEffect(() => {
+ let raf = 0;
+ let last = 0;
+ const loop = (ts: number) => {
+ if (ts - last >= 33) {
+ last = ts;
+ invalidate();
+ }
+ raf = requestAnimationFrame(loop);
+ };
+ raf = requestAnimationFrame(loop);
+ return () => cancelAnimationFrame(raf);
+ }, [invalidate]);
+
+ useFrame(({ clock, size }) => {
+ if (materialRef.current) {
+ materialRef.current.uniforms.uTime.value = clock.getElapsedTime();
+ materialRef.current.uniforms.uResolution.value.set(
+ size.width,
+ size.height
+ );
+ }
+ });
+
+ return (
+
+
+
+
+ );
+}
+
+export default function SandBackground() {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/SortableFavorite.tsx b/frontend/src/components/SortableFavorite.tsx
new file mode 100644
index 0000000000..b1f8decd38
--- /dev/null
+++ b/frontend/src/components/SortableFavorite.tsx
@@ -0,0 +1,103 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+
+type Props = {
+ id: string; // beachId
+ name: string;
+ muni: string;
+ classificationText: string;
+ classificationClass: string; // Tailwind badge class
+ onRemove: () => void;
+ disabled?: boolean;
+};
+
+export default function SortableFavorite({
+ id,
+ name,
+ muni,
+ classificationText,
+ classificationClass,
+ onRemove,
+ disabled,
+}: Props) {
+ const { t } = useTranslation();
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ isDragging,
+ } = useSortable({ id, disabled });
+
+ const style: React.CSSProperties = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ opacity: isDragging ? 0.85 : 1,
+ };
+
+ return (
+
+
+
+ {/* Drag handle */}
+ {!disabled && (
+
+ {/* 2x2 grip dots */}
+
+
+
+
+
+
+
+ )}
+
+
+ {name}
+
+
+
+
{muni || "β"}
+
+
+
+ {classificationText}
+
+
+
+
+ {/* Actions - allow wrapping on narrow screens */}
+
+
+ {t("favorites.view")}
+
+
+ {t("favorites.remove")}
+
+
+
+ );
+}
diff --git a/frontend/src/components/SwimForecastCard.tsx b/frontend/src/components/SwimForecastCard.tsx
new file mode 100644
index 0000000000..5fadc77374
--- /dev/null
+++ b/frontend/src/components/SwimForecastCard.tsx
@@ -0,0 +1,249 @@
+import { useTranslation } from "react-i18next";
+import {
+ CheckCircle,
+ Info,
+ Warning,
+ WarningCircle,
+ Question,
+ Drop,
+ type Icon,
+} from "@phosphor-icons/react";
+import { useWeather } from "../hooks/useWeather";
+import { useBeaches } from "../hooks/useBeaches";
+import {
+ computeSwimForecast,
+ type SwimForecastInput,
+ type SwimLevel,
+} from "../utils/swimForecast";
+
+type Tone = {
+ Icon: Icon;
+ /** i18n key for the short verdict label. */
+ labelKey: string;
+ /** Quality color variable used for text/border/background. */
+ colorVar: string;
+};
+
+const TONE: Record = {
+ good: {
+ Icon: CheckCircle,
+ labelKey: "swimForecast.level.good",
+ colorVar: "--color-quality-good",
+ },
+ caution: {
+ Icon: Warning,
+ labelKey: "swimForecast.level.caution",
+ colorVar: "--color-quality-sufficient",
+ },
+ avoid: {
+ Icon: WarningCircle,
+ labelKey: "swimForecast.level.avoid",
+ colorVar: "--color-quality-poor",
+ },
+ unknown: {
+ Icon: Question,
+ labelKey: "swimForecast.level.unknown",
+ colorVar: "--color-quality-unknown",
+ },
+};
+
+interface Props {
+ /** HaV-derived inputs, assembled by the detail page. */
+ inputs: Omit;
+ beachId: string;
+ lat?: number;
+ lon?: number;
+}
+
+export default function SwimForecastCard({ inputs, beachId, lat, lon }: Props) {
+ const { t, i18n } = useTranslation();
+ // The HaV detail response often omits coordinates, so resolve them from the
+ // beach list first (same source, and same query key, as BeachWeatherPanel β
+ // so this reuses the cached weather request rather than firing a new one).
+ const { data: beaches } = useBeaches();
+ const beach = beaches?.find((b) => b.id === beachId);
+ const resolvedLat = beach?.lat ?? lat;
+ const resolvedLon = beach?.lon ?? lon;
+
+ const { data: weather, isLoading, isError } = useWeather(
+ resolvedLat,
+ resolvedLon
+ );
+
+ // The nowcast leans on rainfall + water temp; without weather we can't score.
+ if (resolvedLat == null || resolvedLon == null) return null;
+
+ if (isLoading) {
+ return (
+
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+
+ );
+ }
+
+ if (isError || !weather) return null;
+
+ const forecast = computeSwimForecast({
+ ...inputs,
+ recentRainMm: weather.recentRainMm,
+ waterTemperature: weather.waterTemperature,
+ days: weather.forecast.map((d) => ({
+ date: d.date,
+ precipSum: d.precipSum,
+ windMax: d.windMax,
+ waveMax: d.waveMax,
+ })),
+ });
+
+ if (!forecast) return null;
+
+ const days = forecast.days.slice(0, 4);
+ const todayTone = TONE[forecast.overall];
+ const TodayIcon = todayTone.Icon;
+
+ const weekdayFmt = new Intl.DateTimeFormat(
+ i18n.language === "en" ? "en-GB" : "sv-SE",
+ { weekday: "short", timeZone: "Europe/Stockholm" }
+ );
+
+ const todayReasons = days[0]?.reasonKeys ?? [];
+ const [primaryReason, ...moreReasons] = todayReasons;
+
+ return (
+
+ {/* Verdict-tinted wash behind the hero. Decorative only. */}
+
+
+
+
+ {t("swimForecast.title")}
+
+
+ {t(`swimForecast.confidenceLabel.${forecast.confidence}`)}
+
+
+
+ {/* Today's verdict, front and center */}
+
+
+
+
+
+
+ {t(todayTone.labelKey)}
+
+ {primaryReason && (
+
+ {t(primaryReason)}
+
+ )}
+
+
+
+ {/* Per-day verdict tiles */}
+
+
+ {/* Secondary reasons behind today's verdict */}
+ {moreReasons.length > 0 && (
+
+ {moreReasons.map((key) => (
+
+
+ {t(key)}
+
+ ))}
+
+ )}
+
+
+
+
+ {t("swimForecast.howItWorks")}
+
+
+ {t("swimForecast.disclaimer")}
+
+
+
+ );
+}
diff --git a/frontend/src/components/Tooltip.tsx b/frontend/src/components/Tooltip.tsx
new file mode 100644
index 0000000000..885c3c48b9
--- /dev/null
+++ b/frontend/src/components/Tooltip.tsx
@@ -0,0 +1,90 @@
+import { ReactNode, useState, useRef, useEffect } from "react";
+
+interface TooltipProps {
+ content: string;
+ children: ReactNode;
+}
+
+export default function Tooltip({ content, children }: TooltipProps) {
+ const [isVisible, setIsVisible] = useState(false);
+ const [styles, setStyles] = useState<{
+ position: "top" | "bottom";
+ offsetX: number;
+ }>({ position: "bottom", offsetX: 0 });
+ const tooltipRef = useRef(null);
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ if (isVisible && tooltipRef.current && containerRef.current) {
+ const tooltip = tooltipRef.current;
+ const container = containerRef.current;
+ const containerRect = container.getBoundingClientRect();
+ const tooltipRect = tooltip.getBoundingClientRect();
+
+ let position: "top" | "bottom" = "bottom";
+ let offsetX = 0;
+
+ // Check vertical space - prefer bottom to avoid cutting off at top
+ const spaceAbove = containerRect.top;
+ const spaceBelow = window.innerHeight - containerRect.bottom;
+
+ if (spaceBelow >= tooltipRect.height + 8) {
+ position = "bottom";
+ } else if (spaceAbove >= tooltipRect.height + 8) {
+ position = "top";
+ } else {
+ position = "bottom"; // default to bottom if neither fits
+ }
+
+ // Check horizontal bounds and adjust if needed
+ const tooltipLeft =
+ containerRect.left + containerRect.width / 2 - tooltipRect.width / 2;
+ const tooltipRight = tooltipLeft + tooltipRect.width;
+
+ if (tooltipLeft < 8) {
+ offsetX = 8 - tooltipLeft;
+ } else if (tooltipRight > window.innerWidth - 8) {
+ offsetX = window.innerWidth - 8 - tooltipRight;
+ }
+
+ setStyles({ position, offsetX });
+ }
+ }, [isVisible, content]);
+
+ return (
+ setIsVisible(true)}
+ onMouseLeave={() => setIsVisible(false)}
+ onFocus={() => setIsVisible(true)}
+ onBlur={() => setIsVisible(false)}
+ >
+ {children}
+ {isVisible && (
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/WaterBackground.tsx b/frontend/src/components/WaterBackground.tsx
new file mode 100644
index 0000000000..bf33cf5e32
--- /dev/null
+++ b/frontend/src/components/WaterBackground.tsx
@@ -0,0 +1,457 @@
+import { useEffect, useRef } from "react";
+import { Canvas, extend, useFrame, useThree } from "@react-three/fiber";
+import * as THREE from "three";
+
+// The Ashima Arts / Stefan Gustavson simplex noise is reserved for breaking up
+// foam and glints. Wave shape and surface normals stay analytic, so the water
+// moves coherently instead of boiling like a noise field.
+const NOISE_GLSL = `
+ vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+ vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+ vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
+ vec4 taylorInvSqrt(vec4 r) {
+ return 1.79284291400159 - 0.85373472095314 * r;
+ }
+
+ float snoise(vec3 v) {
+ const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
+ const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
+ vec3 i = floor(v + dot(v, C.yyy));
+ vec3 x0 = v - i + dot(i, C.xxx);
+ vec3 g = step(x0.yzx, x0.xyz);
+ vec3 l = 1.0 - g;
+ vec3 i1 = min(g.xyz, l.zxy);
+ vec3 i2 = max(g.xyz, l.zxy);
+ vec3 x1 = x0 - i1 + C.xxx;
+ vec3 x2 = x0 - i2 + C.yyy;
+ vec3 x3 = x0 - D.yyy;
+ i = mod289(i);
+ vec4 p = permute(permute(permute(
+ i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ + i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ + i.x + vec4(0.0, i1.x, i2.x, 1.0));
+ float n_ = 0.142857142857;
+ vec3 ns = n_ * D.wyz - D.xzx;
+ vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
+ vec4 x_ = floor(j * ns.z);
+ vec4 y_ = floor(j - 7.0 * x_);
+ vec4 x = x_ * ns.x + ns.yyyy;
+ vec4 y = y_ * ns.x + ns.yyyy;
+ vec4 h = 1.0 - abs(x) - abs(y);
+ vec4 b0 = vec4(x.xy, y.xy);
+ vec4 b1 = vec4(x.zw, y.zw);
+ vec4 s0 = floor(b0) * 2.0 + 1.0;
+ vec4 s1 = floor(b1) * 2.0 + 1.0;
+ vec4 sh = -step(h, vec4(0.0));
+ vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
+ vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
+ vec3 p0 = vec3(a0.xy, h.x);
+ vec3 p1 = vec3(a0.zw, h.y);
+ vec3 p2 = vec3(a1.xy, h.z);
+ vec3 p3 = vec3(a1.zw, h.w);
+ vec4 norm = taylorInvSqrt(
+ vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))
+ );
+ p0 *= norm.x;
+ p1 *= norm.y;
+ p2 *= norm.z;
+ p3 *= norm.w;
+ vec4 m = max(
+ 0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)),
+ 0.0
+ );
+ m *= m;
+ return 42.0 * dot(
+ m * m,
+ vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))
+ );
+ }
+`;
+
+class WaterMaterial extends THREE.ShaderMaterial {
+ constructor() {
+ super({
+ dithering: true,
+ uniforms: {
+ uTime: { value: 0 },
+ uWaterColor: { value: new THREE.Color("#082e41") },
+ uUnderColor: { value: new THREE.Color("#358694") },
+ uFoamColor: { value: new THREE.Color("#e8f6f4") },
+ uSkyZenith: { value: new THREE.Color("#12293a") },
+ uSkyHorizon: { value: new THREE.Color("#7ba1aa") },
+ uSunColor: { value: new THREE.Color("#fff1d6") },
+ uAbsorption: { value: new THREE.Vector3(0.48, 0.14, 0.055) },
+ uSunDirection: {
+ value: new THREE.Vector3(-0.42, 0.38, -0.82).normalize(),
+ },
+ },
+ vertexShader: `
+ uniform float uTime;
+
+ varying vec2 vSurfaceCoord;
+ varying float vElevation;
+ varying float vCrest;
+ varying float vCompression;
+ varying vec3 vWorldNormal;
+ varying vec3 vWorldPos;
+
+ const float PI = 3.141592653589793;
+ const float GRAVITY = 9.81;
+
+ void accumulateWave(
+ vec2 p,
+ vec2 direction,
+ float wavelength,
+ float amplitude,
+ float steepness,
+ float phaseOffset,
+ float time,
+ inout vec3 displaced,
+ inout vec3 tangent,
+ inout vec3 bitangent,
+ inout float crest
+ ) {
+ vec2 d = normalize(direction);
+ float k = 2.0 * PI / wavelength;
+ float omega = sqrt(GRAVITY * k);
+ float phase = k * dot(d, p) - omega * time + phaseOffset;
+ float waveSin = sin(phase);
+ float waveCos = cos(phase);
+ float horizontalAmplitude = steepness * amplitude;
+ float compression = horizontalAmplitude * k * waveSin;
+
+ displaced.xy += d * horizontalAmplitude * waveCos;
+ displaced.z += amplitude * waveSin;
+
+ tangent += vec3(
+ -d.x * d.x * compression,
+ -d.x * d.y * compression,
+ d.x * amplitude * k * waveCos
+ );
+ bitangent += vec3(
+ -d.x * d.y * compression,
+ -d.y * d.y * compression,
+ d.y * amplitude * k * waveCos
+ );
+
+ float crestShape = smoothstep(0.58, 0.98, waveSin);
+ crest = max(crest, crestShape * (steepness + amplitude * k));
+ }
+
+ void main() {
+ float time = uTime * 0.44;
+ vec2 p = position.xy;
+ vec3 displaced = position;
+ vec3 tangent = vec3(1.0, 0.0, 0.0);
+ vec3 bitangent = vec3(0.0, 1.0, 0.0);
+ float crest = 0.0;
+
+ // A calm, wind-driven lake spectrum. Small waves oscillate more
+ // quickly while the longer swells travel faster underneath.
+ accumulateWave(
+ p, vec2(1.0, 0.18), 13.0, 0.22, 0.54, 0.0, time,
+ displaced, tangent, bitangent, crest
+ );
+ accumulateWave(
+ p, vec2(0.72, 0.69), 7.2, 0.10, 0.47, 1.7, time,
+ displaced, tangent, bitangent, crest
+ );
+ accumulateWave(
+ p, vec2(-0.28, 0.96), 3.8, 0.045, 0.36, 3.4, time,
+ displaced, tangent, bitangent, crest
+ );
+ accumulateWave(
+ p, vec2(-0.82, 0.57), 2.2, 0.02, 0.28, 5.1, time,
+ displaced, tangent, bitangent, crest
+ );
+
+ vec3 localNormal = normalize(cross(tangent, bitangent));
+ float jacobian =
+ tangent.x * bitangent.y - tangent.y * bitangent.x;
+
+ vSurfaceCoord = p;
+ vElevation = displaced.z;
+ vCrest = clamp(crest, 0.0, 1.0);
+ vCompression = clamp(1.0 - jacobian, 0.0, 1.0);
+ vWorldNormal = normalize(mat3(modelMatrix) * localNormal);
+
+ vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);
+ vWorldPos = worldPosition.xyz;
+ gl_Position = projectionMatrix * viewMatrix * worldPosition;
+ }
+ `,
+ fragmentShader: `
+ uniform float uTime;
+ uniform vec3 uWaterColor;
+ uniform vec3 uUnderColor;
+ uniform vec3 uFoamColor;
+ uniform vec3 uSkyZenith;
+ uniform vec3 uSkyHorizon;
+ uniform vec3 uSunColor;
+ uniform vec3 uAbsorption;
+ uniform vec3 uSunDirection;
+
+ varying vec2 vSurfaceCoord;
+ varying float vElevation;
+ varying float vCrest;
+ varying float vCompression;
+ varying vec3 vWorldNormal;
+ varying vec3 vWorldPos;
+
+ #include
+
+ const float GRAVITY = 9.81;
+ const float WATER_F0 = 0.02037;
+
+ #include
+
+ ${NOISE_GLSL}
+
+ void accumulateRipple(
+ vec2 p,
+ vec2 direction,
+ float wavelength,
+ float amplitude,
+ float phaseOffset,
+ float time,
+ float phaseWarp,
+ inout vec2 slope
+ ) {
+ vec2 d = normalize(direction);
+ float k = 2.0 * PI / wavelength;
+ float omega = sqrt(GRAVITY * k);
+ float phase =
+ k * dot(d, p) - omega * time + phaseOffset + phaseWarp;
+ slope += d * (amplitude * k * cos(phase));
+ }
+
+ vec2 rippleSlope(vec2 p, float time) {
+ float phaseWarp = snoise(vec3(p * 0.16, time * 0.08)) * 0.72;
+ vec2 slope = vec2(0.0);
+
+ accumulateRipple(
+ p, vec2(0.98, 0.20), 1.10, 0.018, 0.4, time,
+ phaseWarp, slope
+ );
+ accumulateRipple(
+ p, vec2(0.68, 0.73), 0.63, 0.009, 2.1, time,
+ phaseWarp * 1.3, slope
+ );
+ accumulateRipple(
+ p, vec2(-0.37, 0.93), 0.34, 0.0045, 4.0, time,
+ phaseWarp * 1.7, slope
+ );
+ accumulateRipple(
+ p, vec2(-0.91, 0.42), 0.19, 0.002, 5.4, time,
+ phaseWarp * 2.0, slope
+ );
+
+ return slope;
+ }
+
+ vec3 sampleSky(vec3 direction) {
+ float skyHeight = clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
+ float gradient = pow(smoothstep(0.0, 1.0, skyHeight), 0.65);
+ vec3 sky = mix(uSkyHorizon, uSkyZenith, gradient);
+
+ float sunDisc = pow(
+ max(dot(direction, normalize(uSunDirection)), 0.0),
+ 520.0
+ );
+ float horizonVeil = pow(1.0 - abs(direction.y), 5.0);
+ return sky + uSunColor * sunDisc * 2.4
+ + uSkyHorizon * horizonVeil * 0.08;
+ }
+
+ float distributionGGX(float nDotH, float roughness) {
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float d = nDotH * nDotH * (a2 - 1.0) + 1.0;
+ return a2 / max(PI * d * d, 0.0001);
+ }
+
+ float geometrySchlickGGX(float nDotDirection, float roughness) {
+ float r = roughness + 1.0;
+ float k = (r * r) / 8.0;
+ return nDotDirection /
+ max(nDotDirection * (1.0 - k) + k, 0.0001);
+ }
+
+ void main() {
+ vec3 baseNormal = normalize(vWorldNormal);
+ vec3 viewDirection = normalize(cameraPosition - vWorldPos);
+ float cameraDistance = distance(cameraPosition, vWorldPos);
+
+ // Capillary detail lives in the fragment stage and fades into the
+ // distance, keeping the horizon stable instead of sparkling noisily.
+ float detailFade = 1.0 - smoothstep(11.0, 34.0, cameraDistance);
+ vec2 slope = vec2(0.0);
+ if (detailFade > 0.001) {
+ slope = rippleSlope(vSurfaceCoord, uTime * 0.44) * detailFade;
+ }
+ vec3 tangent = normalize(
+ vec3(1.0, 0.0, 0.0) - baseNormal * baseNormal.x
+ );
+ vec3 bitangent = normalize(cross(baseNormal, tangent));
+ vec3 normal = normalize(
+ baseNormal
+ - tangent * slope.x * 0.78
+ - bitangent * slope.y * 0.78
+ );
+
+ float nDotV = clamp(dot(normal, viewDirection), 0.0, 1.0);
+ float fresnel =
+ WATER_F0 + (1.0 - WATER_F0) * pow(1.0 - nDotV, 5.0);
+
+ // A virtual water column gives the empty background believable
+ // angle- and trough-dependent absorption without a second scene pass.
+ float troughDepth = clamp(-vElevation * 2.8, 0.0, 1.0);
+ float opticalDepth = mix(0.85, 2.7, troughDepth);
+ float opticalPath = opticalDepth / max(nDotV, 0.18);
+ vec3 transmittance = exp(-uAbsorption * opticalPath);
+ vec3 bodyColor =
+ uWaterColor * (1.0 - transmittance)
+ + uUnderColor * transmittance;
+
+ vec3 reflectedDirection = reflect(-viewDirection, normal);
+ vec3 reflectionColor = sampleSky(reflectedDirection);
+ vec3 color = mix(bodyColor, reflectionColor, fresnel);
+
+ vec3 lightDirection = normalize(uSunDirection);
+ vec3 halfDirection = normalize(lightDirection + viewDirection);
+ float nDotL = max(dot(normal, lightDirection), 0.0);
+ float nDotH = max(dot(normal, halfDirection), 0.0);
+ float vDotH = max(dot(viewDirection, halfDirection), 0.0);
+ float roughness = 0.16;
+ float distribution = distributionGGX(nDotH, roughness);
+ float geometry =
+ geometrySchlickGGX(nDotV, roughness)
+ * geometrySchlickGGX(nDotL, roughness);
+ vec3 specularFresnel =
+ vec3(WATER_F0)
+ + (1.0 - vec3(WATER_F0)) * pow(1.0 - vDotH, 5.0);
+ vec3 specular =
+ distribution * geometry * specularFresnel
+ / max(4.0 * nDotV * nDotL, 0.001);
+ color += uSunColor * specular * nDotL * 1.35;
+
+ // A tight, distance-faded lobe turns the fine normal field into
+ // sparse sun glitter without letting it fizz across the horizon.
+ float sparkleNoise =
+ snoise(vec3(vSurfaceCoord * 2.1, uTime * 0.06)) * 0.5 + 0.5;
+ float sparkleFade =
+ smoothstep(0.0, 7.0, cameraDistance)
+ * (1.0 - smoothstep(18.0, 34.0, cameraDistance));
+ float sparkle =
+ pow(nDotH, 460.0)
+ * smoothstep(0.58, 0.92, sparkleNoise)
+ * sparkleFade;
+ color += uSunColor * sparkle * 1.8;
+
+ // Thin crests transmit light when the low sun is behind them.
+ float forwardScatter =
+ pow(max(dot(viewDirection, -lightDirection), 0.0), 4.0);
+ float crestTransmission = smoothstep(0.25, 0.68, vCrest);
+ color +=
+ uUnderColor
+ * crestTransmission
+ * forwardScatter
+ * (0.18 + nDotL * 0.18);
+
+ // Whitewater follows wave compression and high crests. Noise only
+ // dissolves the mask; it never creates foam away from a breaking wave.
+ vec2 windOffset = vec2(0.98, 0.20) * uTime * 0.035;
+ float foamNoise =
+ snoise(vec3(
+ (vSurfaceCoord + windOffset) * vec2(0.62, 1.15),
+ uTime * 0.025
+ )) * 0.5 + 0.5;
+ float crestMask = smoothstep(0.42, 0.72, vCrest);
+ float compressionMask = smoothstep(0.045, 0.16, vCompression);
+ float foam =
+ crestMask
+ * compressionMask
+ * smoothstep(0.34, 0.82, foamNoise + crestMask * 0.12);
+ color = mix(color, uFoamColor, foam * 0.24);
+
+ float horizonHaze = smoothstep(19.0, 42.0, cameraDistance);
+ color = mix(color, uSkyHorizon, horizonHaze * 0.18);
+
+ gl_FragColor = vec4(color, 1.0);
+ #include
+ #include
+ #include
+ }
+ `,
+ });
+ }
+}
+
+extend({ WaterMaterial });
+
+declare global {
+ namespace JSX {
+ interface IntrinsicElements {
+ waterMaterial: any;
+ }
+ }
+}
+
+function Water() {
+ const materialRef = useRef(null);
+ const invalidate = useThree((state) => state.invalidate);
+ const viewportWidth = useThree((state) => state.size.width);
+ const segments = viewportWidth < 768 ? 64 : 96;
+
+ // Drive the on-demand canvas near 30 fps. The shader's physically coherent
+ // motion remains smooth at this cadence while leaving room for MapLibre.
+ useEffect(() => {
+ let animationFrame = 0;
+ let lastRender = 0;
+
+ const loop = (timestamp: number) => {
+ if (timestamp - lastRender >= 33) {
+ lastRender = timestamp;
+ invalidate();
+ }
+ animationFrame = requestAnimationFrame(loop);
+ };
+
+ animationFrame = requestAnimationFrame(loop);
+ return () => cancelAnimationFrame(animationFrame);
+ }, [invalidate]);
+
+ useFrame(({ clock }) => {
+ if (materialRef.current) {
+ materialRef.current.uniforms.uTime.value = clock.getElapsedTime();
+ }
+ });
+
+ return (
+
+
+
+
+ );
+}
+
+export default function WaterBackground() {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/hooks/useBeaches.ts b/frontend/src/hooks/useBeaches.ts
new file mode 100644
index 0000000000..03ef42e153
--- /dev/null
+++ b/frontend/src/hooks/useBeaches.ts
@@ -0,0 +1,21 @@
+// frontend/src/hooks/useBeaches.ts
+import { useQuery } from "@tanstack/react-query";
+import { fetchBeaches, fetchBeach } from "../api/beaches";
+import type { BeachSummary, BeachDetail } from "../types/beaches";
+
+export function useBeaches() {
+ return useQuery({
+ queryKey: ["beaches"],
+ queryFn: fetchBeaches,
+ staleTime: 5 * 60 * 1000,
+ });
+}
+
+export function useBeachDetail(id: string | undefined) {
+ return useQuery({
+ queryKey: ["beach", id],
+ enabled: !!id,
+ queryFn: () => fetchBeach(id!),
+ staleTime: 5 * 60 * 1000,
+ });
+}
diff --git a/frontend/src/hooks/useDarkModeObserver.ts b/frontend/src/hooks/useDarkModeObserver.ts
new file mode 100644
index 0000000000..61f82e183f
--- /dev/null
+++ b/frontend/src/hooks/useDarkModeObserver.ts
@@ -0,0 +1,37 @@
+import { useState, useEffect } from "react";
+
+/**
+ * Observes the `.dark` class on `` via MutationObserver (debounced).
+ * Use this hook in components that need to *react* to dark mode changes
+ * without controlling them β e.g. to swap a 3D background.
+ */
+export function useDarkModeObserver() {
+ const [isDark, setIsDark] = useState(() => {
+ if (typeof document === "undefined") return false;
+ return document.documentElement.classList.contains("dark");
+ });
+
+ useEffect(() => {
+ let timeoutId: ReturnType;
+
+ const observer = new MutationObserver(() => {
+ // Debounce to prevent rapid re-renders during theme transition
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => {
+ setIsDark(document.documentElement.classList.contains("dark"));
+ }, 50);
+ });
+
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ["class"],
+ });
+
+ return () => {
+ observer.disconnect();
+ clearTimeout(timeoutId);
+ };
+ }, []);
+
+ return isDark;
+}
diff --git a/frontend/src/hooks/useDocumentTitle.ts b/frontend/src/hooks/useDocumentTitle.ts
new file mode 100644
index 0000000000..ab4ea04538
--- /dev/null
+++ b/frontend/src/hooks/useDocumentTitle.ts
@@ -0,0 +1,17 @@
+import { useEffect } from "react";
+
+const DEFAULT_TITLE = "Badlistan β Koll pΓ₯ badvattnet i hela Sverige";
+
+/**
+ * Sets document.title while the calling component is mounted, restoring the
+ * default on unmount. Gives each route a meaningful title (browser tabs,
+ * history, shared-link text) even without server-side rendering.
+ */
+export function useDocumentTitle(title?: string | null) {
+ useEffect(() => {
+ document.title = title ? `${title} β Badlistan` : DEFAULT_TITLE;
+ return () => {
+ document.title = DEFAULT_TITLE;
+ };
+ }, [title]);
+}
diff --git a/frontend/src/hooks/useGeolocation.ts b/frontend/src/hooks/useGeolocation.ts
new file mode 100644
index 0000000000..8819d3f6d5
--- /dev/null
+++ b/frontend/src/hooks/useGeolocation.ts
@@ -0,0 +1,39 @@
+import { useCallback, useState } from "react";
+
+type Coords = { lat: number; lon: number };
+
+export function useGeolocation() {
+ const [coords, setCoords] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const request = useCallback(() => {
+ if (!("geolocation" in navigator)) {
+ setError("Geolocation not supported");
+ return;
+ }
+ setLoading(true);
+ setError(null);
+
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ setLoading(false);
+ setCoords({
+ lat: pos.coords.latitude,
+ lon: pos.coords.longitude,
+ });
+ },
+ (err) => {
+ setLoading(false);
+ setError(err.message || "Failed to get location");
+ },
+ {
+ enableHighAccuracy: true,
+ timeout: 10_000,
+ maximumAge: 60_000,
+ }
+ );
+ }, []);
+
+ return { coords, loading, error, request };
+}
diff --git a/frontend/src/hooks/useOutsideClose.ts b/frontend/src/hooks/useOutsideClose.ts
new file mode 100644
index 0000000000..18dfcd3cf7
--- /dev/null
+++ b/frontend/src/hooks/useOutsideClose.ts
@@ -0,0 +1,29 @@
+import { useRef, useEffect } from "react";
+
+/**
+ * Attaches mousedown and Escape-key listeners to close a popover/menu.
+ * Returns a ref to attach to the container element.
+ */
+export function useOutsideClose(
+ onClose: () => void,
+ enabled = true
+) {
+ const ref = useRef(null);
+ useEffect(() => {
+ if (!enabled) return;
+
+ function handleClickOutside(e: MouseEvent) {
+ if (ref.current && !ref.current.contains(e.target as Node)) onClose();
+ }
+ function handleEscape(e: KeyboardEvent) {
+ if (e.key === "Escape") onClose();
+ }
+ document.addEventListener("mousedown", handleClickOutside);
+ document.addEventListener("keydown", handleEscape);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ document.removeEventListener("keydown", handleEscape);
+ };
+ }, [enabled, onClose]);
+ return ref;
+}
diff --git a/frontend/src/hooks/usePrefersReducedMotion.ts b/frontend/src/hooks/usePrefersReducedMotion.ts
new file mode 100644
index 0000000000..5bab01fc0c
--- /dev/null
+++ b/frontend/src/hooks/usePrefersReducedMotion.ts
@@ -0,0 +1,23 @@
+import { useState, useEffect } from "react";
+
+/**
+ * Returns true if the user has requested reduced motion
+ * via the `prefers-reduced-motion` media query.
+ */
+export function usePrefersReducedMotion() {
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => {
+ if (typeof window === "undefined") return false;
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+ });
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const handler = (e: MediaQueryListEvent) => {
+ setPrefersReducedMotion(e.matches);
+ };
+ mediaQuery.addEventListener("change", handler);
+ return () => mediaQuery.removeEventListener("change", handler);
+ }, []);
+
+ return prefersReducedMotion;
+}
diff --git a/frontend/src/hooks/useSunTimes.ts b/frontend/src/hooks/useSunTimes.ts
new file mode 100644
index 0000000000..aea29e3019
--- /dev/null
+++ b/frontend/src/hooks/useSunTimes.ts
@@ -0,0 +1,14 @@
+import { useQuery } from '@tanstack/react-query';
+import { fetchSunTimes } from '../api/sun';
+import type { SunTimes } from '../types/weather';
+
+export function useSunTimes(lat: number | undefined, lon: number | undefined) {
+ const today = new Date().toISOString().split('T')[0];
+ const date = new Date(`${today}T12:00:00`);
+ return useQuery({
+ queryKey: ['sun', lat, lon, today],
+ enabled: lat != null && lon != null,
+ queryFn: () => fetchSunTimes(lat!, lon!, date),
+ staleTime: 12 * 60 * 60 * 1000,
+ });
+}
diff --git a/frontend/src/hooks/useToggleDarkMode.ts b/frontend/src/hooks/useToggleDarkMode.ts
new file mode 100644
index 0000000000..2d3c7bb4f2
--- /dev/null
+++ b/frontend/src/hooks/useToggleDarkMode.ts
@@ -0,0 +1,16 @@
+import { useState, useEffect } from "react";
+
+/**
+ * Reads and toggles the `.dark` class on ``.
+ * Use this hook in components that need to *control* dark mode.
+ */
+export function useToggleDarkMode() {
+ const [isDark, setIsDark] = useState(() =>
+ document.documentElement.classList.contains("dark")
+ );
+ useEffect(() => {
+ document.documentElement.classList.toggle("dark", isDark);
+ localStorage.setItem("theme", isDark ? "dark" : "light");
+ }, [isDark]);
+ return { isDark, setIsDark };
+}
diff --git a/frontend/src/hooks/useWeather.ts b/frontend/src/hooks/useWeather.ts
new file mode 100644
index 0000000000..14a4331130
--- /dev/null
+++ b/frontend/src/hooks/useWeather.ts
@@ -0,0 +1,12 @@
+import { useQuery } from '@tanstack/react-query';
+import { fetchBeachWeather } from '../api/weather';
+import type { WeatherData } from '../types/weather';
+
+export function useWeather(lat: number | undefined, lon: number | undefined) {
+ return useQuery({
+ queryKey: ['weather', lat, lon],
+ enabled: lat != null && lon != null,
+ queryFn: () => fetchBeachWeather(lat!, lon!),
+ staleTime: 30 * 60 * 1000,
+ });
+}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
new file mode 100644
index 0000000000..89b0aa5577
--- /dev/null
+++ b/frontend/src/i18n.ts
@@ -0,0 +1,23 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+
+import sv from "./locales/sv/common.json";
+import en from "./locales/en/common.json";
+
+const saved = localStorage.getItem("lang");
+const fallbackLng = "sv";
+const lng = saved || fallbackLng;
+
+i18n.use(initReactI18next).init({
+ resources: {
+ sv: { common: sv },
+ en: { common: en },
+ },
+ lng,
+ fallbackLng,
+ ns: ["common"],
+ defaultNS: "common",
+ interpolation: { escapeValue: false },
+});
+
+export default i18n;
diff --git a/frontend/src/index.css b/frontend/src/index.css
index e69de29bb2..3547551848 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -0,0 +1,551 @@
+@import "tailwindcss";
+@import "maplibre-gl/dist/maplibre-gl.css";
+
+/* The app toggles dark mode via the .dark class (useToggleDarkMode), so the
+ dark: variant must follow the class, not the OS media query */
+@custom-variant dark (&:where(.dark, .dark *));
+
+/* Self-hosted fonts are imported in main.tsx so Vite rebases the woff2 urls */
+
+/* Design tokens (Tailwind v4) */
+@theme {
+ /* Light theme */
+ --color-surface: #efebe4;
+ --color-surface-muted: #f6f2ec;
+ --color-ink: #1a1a1a;
+ --color-ink-muted: #64686c;
+ --color-border: #cdc8c0;
+ --color-accent: #002fa7;
+ --color-badge: #e6e4dc;
+ --color-swimmer-cap: #e11418;
+
+ /* Liquid Glass 2026 web approximation - overall material rule.
+ This is not Apple's platform Liquid Glass. On the web, Badlistan uses a
+ disciplined frosted-glass system: readable opacity floors, refractive
+ edge highlights, restrained tint, and solid reduced-transparency fallbacks.
+
+ Glass material system - three elevation tiers.
+ Tier 0: header/ambient chrome (most transparent, heaviest blur)
+ Tier 1: cards and panels (the default surface)
+ Tier 2: overlays - menus, popovers, dropdowns (most opaque, most readable)
+ Opacity floors are set so ink-on-glass stays WCAG AA over the worst-case
+ frame of the animated background. Do not lower them. */
+ --color-glass: rgba(244, 240, 232, 0.74);
+ --color-glass-1: rgba(248, 245, 239, 0.72);
+ --color-glass-2: rgba(248, 245, 239, 0.92);
+ --color-glass-border: rgba(255, 255, 255, 0.58);
+ --color-glass-highlight: rgba(255, 255, 255, 0.72);
+ --color-glass-shadow: rgba(78, 66, 48, 0.18);
+ --color-glass-refraction: rgba(255, 255, 255, 0.24);
+ --color-glass-tint: rgba(0, 47, 167, 0.08);
+
+ /* Quality colors β tuned for WCAG AA (β₯4.5:1) on the light badge/card bg */
+ --color-quality-excellent: #155a8a;
+ --color-quality-good: #1c7032;
+ --color-quality-sufficient: #7a6000;
+ --color-quality-poor: #b02e20;
+ --color-quality-unknown: #374151;
+
+ /* UV index accent colors (AA on light badge/card bg) */
+ --color-uv-high: #8f4a10;
+ --color-uv-extreme: #8a4ba6;
+
+ /* Fonts: one workhorse family + the wordmark display face.
+ Radius rule: surfaces (cards, panels, overlays) = rounded-2xl,
+ controls (buttons, inputs) = rounded-lg, pills/badges/icon buttons =
+ rounded-full. Inline-link focus rings keep rounded-sm. */
+ --font-sans: "Zalando Sans Variable", system-ui, sans-serif;
+ --font-display: "Zalando Sans Variable", system-ui, sans-serif;
+ --font-title: "Special Gothic Expanded One", sans-serif;
+ --font-beach: "Zalando Sans Variable", system-ui, sans-serif;
+}
+
+/* Dark theme overrides */
+.dark {
+ --color-surface: #122c3e;
+ --color-surface-muted: #1a3a4f;
+ --color-ink: #f8fafc;
+ --color-ink-muted: #cbd5e1;
+ --color-border: #2c5c7a;
+ --color-accent: #38bdf8;
+ --color-badge: #1e4057;
+ --color-swimmer-cap: #38bdf8;
+
+ /* Glass tiers - ocean-tinted */
+ --color-glass: rgba(16, 54, 82, 0.8);
+ --color-glass-1: rgba(19, 60, 89, 0.82);
+ --color-glass-2: rgba(13, 38, 57, 0.94);
+ --color-glass-border: rgba(144, 216, 255, 0.24);
+ --color-glass-highlight: rgba(255, 255, 255, 0.2);
+ --color-glass-shadow: rgba(2, 16, 28, 0.5);
+ --color-glass-refraction: rgba(255, 255, 255, 0.08);
+ --color-glass-tint: rgba(56, 189, 248, 0.1);
+
+ /* Quality colors for dark mode β lighter shades, AA on dark badge/card bg */
+ --color-quality-excellent: #7cc4f2;
+ --color-quality-good: #5fd07f;
+ --color-quality-sufficient: #ecc25a;
+ --color-quality-poor: #fab3a7;
+ --color-quality-unknown: #c2cad4;
+
+ /* UV index accent colors (AA on dark badge/card bg) */
+ --color-uv-high: #f6b67a;
+ --color-uv-extreme: #dab6ec;
+}
+
+/* Base defaults */
+@layer base {
+ html,
+ body,
+ #root {
+ height: 100%;
+ }
+
+ body {
+ @apply text-ink antialiased font-sans;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4 {
+ @apply font-display font-semibold text-ink;
+ }
+
+ .font-title {
+ font-family: var(--font-title);
+ }
+
+ .font-beach {
+ font-family: var(--font-beach);
+ font-weight: 650;
+ letter-spacing: 0;
+ }
+
+ a {
+ @apply text-accent underline-offset-2 hover:underline
+ focus-visible:outline-none focus-visible:ring-2
+ focus-visible:ring-accent/50 rounded-sm;
+ }
+}
+
+/* Reusable bits */
+@layer components {
+ /* Display type carries weight and tracking on non-heading elements too */
+ .font-display {
+ font-weight: 600;
+ letter-spacing: 0;
+ }
+
+ /* Glass tier 1 - default surface for cards and panels */
+ .card {
+ @apply rounded-2xl border;
+ position: relative;
+ isolation: isolate;
+ overflow: hidden;
+ border-color: var(--color-glass-border);
+ background:
+ linear-gradient(
+ 135deg,
+ var(--color-glass-refraction),
+ transparent 42%
+ ),
+ linear-gradient(
+ 180deg,
+ var(--color-glass-1),
+ color-mix(in srgb, var(--color-glass-1) 86%, transparent)
+ );
+ backdrop-filter: blur(18px) saturate(1.24) contrast(1.03);
+ -webkit-backdrop-filter: blur(18px) saturate(1.24) contrast(1.03);
+ box-shadow:
+ inset 0 1px 0 var(--color-glass-highlight),
+ inset 0 -1px 0 rgba(255, 255, 255, 0.08),
+ 0 14px 42px var(--color-glass-shadow);
+ transition:
+ transform 150ms ease,
+ box-shadow 150ms ease;
+ }
+
+ .card::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ border-radius: inherit;
+ background:
+ radial-gradient(
+ circle at 18% 0%,
+ var(--color-glass-highlight),
+ transparent 30%
+ ),
+ linear-gradient(
+ 90deg,
+ transparent,
+ var(--color-glass-tint) 44%,
+ transparent 74%
+ );
+ opacity: 0.42;
+ pointer-events: none;
+ }
+
+ /* Interactive glass: lift on hover, press on tap */
+ .card-hover:hover {
+ transform: translateY(-2px);
+ box-shadow:
+ inset 0 1px 0 var(--color-glass-highlight),
+ inset 0 -1px 0 rgba(255, 255, 255, 0.1),
+ 0 18px 54px var(--color-glass-shadow);
+ }
+ .card-hover:active {
+ transform: translateY(0) scale(0.99);
+ }
+
+ /* Glass tier 2 - overlays: menus, popovers, dropdowns */
+ .glass-overlay {
+ @apply rounded-2xl border;
+ position: relative;
+ isolation: isolate;
+ overflow: hidden;
+ border-color: var(--color-glass-border);
+ background:
+ linear-gradient(135deg, var(--color-glass-refraction), transparent 46%),
+ var(--color-glass-2);
+ backdrop-filter: blur(26px) saturate(1.28) contrast(1.04);
+ -webkit-backdrop-filter: blur(26px) saturate(1.28) contrast(1.04);
+ box-shadow:
+ inset 0 1px 0 var(--color-glass-highlight),
+ inset 0 -1px 0 rgba(255, 255, 255, 0.1),
+ 0 18px 56px var(--color-glass-shadow);
+ }
+
+ .liquid-chip {
+ @apply inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium;
+ border-color: var(--color-glass-border);
+ color: var(--color-ink);
+ background: color-mix(in srgb, var(--color-glass-2) 70%, transparent);
+ backdrop-filter: blur(16px) saturate(1.2);
+ -webkit-backdrop-filter: blur(16px) saturate(1.2);
+ }
+
+ .page-shell {
+ @apply mx-auto w-full max-w-6xl px-4 py-4 sm:py-6;
+ }
+
+ .content-shell {
+ @apply mx-auto w-full max-w-3xl px-4 py-6 sm:px-6 sm:py-8;
+ }
+
+ /* Controls: rounded-lg per the radius rule, tactile press feedback */
+ .btn {
+ @apply inline-flex items-center justify-center gap-2 rounded-lg border
+ px-3 py-2 font-medium no-underline
+ hover:no-underline
+ focus-visible:outline-none focus-visible:ring-2
+ focus-visible:ring-accent/50
+ disabled:opacity-60 disabled:pointer-events-none;
+ border-color: var(--color-glass-border);
+ color: var(--color-ink);
+ background:
+ linear-gradient(135deg, var(--color-glass-refraction), transparent 45%),
+ color-mix(in srgb, var(--color-glass-2) 72%, transparent);
+ box-shadow:
+ inset 0 1px 0 var(--color-glass-highlight),
+ 0 8px 22px var(--color-glass-shadow);
+ backdrop-filter: blur(14px) saturate(1.18);
+ -webkit-backdrop-filter: blur(14px) saturate(1.18);
+ transition:
+ transform 120ms ease,
+ background-color 150ms ease;
+ }
+ .btn:hover {
+ background:
+ linear-gradient(135deg, var(--color-glass-refraction), transparent 45%),
+ color-mix(in srgb, var(--color-glass-2) 88%, transparent);
+ }
+ .btn:active {
+ transform: scale(0.98);
+ }
+
+ .btn-primary {
+ @apply border-transparent bg-accent text-white;
+ background:
+ linear-gradient(135deg, rgba(255, 255, 255, 0.24), transparent 44%),
+ var(--color-accent);
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.28),
+ 0 14px 34px color-mix(in srgb, var(--color-accent) 28%, transparent);
+ }
+ /* Dark-mode accent is light cyan; flip the label to ink-dark for AA */
+ .dark .btn-primary {
+ color: #07202e;
+ }
+
+ .input {
+ @apply w-full rounded-lg border border-border px-3 py-2
+ placeholder:text-ink-muted/70
+ focus:outline-none focus:ring-2 focus:ring-accent
+ focus:border-accent/50;
+ color: var(--color-ink);
+ background: var(--color-glass-2);
+ }
+
+ .badge {
+ @apply inline-flex items-center gap-1 px-2.5 py-1 rounded-full
+ text-xs font-medium bg-badge text-ink;
+ }
+
+ /* Glassmorphism header with smooth crossfade into background */
+ .glass-header {
+ position: relative;
+ padding-bottom: 12px;
+ }
+
+ /* Background layer with blur and fade - separate from content */
+ .glass-header::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to bottom,
+ var(--color-glass) 0%,
+ var(--color-glass) 50%,
+ rgba(239, 235, 228, 0.12) 80%,
+ transparent 100%
+ );
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ /* Mask fades out the blur smoothly */
+ mask-image: linear-gradient(
+ to bottom,
+ black 0%,
+ black 60%,
+ rgba(0, 0, 0, 0.5) 80%,
+ transparent 100%
+ );
+ -webkit-mask-image: linear-gradient(
+ to bottom,
+ black 0%,
+ black 60%,
+ rgba(0, 0, 0, 0.5) 80%,
+ transparent 100%
+ );
+ z-index: -1;
+ pointer-events: none;
+ }
+
+ .dark .glass-header::before {
+ background: linear-gradient(
+ to bottom,
+ var(--color-glass) 0%,
+ var(--color-glass) 50%,
+ rgba(20, 60, 90, 0.2) 80%,
+ transparent 100%
+ );
+ }
+
+ /* Soft glow/shadow beneath header for depth */
+ .glass-header::after {
+ content: "";
+ position: absolute;
+ bottom: -32px;
+ left: 0;
+ right: 0;
+ height: 32px;
+ background: linear-gradient(
+ to bottom,
+ rgba(239, 235, 228, 0.15) 0%,
+ rgba(239, 235, 228, 0.05) 40%,
+ transparent 100%
+ );
+ pointer-events: none;
+ z-index: -1;
+ }
+
+ .dark .glass-header::after {
+ background: linear-gradient(
+ to bottom,
+ rgba(20, 60, 90, 0.2) 0%,
+ rgba(20, 60, 90, 0.05) 40%,
+ transparent 100%
+ );
+ }
+
+ .kpi-excellent {
+ color: var(--color-quality-excellent);
+ }
+ .kpi-good {
+ color: var(--color-quality-good);
+ }
+ .kpi-sufficient {
+ color: var(--color-quality-sufficient);
+ }
+ .kpi-poor {
+ color: var(--color-quality-poor);
+ }
+ .kpi-unknown {
+ color: var(--color-quality-unknown);
+ }
+}
+
+@layer utilities {
+ /* widely supported; falls back to the fixed min-height below */
+ .aspect-square {
+ aspect-ratio: 1 / 1;
+ }
+
+ /* Screen reader only */
+ .sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+ }
+
+ /* Show on focus */
+ .sr-only:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ padding: inherit;
+ margin: inherit;
+ overflow: visible;
+ clip: auto;
+ white-space: normal;
+ }
+}
+
+/* ============================================
+ AMBIENT BACKGROUNDS
+ ============================================ */
+
+/* Light mode: Sand texture with subtle grain */
+.ambient-bg {
+ background:
+ /* Subtle noise texture overlay */ url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"),
+ /* Warm sand gradient */
+ linear-gradient(
+ 180deg,
+ #f6f2ec 0%,
+ #efebe4 30%,
+ #e8e2d8 70%,
+ #dfd8cc 100%
+ );
+ background-size: 200px 200px, 100% 100%;
+ animation: sand-drift 45s ease-in-out infinite;
+}
+
+@keyframes sand-drift {
+ 0%,
+ 100% {
+ background-position: 0 0, 0 0;
+ }
+ 50% {
+ background-position: 50px 25px, 0 0;
+ }
+}
+
+/* Dark mode: Animated water/ocean waves */
+.dark .ambient-bg {
+ background:
+ /* Deep wave layer 1 */ radial-gradient(
+ ellipse 150% 80% at 50% 120%,
+ rgba(30, 90, 140, 0.4) 0%,
+ transparent 60%
+ ),
+ /* Wave layer 2 */
+ radial-gradient(
+ ellipse 120% 60% at 30% 100%,
+ rgba(40, 100, 160, 0.35) 0%,
+ transparent 50%
+ ),
+ /* Wave layer 3 */
+ radial-gradient(
+ ellipse 100% 50% at 70% 110%,
+ rgba(50, 110, 170, 0.3) 0%,
+ transparent 40%
+ ),
+ /* Base deep ocean gradient */
+ linear-gradient(
+ 180deg,
+ #2a6f97 0%,
+ #357ca5 25%,
+ #468faf 50%,
+ #539bb8 75%,
+ #61a5c2 100%
+ );
+ background-size: 200% 100%, 180% 100%, 160% 100%, 100% 100%;
+ animation: ocean-waves 20s ease-in-out infinite;
+}
+
+@keyframes ocean-waves {
+ 0%,
+ 100% {
+ background-position: 0% 0%, 100% 0%, 50% 0%, 0% 0%;
+ }
+ 33% {
+ background-position: 50% 0%, 0% 0%, 100% 0%, 0% 0%;
+ }
+ 66% {
+ background-position: 100% 0%, 50% 0%, 0% 0%, 0% 0%;
+ }
+}
+
+/* Reduced motion support */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+
+ /* Static backgrounds for reduced motion preference */
+ .ambient-bg {
+ animation: none !important;
+ background-position: 0 0, 0 0 !important;
+ }
+
+ .dark .ambient-bg {
+ animation: none !important;
+ background-position: 50% 0%, 50% 0%, 50% 0%, 0% 0% !important;
+ }
+
+ .card-hover:hover,
+ .card-hover:active,
+ .btn:active {
+ transform: none;
+ }
+}
+
+/* Reduced transparency: solid fills, no blur */
+@media (prefers-reduced-transparency: reduce) {
+ .card,
+ .glass-overlay,
+ .input,
+ .btn,
+ .liquid-chip {
+ background: var(--color-surface);
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
+ box-shadow: none;
+ }
+
+ .card::before {
+ display: none;
+ }
+
+ .glass-header::before {
+ background: var(--color-surface);
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
+ mask-image: none;
+ -webkit-mask-image: none;
+ border-bottom: 1px solid var(--color-border);
+ }
+}
diff --git a/frontend/src/locales/en/common.json b/frontend/src/locales/en/common.json
new file mode 100644
index 0000000000..3b1c1d4583
--- /dev/null
+++ b/frontend/src/locales/en/common.json
@@ -0,0 +1,415 @@
+{
+ "appTitle": "Badlistan",
+ "loading": "Loadingβ¦",
+ "home": {
+ "title": "Find safer bathing water",
+ "subtitle": "Search EU-classified bathing sites, scan the map, and open official water details before you swim.",
+ "badge": "Official beach data",
+ "visible": "Visible",
+ "mode": "View",
+ "modeSearch": "Search results",
+ "modeNearby": "Near you",
+ "modeViewport": "Map area",
+ "modeDefault": "Stockholm area",
+ "resultsTitle": "Bathing sites",
+ "resultsMeta": "Showing {{visible}} of {{total}} sites",
+ "euSite": "EU site",
+ "openDetails": "Open details"
+ },
+ "beaches": "Beaches",
+ "loadingBeaches": "Loading beachesβ¦",
+ "loadError": "Could not load data.",
+ "back": "Back",
+ "waterQuality": "Water quality",
+ "latestYear": "Latest year",
+ "coordinates": "Coordinates",
+ "municipality": "Municipality",
+ "classification": {
+ "excellent": "Excellent",
+ "good": "Good",
+ "sufficient": "Sufficient",
+ "poor": "Poor",
+ "unknown": "Unknown",
+ "inProgress": "Classification in progress",
+ "notClassified": "Not classified"
+ },
+ "classificationTooltip": {
+ "excellent": "Highest water quality standards with excellent microbiological quality.",
+ "good": "Good water quality meeting required standards.",
+ "sufficient": "Water meets minimum quality standards.",
+ "poor": "Poor water quality. Swimming not recommended.",
+ "inProgress": "Classification is in progress. Check recent samples and local conditions.",
+ "notClassified": "This bathing site is not classified yet. Check recent samples and local conditions.",
+ "unknown": "Classification unavailable. Testing typically occurs MayβSeptember."
+ },
+ "apiErrors": {
+ "alreadyFavorited": "This beach is already in your favorites.",
+ "emailInUse": "An account with that email already exists.",
+ "internal": "Something went wrong. Please try again.",
+ "invalidBody": "Please check the form and try again.",
+ "invalidCredentials": "Email or password is incorrect.",
+ "notFound": "That item could not be found.",
+ "sessionExpired": "Your session has expired. Please sign in again.",
+ "tooManyRequests": "Too many attempts. Please wait a moment and try again.",
+ "upstream": "The official data source is unavailable right now."
+ },
+ "sourceTextNote": "Source text in Swedish",
+ "header": {
+ "menu": "Menu",
+ "language": "Language",
+ "searchPlaceholder": "Search beachesβ¦",
+ "clear": "Clear",
+ "openMenu": "Open menu",
+ "userMenu": "User menu",
+ "skipToMain": "Skip to main content",
+ "euBeaches": "EU Beaches",
+ "inSweden": "in Sweden",
+ "viewAllResults": "View all results on map"
+ },
+ "nav": {
+ "whatIsEUBeach": "What is an EU Beach?",
+ "about": "About Badlistan",
+ "terms": "Terms of Use",
+ "privacy": "Privacy Policy",
+ "contact": "Contact",
+ "favoriteBeaches": "Favourite beaches",
+ "profile": "Profile",
+ "settings": "Settings",
+ "logOut": "Log out",
+ "signIn": "Sign in",
+ "register": "Register",
+ "account": "Account",
+ "user": "User"
+ },
+ "auth": {
+ "signInTitle": "Sign in",
+ "registerTitle": "Register",
+ "email": "Email",
+ "password": "Password",
+ "confirmPassword": "Confirm Password",
+ "signIn": "Sign in",
+ "signInProgress": "Signing inβ¦",
+ "register": "Register",
+ "registerProgress": "Registeringβ¦",
+ "noAccount": "No account?",
+ "registerHere": "Register here",
+ "alreadyHaveAccount": "Already have an account?",
+ "signInHere": "Sign in here",
+ "signInSuccess": "Successfully signed in!",
+ "registerSuccess": "Registration successful! Please sign in.",
+ "signInFailed": "Login failed",
+ "registerFailed": "Registration failed",
+ "emailRequired": "Email is required",
+ "passwordRequired": "Password is required",
+ "passwordTooShort": "Password must be at least 8 characters",
+ "confirmPasswordRequired": "Confirm password is required",
+ "passwordsMustMatch": "Passwords must match",
+ "invalidEmail": "Invalid email address"
+ },
+ "favorites": {
+ "title": "Your favorite beaches",
+ "sortBy": "Sort by:",
+ "sortOptions": {
+ "custom": "Custom (drag & drop)",
+ "name": "Name (AβZ)",
+ "municipality": "Municipality (AβZ)"
+ },
+ "browseAll": "Browse all beaches",
+ "empty": "You have no favorite beaches yet",
+ "addFavorites": "Add favorites from the beach map",
+ "view": "View",
+ "remove": "Remove",
+ "dragToReorder": "Drag to reorder",
+ "added": "Added to favorites",
+ "removed": "Removed from favorites",
+ "error": "Could not update favorites. Please try again."
+ },
+ "beachDetail": {
+ "addFavorite": "Add favorite",
+ "removeFavorite": "Remove favorite",
+ "added": "Added to favorites",
+ "removed": "Removed from favorites",
+ "bathInformation": "About this beach",
+ "latestSampleDate": "Latest sample",
+ "beach": "Beach",
+ "algalBloom": "Algal bloom",
+ "euMotive": "EU motive",
+ "nutsCode": "NUTS code",
+ "mail": "Mail:",
+ "phone": "Phone:",
+ "website": "Website:",
+ "pleaseTryAgain": "Please try again.",
+ "euBad": "EU Beach",
+ "dissuasion": "Swimming advisory",
+ "advisoryPrecautionaryNote": "This advisory is precautionary and stays in force until conditions clearly improve β even though the most recent sample showed no algal bloom.",
+ "sourceAndSafety": "Source and safety note",
+ "safetyDisclaimer": "This shows the official EU/HaV classification and the most recently reported sample. It is not a real-time measurement of the water today. Conditions can change quickly, for example after heavy rain or warm weather. Always check how the water looks and smells. When in doubt, don't swim, especially with young children or pets.",
+ "safetyDisclaimerSource": "Check the official source (Badplatsen, HaV)",
+ "swimAdvisory": "Swimming advisory",
+ "checkLocalConditions": "Check local conditions"
+ },
+ "algalStatus": {
+ "ingenBlomning": "No blooming",
+ "blomning": "Blooming",
+ "kraftigBlomning": "Heavy blooming",
+ "mattligBlomning": "Moderate blooming",
+ "lindrigBlomning": "Mild blooming"
+ },
+ "algalSafety": {
+ "safe": "safe to swim",
+ "caution": "swim with caution",
+ "avoid": "swimming not advised"
+ },
+ "algalBloomInfo": "Shows the most recently reported algal bloom. No reported bloom doesn't guarantee the water is algae-free. There may be a bloom that simply hasn't been measured yet. If the water looks cloudy, discoloured or smells bad, avoid swimming, especially with children and pets.",
+ "euMotive": {
+ "storBadbelastning": "High bathing load",
+ "badbelastning": "Bathing load",
+ "tradition": "Traditional bathing site",
+ "turistattraktion": "Tourist attraction"
+ },
+ "beachesList": {
+ "modeDefault": "Show all",
+ "modeNearby": "Near me",
+ "modeViewport": "Show viewport",
+ "gettingLocation": "Getting locationβ¦",
+ "locationError": "Could not get your location",
+ "requestLocation": "Use current location",
+ "noBeachesFound": "No beaches found",
+ "emptyState": "Try adjusting your search filters",
+ "loadError": "Could not load beaches.",
+ "pleaseRetry": "Please try again.",
+ "retry": "Retry",
+ "noMatches": "No matches here.",
+ "panOrZoom": "Pan or zoom to a different area.",
+ "noMatchesHelp": "Zoom out, pan the map, or return to the start view.",
+ "resetToStart": "Reset to start",
+ "showNearby5km": "Show 5 km near me",
+ "widenRadius": "Try widening the radius.",
+ "mapUnavailable": "The map cannot be shown on this device. The beach list is still available below."
+ },
+ "theme": {
+ "darkOn": "Dark theme: on",
+ "darkOff": "Dark theme: off"
+ },
+ "pages": {
+ "about": {
+ "title": "About Badlistan",
+ "whatIsBADA": "What is Badlistan?",
+ "whatIsBADADesc": "Badlistan helps beachgoers and families in Sweden find safe, EU-classified bathing waters with real-time quality updates. We replace outdated or clunky websites with a clean, mobile-friendly experience.",
+ "whatIsBADAMission": "Our mission is to make water quality information accessible to everyone, helping you make informed decisions about where to swim and enjoy Sweden's beautiful coastal waters.",
+ "dataSource": "Data Source",
+ "dataSourceDesc": "Badlistan uses official data from the Swedish Agency for Marine and Water Management (Havs- och Vattenmyndigheten, HaV), which monitors and classifies EU bathing waters across Sweden.",
+ "dataSourceDirective": "Water quality classifications are based on the European Bathing Water Directive and are updated regularly throughout the bathing season.",
+ "features": "Features",
+ "featureMap": "Interactive map of all EU-classified beaches in Sweden",
+ "featureLocation": "Find the nearest beach based on your current location",
+ "featureQuality": "View water quality, classification, and recent test results",
+ "featureFavorites": "Save your favorite beaches to your profile",
+ "featureDarkMode": "Dark mode and responsive design",
+ "featureLang": "Multi-language support (Swedish / English)",
+ "builtWith": "Built With",
+ "builtWithDesc": "Badlistan is built with React, TypeScript, Node.js, Express, and MongoDB. Maps are powered by MapLibre and OpenStreetMap. See our",
+ "builtWithDesc2": "for more details.",
+ "githubRepo": "GitHub repository"
+ },
+ "whatIsEUBeach": {
+ "title": "What is an EU Beach?",
+ "directive": "EU Bathing Water Directive",
+ "directiveDesc": "An EU bathing water is a beach or section of a coastline where the European Union's Bathing Water Directive (2006/7/EC) applies. This directive requires member states to monitor and regularly test the water quality at designated bathing sites.",
+ "directiveDesc2": "In Sweden, the Swedish Agency for Marine and Water Management (HaV) monitors these beaches and classifies them according to water quality standards.",
+ "classifications": "Water Quality Classifications",
+ "excellentDesc": "Best water quality. The water meets the highest standards with excellent microbiological quality.",
+ "goodDesc": "Good water quality. The water meets the standards with good microbiological quality.",
+ "sufficientDesc": "Sufficient water quality. The water meets the minimum standards.",
+ "poorDesc": "Poor water quality. Swimming is not recommended. The water does not meet the minimum standards.",
+ "unknownDesc": "Water quality status is unknown or not classified. Note that most beaches conduct water tests between May and September. Therefore, you may see \"Unknown\" outside those months or for newly designated beaches.",
+ "monitoring": "Monitoring",
+ "monitoringDesc": "Water quality is monitored throughout the bathing season (typically May to September). Samples are taken regularly and tested for bacteria and other contaminants that could affect human health.",
+ "monitoringDesc2": "The classification is based on data from the last four bathing seasons to provide a stable and reliable assessment of water quality."
+ },
+ "terms": {
+ "title": "Terms of Use",
+ "acceptance": "Acceptance of Terms",
+ "acceptanceDesc": "By using Badlistan, you agree to these terms of use. If you do not agree with any part of these terms, please do not use our service.",
+ "service": "Service Description",
+ "serviceDesc": "Badlistan provides information about EU-classified bathing waters in Sweden. We aggregate data from official sources and present it in an accessible format.",
+ "disclaimer": "Disclaimer",
+ "disclaimerDesc": "The water quality information provided by Badlistan is based on official data from the Swedish Agency for Marine and Water Management. While we strive for accuracy, we cannot guarantee the completeness or timeliness of all information.",
+ "disclaimerDesc2": "Badlistan is provided \"as is\" without warranty of any kind. Users should exercise their own judgment and discretion when using this information to make decisions about swimming or other water activities.",
+ "accounts": "User Accounts",
+ "accountsDesc": "When you create an account, you are responsible for maintaining the confidentiality of your account information and password. You agree to notify us immediately of any unauthorized use of your account.",
+ "ip": "Intellectual Property",
+ "ipDesc": "The Badlistan application and its original content, features, and functionality are owned by the Badlistan project and are protected by international copyright, trademark, and other intellectual property laws.",
+ "changes": "Changes to Terms",
+ "changesDesc": "We reserve the right to modify these terms at any time. We will notify users of any significant changes by posting the new terms on this page.",
+ "contact": "Contact",
+ "contactDesc": "If you have questions about these terms, please",
+ "contactUs": "contact us"
+ },
+ "contact": {
+ "title": "Contact",
+ "getInTouch": "Get in Touch",
+ "getInTouchDesc": "We'd love to hear from you! Whether you have feedback, suggestions, questions, or found a bug, please don't hesitate to reach out.",
+ "github": "GitHub",
+ "githubDesc": "For bug reports, feature requests, or code contributions, please visit our",
+ "githubRepo": "GitHub repository",
+ "feedback": "Issues & Feedback",
+ "feedbackDesc": "If you encounter any issues with the application or have suggestions for improvement, please open an issue on GitHub. Your feedback helps us make Badlistan better.",
+ "accuracy": "Data Accuracy",
+ "accuracyDesc": "If you notice any inaccuracies in the water quality data or beach information, please let us know. We rely on official sources, but errors can occur and we'll work to correct them promptly.",
+ "privacy": "Privacy",
+ "privacyDesc": "For questions about privacy and data handling, please see our terms of use or contact us through GitHub."
+ },
+ "profile": {
+ "title": "Your Profile",
+ "accountInfo": "Account information",
+ "accountInfoDesc": "You are signed in. Your account lets you save beaches and access them from any device.",
+ "favorites": "Your favorites",
+ "favoritesDesc": "Manage your saved beaches and keep track of the places you want to revisit.",
+ "viewFavorites": "View favorites",
+ "signOut": "Sign out",
+ "signOutDesc": "Sign out of your account. You will need to sign in again to access saved beaches."
+ },
+ "settings": {
+ "title": "Settings",
+ "appearance": "Appearance",
+ "language": "Language",
+ "languageDescription": "Choose your preferred language for Badlistan.",
+ "theme": "Theme",
+ "themeDesc": "Switch between the light sand surface and the dark water surface.",
+ "useDark": "Use dark theme",
+ "useLight": "Use light theme",
+ "account": "Account",
+ "deleteAccount": "Delete account",
+ "deleteAccountDesc": "Permanently delete your account and all of your saved favourites. This cannot be undone.",
+ "deleteConfirm": "Are you sure? This permanently deletes your account and all your favourites.",
+ "deleteConfirmYes": "Yes, delete my account",
+ "deleting": "Deletingβ¦",
+ "cancel": "Cancel",
+ "deleteSuccess": "Your account and data have been deleted.",
+ "deleteError": "Could not delete your account. Please try again."
+ },
+ "privacy": {
+ "title": "Privacy Policy",
+ "intro": "This policy explains what personal data Badlistan collects, why, and the choices you have. Badlistan is a non-commercial project for finding EU-classified bathing waters in Sweden.",
+ "dataWeCollect": "Data we collect",
+ "dataWeCollectDesc": "If you create an account, we store your email address and a securely hashed version of your password (never the password itself). We also store the beaches you save as favourites.",
+ "location": "Location",
+ "locationDesc": "If you use \"find the nearest beach\", your device location is used in your browser to sort beaches by distance. It is processed on your device and is not sent to or stored on our servers.",
+ "howWeUse": "How we use your data",
+ "howWeUseDesc": "We use your data only to provide the service: to authenticate you, to show and manage your favourites, and to keep the app working. We do not sell your data or use it for advertising.",
+ "sharing": "Sharing and processors",
+ "sharingDesc": "We do not share your personal data with third parties for their own purposes. Data is stored and served through our infrastructure providers (database and hosting), who process it solely on our behalf. Beach quality data comes from the Swedish Agency for Marine and Water Management (HaV); that is a data source, not a recipient of your data.",
+ "storage": "Storage in your browser",
+ "storageDesc": "When you log in, the server sets a secure httpOnly session cookie so you stay signed in. JavaScript in the app cannot read this cookie, and you can clear it by logging out.",
+ "retention": "Retention",
+ "retentionDesc": "We keep your account data until you delete your account. When you delete your account, your profile and all your favourites are permanently removed.",
+ "rights": "Your rights",
+ "rightsDesc": "You can access and delete your data at any time. To delete everything, go to Settings and choose \"Delete account\". For any other privacy request, contact us.",
+ "contact": "Contact",
+ "contactDesc": "If you have questions about this policy or your data, please",
+ "contactUs": "contact us"
+ },
+ "backToMap": "Back to map",
+ "notFound": {
+ "title": "Page not found",
+ "desc": "The page you are looking for has moved or no longer exists."
+ }
+ },
+ "weather": {
+ "title": "Weather",
+ "temperature": "Temperature",
+ "feelsLike": "Feels like",
+ "waterTemp": "Water temperature",
+ "locationUnavailable": "Weather and sun times are unavailable because this beach is missing coordinates.",
+ "uvIndex": "UV index",
+ "uvLow": "Low",
+ "uvModerate": "Moderate",
+ "uvHigh": "High",
+ "uvVeryHigh": "Very high",
+ "uvExtreme": "Extreme",
+ "notAvailable": "N/A",
+ "error": "Weather data unavailable",
+ "forecast": "5-day forecast",
+ "today": "Today",
+ "high": "High",
+ "low": "Low",
+ "rain": "Rain",
+ "conditionClear": "Clear sky",
+ "conditionMainlyClear": "Mainly clear",
+ "conditionPartlyCloudy": "Partly cloudy",
+ "conditionOvercast": "Overcast",
+ "conditionFog": "Fog",
+ "conditionDrizzle": "Drizzle",
+ "conditionRain": "Rain",
+ "conditionShowers": "Showers",
+ "conditionSnow": "Snow",
+ "conditionThunderstorm": "Thunderstorm"
+ },
+ "sun": {
+ "title": "Sun times",
+ "sunrise": "Sunrise",
+ "sunset": "Sunset",
+ "goldenHour": "Golden hour",
+ "morning": "Morning",
+ "evening": "Evening",
+ "civilTwilightBegin": "Dawn",
+ "civilTwilightEnd": "Dusk",
+ "nauticalTwilightBegin": "Nautical dawn",
+ "nauticalTwilightEnd": "Nautical dusk",
+ "dayLength": "Day length",
+ "hourShort": "h",
+ "minuteShort": "m",
+ "error": "Sun times unavailable"
+ },
+ "watch": {
+ "watch": "Watch this beach",
+ "watching": "Watching",
+ "added": "Watch started β we'll email you when something changes.",
+ "removed": "Watch removed.",
+ "limit": "You can watch up to 20 beaches.",
+ "error": "Could not update the watch. Please try again."
+ },
+ "swimForecast": {
+ "title": "Swim forecast",
+ "howItWorks": "How this is calculated",
+ "disclaimer": "A short-term guide that blends the official EU/HaV classification and latest sample with recent and forecast rainfall (the main cause of bacterial spikes), sea temperature, wind and wave height. It is an advisory estimate, not a real-time measurement β an active swimming advisory always takes priority. Always check conditions on site.",
+ "level": {
+ "good": "Good to swim",
+ "caution": "Take care",
+ "avoid": "Avoid swimming",
+ "unknown": "Not enough data"
+ },
+ "confidenceLabel": {
+ "high": "High confidence",
+ "medium": "Medium confidence",
+ "low": "Low confidence"
+ },
+ "confidence": {
+ "fresh": "Based on a sample from the last week.",
+ "recent": "Based on a sample from the last two weeks.",
+ "stale": "The latest official sample is over two weeks old, so this leans more on weather.",
+ "noSample": "No recent official sample, so this leans on weather and classification."
+ },
+ "reason": {
+ "clear": "Clean baseline and dry weather β no red flags.",
+ "classification": "Based on this beach's official water-quality classification.",
+ "advisory": "An official swimming advisory is in force.",
+ "algalAvoid": "Algal bloom reported β avoid contact.",
+ "algalCaution": "Some algal bloom activity β take care, especially with children and pets.",
+ "heavyRain": "Heavy rain can wash bacteria into the water.",
+ "rain": "Rain can wash bacteria into the water.",
+ "warmWater": "Warm, calm water makes algal blooms more likely.",
+ "windDisperses": "The wind is helping break up the algal bloom.",
+ "highWaves": "High waves expected β swim with care.",
+ "noData": "This beach has no recent classification or sample."
+ }
+ },
+ "errorBoundary": {
+ "title": "Oops! Something went wrong",
+ "message": "We encountered an unexpected error. Please try refreshing the page or return to the homepage.",
+ "technicalDetails": "Technical details",
+ "reloadPage": "Reload Page",
+ "goHome": "Go Home"
+ }
+}
diff --git a/frontend/src/locales/sv/common.json b/frontend/src/locales/sv/common.json
new file mode 100644
index 0000000000..59917a4293
--- /dev/null
+++ b/frontend/src/locales/sv/common.json
@@ -0,0 +1,415 @@
+{
+ "appTitle": "Badlistan",
+ "loading": "Laddarβ¦",
+ "home": {
+ "title": "Hitta tryggare badvatten",
+ "subtitle": "SΓΆk EU-klassade badplatser, skanna kartan och ΓΆppna officiell vatteninfo innan du badar.",
+ "badge": "Officiell badplatsdata",
+ "visible": "Visas",
+ "mode": "Vy",
+ "modeSearch": "SΓΆkresultat",
+ "modeNearby": "NΓ€ra dig",
+ "modeViewport": "KartomrΓ₯de",
+ "modeDefault": "StockholmsomrΓ₯det",
+ "resultsTitle": "Badplatser",
+ "resultsMeta": "Visar {{visible}} av {{total}} badplatser",
+ "euSite": "EU-bad",
+ "openDetails": "Γppna detaljer"
+ },
+ "beaches": "Badplatser",
+ "loadingBeaches": "Laddar badplatserβ¦",
+ "loadError": "Kunde inte ladda data.",
+ "back": "Tillbaka",
+ "waterQuality": "Vattenkvalitet",
+ "latestYear": "Senaste Γ₯r",
+ "coordinates": "Koordinater",
+ "municipality": "Kommun",
+ "classification": {
+ "excellent": "UtmΓ€rkt kvalitet",
+ "good": "Bra kvalitet",
+ "sufficient": "TillfredsstΓ€llande kvalitet",
+ "poor": "DΓ₯lig kvalitet",
+ "unknown": "OkΓ€nd",
+ "inProgress": "Klassificering pΓ₯gΓ₯r",
+ "notClassified": "Ej klassificerad"
+ },
+ "classificationTooltip": {
+ "excellent": "HΓΆgsta vattenkvalitetsstandard med utmΓ€rkt mikrobiologisk kvalitet.",
+ "good": "Bra vattenkvalitet som uppfyller standarderna.",
+ "sufficient": "Vattnet uppfyller minimikvalitetsstandarderna.",
+ "poor": "DΓ₯lig vattenkvalitet. Simning rekommenderas ej.",
+ "inProgress": "Klassificeringen pΓ₯gΓ₯r. Kontrollera senaste prover och lokala fΓΆrhΓ₯llanden.",
+ "notClassified": "Badplatsen Γ€r inte klassificerad Γ€nnu. Kontrollera senaste prover och lokala fΓΆrhΓ₯llanden.",
+ "unknown": "Klassificering saknas. Provtagning sker vanligtvis majβseptember."
+ },
+ "apiErrors": {
+ "alreadyFavorited": "Badplatsen finns redan bland dina favoriter.",
+ "emailInUse": "Det finns redan ett konto med den e-postadressen.",
+ "internal": "NΓ₯got gick fel. FΓΆrsΓΆk igen.",
+ "invalidBody": "Kontrollera formulΓ€ret och fΓΆrsΓΆk igen.",
+ "invalidCredentials": "E-post eller lΓΆsenord Γ€r fel.",
+ "notFound": "Det gick inte att hitta posten.",
+ "sessionExpired": "Din session har gΓ₯tt ut. Logga in igen.",
+ "tooManyRequests": "FΓΆr mΓ₯nga fΓΆrsΓΆk. VΓ€nta en stund och fΓΆrsΓΆk igen.",
+ "upstream": "Den officiella datakΓ€llan Γ€r inte tillgΓ€nglig just nu."
+ },
+ "sourceTextNote": "KΓ€lltext pΓ₯ svenska",
+ "header": {
+ "menu": "Meny",
+ "language": "SprΓ₯k",
+ "searchPlaceholder": "SΓΆk badplatserβ¦",
+ "clear": "Rensa",
+ "openMenu": "Γppna meny",
+ "userMenu": "AnvΓ€ndarmenyn",
+ "skipToMain": "Hoppa till huvudinnehΓ₯llet",
+ "euBeaches": "EU-bad",
+ "inSweden": "i Sverige",
+ "viewAllResults": "Visa alla resultat pΓ₯ kartan"
+ },
+ "nav": {
+ "whatIsEUBeach": "Vad Γ€r en EU-badplats?",
+ "about": "Om Badlistan",
+ "terms": "AnvΓ€ndarvillkor",
+ "privacy": "Integritetspolicy",
+ "contact": "Kontakt",
+ "favoriteBeaches": "Favoritbadplatser",
+ "profile": "Profil",
+ "settings": "InstΓ€llningar",
+ "logOut": "Logga ut",
+ "signIn": "Logga in",
+ "register": "Registrera",
+ "account": "Konto",
+ "user": "AnvΓ€ndare"
+ },
+ "auth": {
+ "signInTitle": "Logga in",
+ "registerTitle": "Registrera",
+ "email": "E-post",
+ "password": "LΓΆsenord",
+ "confirmPassword": "BekrΓ€fta lΓΆsenord",
+ "signIn": "Logga in",
+ "signInProgress": "Loggar inβ¦",
+ "register": "Registrera",
+ "registerProgress": "Registrerarβ¦",
+ "noAccount": "Inget konto?",
+ "registerHere": "Registrera hΓ€r",
+ "alreadyHaveAccount": "Har du redan ett konto?",
+ "signInHere": "Logga in hΓ€r",
+ "signInSuccess": "Inloggad!",
+ "registerSuccess": "Registrering lyckades! Logga in.",
+ "signInFailed": "Inloggning misslyckades",
+ "registerFailed": "Registrering misslyckades",
+ "emailRequired": "E-post krΓ€vs",
+ "passwordRequired": "LΓΆsenord krΓ€vs",
+ "passwordTooShort": "LΓΆsenordet mΓ₯ste vara minst 8 tecken",
+ "confirmPasswordRequired": "BekrΓ€ftelse av lΓΆsenord krΓ€vs",
+ "passwordsMustMatch": "LΓΆsenorden mΓ₯ste matcha",
+ "invalidEmail": "Ogiltig e-postadress"
+ },
+ "favorites": {
+ "title": "Dina favoritbadplatser",
+ "sortBy": "Sortera efter:",
+ "sortOptions": {
+ "custom": "Egen ordning (dra & slΓ€pp)",
+ "name": "Namn (AβΓ)",
+ "municipality": "Kommun (AβΓ)"
+ },
+ "browseAll": "BlΓ€ddra bland alla badplatser",
+ "empty": "Du har inga favoritbadplatser Γ€nnu",
+ "addFavorites": "LΓ€gg till favoriter frΓ₯n badplatskartan",
+ "view": "Visa",
+ "remove": "Ta bort",
+ "dragToReorder": "Dra fΓΆr att Γ€ndra ordning",
+ "added": "Tillagd som favorit",
+ "removed": "Borttagen frΓ₯n favoriter",
+ "error": "Kunde inte uppdatera favoriter. FΓΆrsΓΆk igen."
+ },
+ "beachDetail": {
+ "addFavorite": "LΓ€gg till favorit",
+ "removeFavorite": "Ta bort favorit",
+ "added": "Tillagd som favorit",
+ "removed": "Borttagen frΓ₯n favoriter",
+ "bathInformation": "Om badplatsen",
+ "latestSampleDate": "Senaste provtagning",
+ "beach": "Badplats",
+ "algalBloom": "Algblomning",
+ "euMotive": "EU-motivering",
+ "nutsCode": "NUTS-kod",
+ "mail": "E-post:",
+ "phone": "Telefon:",
+ "website": "Webbplats:",
+ "pleaseTryAgain": "VΓ€nligen fΓΆrsΓΆk igen.",
+ "euBad": "EU-bad",
+ "dissuasion": "AvrΓ₯dan frΓ₯n bad",
+ "advisoryPrecautionaryNote": "AvrΓ₯dan Γ€r en fΓΆrsiktighetsΓ₯tgΓ€rd och gΓ€ller tills fΓΆrhΓ₯llandena tydligt fΓΆrbΓ€ttrats β Γ€ven om det senaste provet inte visade nΓ₯gon algblomning.",
+ "sourceAndSafety": "KΓ€lla och sΓ€kerhetsnotis",
+ "safetyDisclaimer": "Detta visar den officiella EU/HaV-klassificeringen och den senast rapporterade provtagningen. Det Γ€r inte en mΓ€tning av vattnet just idag. FΓΆrhΓ₯llandena kan Γ€ndras snabbt, till exempel efter kraftigt regn eller varmt vΓ€der. Titta alltid pΓ₯ hur vattnet ser ut och luktar. Bada inte om du Γ€r osΓ€ker, sΓ€rskilt med smΓ₯ barn eller husdjur.",
+ "safetyDisclaimerSource": "Se den officiella kΓ€llan (Badplatsen, HaV)",
+ "swimAdvisory": "AvrΓ₯dan frΓ₯n bad",
+ "checkLocalConditions": "Kontrollera platsen"
+ },
+ "algalStatus": {
+ "ingenBlomning": "Ingen blomning",
+ "blomning": "Blomning",
+ "kraftigBlomning": "Kraftig blomning",
+ "mattligBlomning": "MΓ₯ttlig blomning",
+ "lindrigBlomning": "Lindrig blomning"
+ },
+ "algalSafety": {
+ "safe": "sΓ€kert att bada",
+ "caution": "bada med fΓΆrsiktighet",
+ "avoid": "bad avrΓ₯des"
+ },
+ "algalBloomInfo": "Visar den senast rapporterade algblomningen. Att ingen blomning rapporterats betyder inte sΓ€kert att vattnet Γ€r fritt frΓ₯n alger. Det kan finnas blomning som Γ€nnu inte hunnit mΓ€tas. Γr vattnet grumligt, missfΓ€rgat eller luktar illa: undvik att bada, sΓ€rskilt fΓΆr barn och husdjur.",
+ "euMotive": {
+ "storBadbelastning": "Stor badbelastning",
+ "badbelastning": "Badbelastning",
+ "tradition": "Tradition",
+ "turistattraktion": "Turistattraktion"
+ },
+ "beachesList": {
+ "modeDefault": "Visa alla",
+ "modeNearby": "NΓ€ra mig",
+ "modeViewport": "Visa i fΓΆnster",
+ "gettingLocation": "HΓ€mtar positionβ¦",
+ "locationError": "Kunde inte hitta din position",
+ "requestLocation": "AnvΓ€nd min position",
+ "noBeachesFound": "Inga badplatser hittades",
+ "emptyState": "Prova att Γ€ndra sΓΆkfilter",
+ "loadError": "Kunde inte ladda badplatser.",
+ "pleaseRetry": "VΓ€nligen fΓΆrsΓΆk igen.",
+ "retry": "FΓΆrsΓΆk igen",
+ "noMatches": "Inga trΓ€ffar hΓ€r.",
+ "panOrZoom": "Panorera eller zooma till ett annat omrΓ₯de.",
+ "noMatchesHelp": "Zooma ut, panorera kartan eller gΓ₯ tillbaka till startvyn.",
+ "resetToStart": "Γ
tergΓ₯ till start",
+ "showNearby5km": "Visa 5 km nΓ€ra mig",
+ "widenRadius": "Prova att ΓΆka sΓΆkradien.",
+ "mapUnavailable": "Kartan kan inte visas pΓ₯ den hΓ€r enheten. Badplatserna visas fortfarande i listan nedan."
+ },
+ "theme": {
+ "darkOn": "MΓΆrkt tema: pΓ₯",
+ "darkOff": "MΓΆrkt tema: av"
+ },
+ "pages": {
+ "about": {
+ "title": "Om Badlistan",
+ "whatIsBADA": "Vad Γ€r Badlistan?",
+ "whatIsBADADesc": "Badlistan hjΓ€lper badgΓ€ster och familjer i Sverige att hitta sΓ€kra, EU-klassade badvatten med uppdateringar i realtid. Vi ersΓ€tter inaktuella eller tunga webbplatser med en ren, mobilanpassad upplevelse.",
+ "whatIsBADAMission": "VΓ₯rt uppdrag Γ€r att gΓΆra vattenkvalitetsinformation tillgΓ€nglig fΓΆr alla, och hjΓ€lpa dig att fatta informerade beslut om var du ska simma och njuta av Sveriges vackra kustvatten.",
+ "dataSource": "DatakΓ€lla",
+ "dataSourceDesc": "Badlistan anvΓ€nder officiell data frΓ₯n Havs- och Vattenmyndigheten (HaV), som ΓΆvervakar och klassar EU-badplatser ΓΆver hela Sverige.",
+ "dataSourceDirective": "Vattenkvalitetsklassningar baseras pΓ₯ EU:s badvattendirektiv och uppdateras regelbundet under badsΓ€songen.",
+ "features": "Funktioner",
+ "featureMap": "Interaktiv karta ΓΆver alla EU-klassade badplatser i Sverige",
+ "featureLocation": "Hitta nΓ€rmaste badplats baserat pΓ₯ din nuvarande plats",
+ "featureQuality": "Se vattenkvalitet, klassning och senaste provresultat",
+ "featureFavorites": "Spara dina favoritbadplatser till din profil",
+ "featureDarkMode": "MΓΆrkt lΓ€ge och responsiv design",
+ "featureLang": "FlersprΓ₯kigt stΓΆd (Svenska / Engelska)",
+ "builtWith": "Byggd med",
+ "builtWithDesc": "Badlistan Γ€r byggd med React, TypeScript, Node.js, Express och MongoDB. Kartor drivs av MapLibre och OpenStreetMap. Se vΓ₯r",
+ "builtWithDesc2": "fΓΆr mer information.",
+ "githubRepo": "GitHub-repository"
+ },
+ "whatIsEUBeach": {
+ "title": "Vad Γ€r en EU-badplats?",
+ "directive": "EU:s badvattendirektiv",
+ "directiveDesc": "En EU-badplats Γ€r en strand eller del av en kust dΓ€r EU:s badvattendirektiv (2006/7/EG) gΓ€ller. Detta direktiv krΓ€ver att medlemsstaterna ΓΆvervakar och regelbundet testar vattenkvaliteten vid utsedda badplatser.",
+ "directiveDesc2": "I Sverige ΓΆvervakar Havs- och Vattenmyndigheten (HaV) dessa badplatser och klassar dem enligt vattenkvalitetsstandarder.",
+ "classifications": "Vattenkvalitetsklassningar",
+ "excellentDesc": "BΓ€st vattenkvalitet. Vattnet uppfyller de hΓΆgsta standarderna med utmΓ€rkt mikrobiologisk kvalitet.",
+ "goodDesc": "Bra vattenkvalitet. Vattnet uppfyller standarderna med bra mikrobiologisk kvalitet.",
+ "sufficientDesc": "TillfredsstΓ€llande vattenkvalitet. Vattnet uppfyller minimistandarderna.",
+ "poorDesc": "DΓ₯lig vattenkvalitet. Simning rekommenderas inte. Vattnet uppfyller inte minimistandarderna.",
+ "unknownDesc": "Vattenkvalitetsstatus Γ€r okΓ€nd eller inte klassificerad. Observera att de flesta badplatser genomfΓΆr vattenprovtagningar mellan maj och september. DΓ€rfΓΆr kan du se \"OkΓ€nd\" utanfΓΆr dessa mΓ₯nader eller fΓΆr nyutnΓ€mnda badplatser.",
+ "monitoring": "Γvervakning",
+ "monitoringDesc": "Vattenkvaliteten ΓΆvervakas under hela badsΓ€songen (vanligtvis maj till september). Prover tas regelbundet och testas fΓΆr bakterier och andra fΓΆroreningar som kan pΓ₯verka mΓ€nniskors hΓ€lsa.",
+ "monitoringDesc2": "Klassningen baseras pΓ₯ data frΓ₯n de senaste fyra badsΓ€songerna fΓΆr att ge en stabil och tillfΓΆrlitlig bedΓΆmning av vattenkvaliteten."
+ },
+ "terms": {
+ "title": "AnvΓ€ndarvillkor",
+ "acceptance": "GodkΓ€nnande av villkor",
+ "acceptanceDesc": "Genom att anvΓ€nda Badlistan godkΓ€nner du dessa anvΓ€ndarvillkor. Om du inte hΓ₯ller med om nΓ₯gon del av dessa villkor, vΓ€nligen anvΓ€nd inte vΓ₯r tjΓ€nst.",
+ "service": "TjΓ€nstebeskrivning",
+ "serviceDesc": "Badlistan tillhandahΓ₯ller information om EU-klassade badvatten i Sverige. Vi samlar data frΓ₯n officiella kΓ€llor och presenterar den i ett tillgΓ€ngligt format.",
+ "disclaimer": "Villkor",
+ "disclaimerDesc": "Vattenkvalitetsinformationen som tillhandahΓ₯lls av Badlistan baseras pΓ₯ officiella data frΓ₯n Havs- och Vattenmyndigheten. Γven om vi strΓ€var efter noggrannhet kan vi inte garantera fullstΓ€ndigheten eller aktuellheten av all information.",
+ "disclaimerDesc2": "Badlistan tillhandahΓ₯lls \"som den Γ€r\" utan garantier av nΓ₯got slag. AnvΓ€ndare bΓΆr anvΓ€nda sitt eget omdΓΆme och diskretion nΓ€r de anvΓ€nder denna information fΓΆr att fatta beslut om simning eller andra vattenaktiviteter.",
+ "accounts": "AnvΓ€ndarkonton",
+ "accountsDesc": "NΓ€r du skapar ett konto Γ€r du ansvarig fΓΆr att upprΓ€tthΓ₯lla konfidentialiteten fΓΆr din kontoinformation och ditt lΓΆsenord. Du godkΓ€nner att meddela oss omedelbart om obehΓΆrig anvΓ€ndning av ditt konto.",
+ "ip": "Immateriella rΓ€ttigheter",
+ "ipDesc": "Badlistan-applikationen och dess ursprungliga innehΓ₯ll, funktioner och funktionalitet Γ€gs av Badlistan-projektet och skyddas av internationella upphovsrΓ€tt, varumΓ€rke och andra immateriella lagar.",
+ "changes": "Γndringar av villkor",
+ "changesDesc": "Vi fΓΆrbehΓ₯ller oss rΓ€tten att Γ€ndra dessa villkor nΓ€r som helst. Vi kommer att meddela anvΓ€ndare om betydande Γ€ndringar genom att publicera de nya villkoren pΓ₯ denna sida.",
+ "contact": "Kontakt",
+ "contactDesc": "Om du har frΓ₯gor om dessa villkor, vΓ€nligen",
+ "contactUs": "kontakta oss"
+ },
+ "contact": {
+ "title": "Kontakt",
+ "getInTouch": "HΓΆr av dig",
+ "getInTouchDesc": "Vi skulle gΓ€rna hΓΆra frΓ₯n dig! Om du har feedback, fΓΆrslag, frΓ₯gor eller hittat en bugg, tveka inte att kontakta oss.",
+ "github": "GitHub",
+ "githubDesc": "FΓΆr buggrapporter, funktionsfΓΆrfrΓ₯gningar eller kodbidrag, besΓΆk vΓ₯r",
+ "githubRepo": "GitHub-repository",
+ "feedback": "Problem & Feedback",
+ "feedbackDesc": "Om du stΓΆter pΓ₯ nΓ₯gra problem med applikationen eller har fΓΆrslag pΓ₯ fΓΆrbΓ€ttringar, ΓΆppna gΓ€rna ett issue pΓ₯ GitHub. Din feedback hjΓ€lper oss att gΓΆra Badlistan bΓ€ttre.",
+ "accuracy": "Datan noggrannhet",
+ "accuracyDesc": "Om du mΓ€rker nΓ₯gra felaktigheter i vattenkvalitetsdata eller badplatsinformation, lΓ₯t oss veta. Vi fΓΆrlitar oss pΓ₯ officiella kΓ€llor, men fel kan uppstΓ₯ och vi kommer att arbeta fΓΆr att korrigera dem snabbt.",
+ "privacy": "Integritet",
+ "privacyDesc": "FΓΆr frΓ₯gor om integritet och datahantering, se vΓ₯ra anvΓ€ndarvillkor eller kontakta oss via GitHub."
+ },
+ "profile": {
+ "title": "Din profil",
+ "accountInfo": "Kontoinformation",
+ "accountInfoDesc": "Du Γ€r inloggad. Ditt konto lΓ₯ter dig spara badplatser och nΓ₯ dem frΓ₯n vilken enhet som helst.",
+ "favorites": "Dina favoriter",
+ "favoritesDesc": "Hantera dina sparade badplatser och hΓ₯ll koll pΓ₯ platserna du vill Γ₯tervΓ€nda till.",
+ "viewFavorites": "Visa favoriter",
+ "signOut": "Logga ut",
+ "signOutDesc": "Logga ut frΓ₯n ditt konto. Du behΓΆver logga in igen fΓΆr att nΓ₯ sparade badplatser."
+ },
+ "settings": {
+ "title": "InstΓ€llningar",
+ "appearance": "Utseende",
+ "language": "SprΓ₯k",
+ "languageDescription": "VΓ€lj ditt fΓΆredragna sprΓ₯k fΓΆr Badlistan.",
+ "theme": "Tema",
+ "themeDesc": "VΓ€xla mellan den ljusa sandytan och den mΓΆrka vattenytan.",
+ "useDark": "AnvΓ€nd mΓΆrkt tema",
+ "useLight": "AnvΓ€nd ljust tema",
+ "account": "Konto",
+ "deleteAccount": "Radera konto",
+ "deleteAccountDesc": "Radera ditt konto och alla dina sparade favoriter permanent. Detta gΓ₯r inte att Γ₯ngra.",
+ "deleteConfirm": "Γr du sΓ€ker? Detta raderar ditt konto och alla dina favoriter permanent.",
+ "deleteConfirmYes": "Ja, radera mitt konto",
+ "deleting": "Raderarβ¦",
+ "cancel": "Avbryt",
+ "deleteSuccess": "Ditt konto och dina uppgifter har raderats.",
+ "deleteError": "Kunde inte radera ditt konto. FΓΆrsΓΆk igen."
+ },
+ "privacy": {
+ "title": "Integritetspolicy",
+ "intro": "Den hΓ€r policyn fΓΆrklarar vilka personuppgifter Badlistan samlar in, varfΓΆr, och vilka val du har. Badlistan Γ€r ett ideellt projekt fΓΆr att hitta EU-klassificerade badvatten i Sverige.",
+ "dataWeCollect": "Uppgifter vi samlar in",
+ "dataWeCollectDesc": "Om du skapar ett konto lagrar vi din e-postadress och en sΓ€kert hashad version av ditt lΓΆsenord (aldrig sjΓ€lva lΓΆsenordet). Vi lagrar Γ€ven de badplatser du sparar som favoriter.",
+ "location": "Plats",
+ "locationDesc": "Om du anvΓ€nder \"hitta nΓ€rmaste badplats\" anvΓ€nds din enhets position i webblΓ€saren fΓΆr att sortera badplatser efter avstΓ₯nd. Den behandlas pΓ₯ din enhet och skickas inte till eller lagras pΓ₯ vΓ₯ra servrar.",
+ "howWeUse": "Hur vi anvΓ€nder dina uppgifter",
+ "howWeUseDesc": "Vi anvΓ€nder dina uppgifter endast fΓΆr att tillhandahΓ₯lla tjΓ€nsten: fΓΆr att logga in dig, visa och hantera dina favoriter och hΓ₯lla appen igΓ₯ng. Vi sΓ€ljer inte dina uppgifter och anvΓ€nder dem inte fΓΆr reklam.",
+ "sharing": "Delning och personuppgiftsbitrΓ€den",
+ "sharingDesc": "Vi delar inte dina personuppgifter med tredje part fΓΆr deras egna Γ€ndamΓ₯l. Uppgifter lagras och levereras via vΓ₯ra infrastrukturleverantΓΆrer (databas och hosting), som endast behandlar dem fΓΆr vΓ₯r rΓ€kning. Badvattendata kommer frΓ₯n Havs- och vattenmyndigheten (HaV); det Γ€r en datakΓ€lla, inte en mottagare av dina uppgifter.",
+ "storage": "Lagring i din webblΓ€sare",
+ "storageDesc": "NΓ€r du loggar in sΓ€tter servern en sΓ€ker httpOnly-sessionscookie sΓ₯ att du fΓΆrblir inloggad. JavaScript i appen kan inte lΓ€sa cookien, och du kan rensa den genom att logga ut.",
+ "retention": "Lagringstid",
+ "retentionDesc": "Vi behΓ₯ller dina kontouppgifter tills du raderar ditt konto. NΓ€r du raderar ditt konto tas din profil och alla dina favoriter bort permanent.",
+ "rights": "Dina rΓ€ttigheter",
+ "rightsDesc": "Du kan nΓ€r som helst fΓ₯ tillgΓ₯ng till och radera dina uppgifter. FΓΆr att radera allt, gΓ₯ till InstΓ€llningar och vΓ€lj \"Radera konto\". FΓΆr ΓΆvriga integritetsfrΓ₯gor, kontakta oss.",
+ "contact": "Kontakt",
+ "contactDesc": "Om du har frΓ₯gor om denna policy eller dina uppgifter, vΓ€nligen",
+ "contactUs": "kontakta oss"
+ },
+ "backToMap": "Tillbaka till kartan",
+ "notFound": {
+ "title": "Sidan finns inte",
+ "desc": "Sidan du letar efter har flyttats eller finns inte lΓ€ngre."
+ }
+ },
+ "weather": {
+ "title": "VΓ€der",
+ "temperature": "Temperatur",
+ "feelsLike": "KΓ€nns som",
+ "waterTemp": "Vattentemperatur",
+ "locationUnavailable": "VΓ€der och soltider kan inte visas eftersom badplatsen saknar koordinater.",
+ "uvIndex": "UV-index",
+ "uvLow": "LΓ₯gt",
+ "uvModerate": "MΓ₯ttligt",
+ "uvHigh": "HΓΆgt",
+ "uvVeryHigh": "Mycket hΓΆgt",
+ "uvExtreme": "Extremt",
+ "notAvailable": "Ej tillgΓ€nglig",
+ "error": "VΓ€derdata ej tillgΓ€nglig",
+ "forecast": "5-dygnsprognos",
+ "today": "Idag",
+ "high": "Max",
+ "low": "Min",
+ "rain": "Regn",
+ "conditionClear": "Klart",
+ "conditionMainlyClear": "Mestadels klart",
+ "conditionPartlyCloudy": "Delvis molnigt",
+ "conditionOvercast": "Mulet",
+ "conditionFog": "Dimma",
+ "conditionDrizzle": "Duggregn",
+ "conditionRain": "Regn",
+ "conditionShowers": "Skurar",
+ "conditionSnow": "SnΓΆ",
+ "conditionThunderstorm": "Γ
ska"
+ },
+ "sun": {
+ "title": "Soltider",
+ "sunrise": "SoluppgΓ₯ng",
+ "sunset": "SolnedgΓ₯ng",
+ "goldenHour": "Gyllene timmen",
+ "morning": "Morgon",
+ "evening": "KvΓ€ll",
+ "civilTwilightBegin": "Gryning",
+ "civilTwilightEnd": "Skymning",
+ "nauticalTwilightBegin": "Nautisk gryning",
+ "nauticalTwilightEnd": "Nautisk skymning",
+ "dayLength": "DaglΓ€ngd",
+ "hourShort": "t",
+ "minuteShort": "m",
+ "error": "Soltider ej tillgΓ€ngliga"
+ },
+ "watch": {
+ "watch": "Bevaka badplatsen",
+ "watching": "Bevakas",
+ "added": "Bevakning startad β vi mejlar dig vid fΓΆrΓ€ndringar.",
+ "removed": "Bevakningen Γ€r avslutad.",
+ "limit": "Du kan bevaka hΓΆgst 20 badplatser.",
+ "error": "Kunde inte uppdatera bevakningen. FΓΆrsΓΆk igen."
+ },
+ "swimForecast": {
+ "title": "Badprognos",
+ "howItWorks": "SΓ₯ berΓ€knas detta",
+ "disclaimer": "En kortsiktig vΓ€gledning som vΓ€ger samman den officiella EU/HaV-klassificeringen och senaste provet med nyligen fallet och vΓ€ntat regn (den vanligaste orsaken till bakterietoppar), vattentemperatur, vind och vΓ₯ghΓΆjd. Det Γ€r en uppskattning, inte en realtidsmΓ€tning β ett aktivt badfΓΆrbud gΓ₯r alltid fΓΆrst. Kontrollera alltid fΓΆrhΓ₯llandena pΓ₯ plats.",
+ "level": {
+ "good": "Bra att bada",
+ "caution": "Var fΓΆrsiktig",
+ "avoid": "Undvik att bada",
+ "unknown": "OtillrΓ€ckliga data"
+ },
+ "confidenceLabel": {
+ "high": "HΓΆg tillfΓΆrlitlighet",
+ "medium": "Medel tillfΓΆrlitlighet",
+ "low": "LΓ₯g tillfΓΆrlitlighet"
+ },
+ "confidence": {
+ "fresh": "Baserat pΓ₯ ett prov frΓ₯n den senaste veckan.",
+ "recent": "Baserat pΓ₯ ett prov frΓ₯n de senaste tvΓ₯ veckorna.",
+ "stale": "Det senaste officiella provet Γ€r ΓΆver tvΓ₯ veckor gammalt, sΓ₯ detta bygger mer pΓ₯ vΓ€dret.",
+ "noSample": "Inget nytt officiellt prov, sΓ₯ detta bygger pΓ₯ vΓ€der och klassificering."
+ },
+ "reason": {
+ "clear": "Ren grund och torrt vΓ€der β inga varningsflaggor.",
+ "classification": "Baserat pΓ₯ badplatsens officiella vattenkvalitetsklassificering.",
+ "advisory": "Ett officiellt badfΓΆrbud gΓ€ller.",
+ "algalAvoid": "Algblomning rapporterad β undvik kontakt.",
+ "algalCaution": "Viss algblomning β var fΓΆrsiktig, sΓ€rskilt med barn och husdjur.",
+ "heavyRain": "Kraftigt regn kan spola ut bakterier i vattnet.",
+ "rain": "Regn kan spola ut bakterier i vattnet.",
+ "warmWater": "Varmt, stilla vatten ΓΆkar risken fΓΆr algblomning.",
+ "windDisperses": "BlΓ₯sten hjΓ€lper till att bryta upp algblomningen.",
+ "highWaves": "HΓΆga vΓ₯gor vΓ€ntas β bada med fΓΆrsiktighet.",
+ "noData": "Denna badplats saknar ny klassificering eller prov."
+ }
+ },
+ "errorBoundary": {
+ "title": "Hoppsan! NΓ₯got gick fel",
+ "message": "Vi stΓΆtte pΓ₯ ett ovΓ€ntat fel. VΓ€nligen fΓΆrsΓΆk uppdatera sidan eller Γ₯tervΓ€nd till startsidan.",
+ "technicalDetails": "Tekniska detaljer",
+ "reloadPage": "Ladda om sidan",
+ "goHome": "GΓ₯ till startsidan"
+ }
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
deleted file mode 100644
index 51294f3998..0000000000
--- a/frontend/src/main.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import { App } from "./App.jsx";
-import "./index.css";
-
-ReactDOM.createRoot(document.getElementById("root")).render(
-
-
-
-);
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000000..574950140f
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,72 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { BrowserRouter } from "react-router";
+import { Toaster } from "react-hot-toast";
+import i18n from "./i18n";
+import "@fontsource-variable/zalando-sans";
+import "@fontsource/special-gothic-expanded-one";
+import "./index.css";
+import "maplibre-gl/dist/maplibre-gl.css";
+
+const queryClient = new QueryClient();
+
+const savedTheme = localStorage.getItem("theme");
+document.documentElement.classList.toggle(
+ "dark",
+ savedTheme ? savedTheme === "dark" : true
+);
+
+// Set HTML lang attribute based on i18n language
+i18n.on("languageChanged", (lng) => {
+ document.documentElement.lang = lng;
+});
+
+// Set initial language on mount
+document.documentElement.lang = i18n.language;
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+
+
+
+);
diff --git a/frontend/src/pages/AboutPage.tsx b/frontend/src/pages/AboutPage.tsx
new file mode 100644
index 0000000000..a4dfbb1ae6
--- /dev/null
+++ b/frontend/src/pages/AboutPage.tsx
@@ -0,0 +1,100 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import {
+ ArrowLeft,
+ GlobeHemisphereWest,
+ Heart,
+ MapTrifold,
+ Moon,
+ NavigationArrow,
+ TestTube,
+ type Icon,
+} from "@phosphor-icons/react";
+
+export default function AboutPage() {
+ const { t } = useTranslation();
+ const features: Array<{ key: string; Icon: Icon }> = [
+ { key: "featureMap", Icon: MapTrifold },
+ { key: "featureLocation", Icon: NavigationArrow },
+ { key: "featureQuality", Icon: TestTube },
+ { key: "featureFavorites", Icon: Heart },
+ { key: "featureDarkMode", Icon: Moon },
+ { key: "featureLang", Icon: GlobeHemisphereWest },
+ ];
+
+ return (
+
+ {t("pages.about.title")}
+
+
+ {t("pages.about.whatIsBADA")}
+
+ {t("pages.about.whatIsBADADesc")}
+
+
+ {t("pages.about.whatIsBADAMission")}
+
+
+
+
+ {t("pages.about.dataSource")}
+
+ {t("pages.about.dataSourceDesc")}
+
+
+ {t("pages.about.dataSourceDirective")}
+
+
+
+
+ {t("pages.about.features")}
+
+
+
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/BeachDetailPage.tsx b/frontend/src/pages/BeachDetailPage.tsx
new file mode 100644
index 0000000000..fc79ab38de
--- /dev/null
+++ b/frontend/src/pages/BeachDetailPage.tsx
@@ -0,0 +1,516 @@
+import { useParams, Link, useNavigate, useLocation } from "react-router";
+import { useQuery, useQueryClient } from "@tanstack/react-query";
+import { useTranslation } from "react-i18next";
+import toast from "react-hot-toast";
+import { fetchBeach } from "../api/beaches";
+import { formatDate } from "../utils/format";
+import {
+ getQualityCategory,
+ classificationLabelKey,
+ qualityToneClass,
+} from "../utils/quality";
+import { getAlgalStatusKey, getAlgalSafety, getEuMotiveKey } from "../utils/algal";
+import {
+ useFavorites,
+ useAddFavorite,
+ useRemoveFavorite,
+} from "../api/favorites";
+import { useWatches, useAddWatch, useRemoveWatch } from "../api/watches";
+import { useAuth } from "@/store/auth";
+import {
+ ArrowLeft,
+ Bell,
+ BellRinging,
+ CalendarBlank,
+ ClipboardText,
+ EnvelopeSimple,
+ Globe,
+ Info,
+ Leaf,
+ MapPin,
+ Phone,
+ Star,
+ Warning,
+ type Icon,
+} from "@phosphor-icons/react";
+import Tooltip from "../components/Tooltip";
+import BeachWeatherPanel from "../components/BeachWeatherPanel";
+import SwimForecastCard from "../components/SwimForecastCard";
+import { useDocumentTitle } from "../hooks/useDocumentTitle";
+import { apiErrorMessage } from "@/api/client";
+
+// Map numeric/class text β color class
+function DetailFact({
+ icon: FactIcon,
+ label,
+ value,
+}: {
+ icon: Icon;
+ label: string;
+ value: React.ReactNode;
+}) {
+ return (
+
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
+
+function ContactItem({
+ icon: ContactIcon,
+ label,
+ children,
+}: {
+ icon: Icon;
+ label: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+
{label}
+
+ {children}
+
+
+
+ );
+}
+
+export default function BeachDetailPage() {
+ const { t } = useTranslation();
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const status = useAuth((s) => s.status);
+
+ const { data, isLoading, isError, error } = useQuery({
+ queryKey: ["beach", id],
+ enabled: !!id,
+ queryFn: () => fetchBeach(id!),
+ staleTime: 5 * 60 * 1000,
+ });
+
+ useDocumentTitle(data?.locationName);
+
+ // --- FAVORITES HOOKS ---
+ const queryClient = useQueryClient();
+ const { data: favorites } = useFavorites();
+ const addFav = useAddFavorite();
+ const rmFav = useRemoveFavorite();
+ const existingFav = favorites?.find((f) => f.beachId === id);
+ const isFav = !!existingFav;
+
+ // --- WATCH (bevakning) HOOKS ---
+ const { data: watches } = useWatches(status === "authenticated");
+ const addWatch = useAddWatch();
+ const rmWatch = useRemoveWatch();
+ const isWatched = !!watches?.some((w) => w.beachId === id);
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (isError || !data) {
+ return (
+
+
+
{t("loadError")}
+
+ {(error as Error)?.message ?? t("beachDetail.pleaseTryAgain")}
+
+
+
{t("back")}
+
+
+
+ );
+ }
+
+ const title = data.locationName ?? t("beachDetail.beach");
+ const muni = data.locationArea ?? "";
+ // Label and pill color both derive from one category, so they can't disagree.
+ const qualityCategory = getQualityCategory(
+ data.classification ?? data.classificationText
+ );
+ const qualityText = t(classificationLabelKey(qualityCategory));
+ const pillClass = qualityToneClass(qualityCategory);
+
+ // Short description for the tooltip (falls back to the key if none exists).
+ const qualityTooltip = t(`classificationTooltip.${qualityCategory}`);
+
+ const latestSampleLabel = data.latestSampleDate
+ ? formatDate(data.latestSampleDate, "short")
+ : "-";
+
+ // HaV sometimes sends "false"/"true"/empty for unset text fields. Treat
+ // those as no-data so we never print a raw "false" to the user.
+ const cleanText = (v: unknown): string => {
+ const s = String(v ?? "").trim();
+ const lower = s.toLowerCase();
+ return s === "" || lower === "false" || lower === "true" ? "" : s;
+ };
+
+ // Translate algal status
+ const algalKey = getAlgalStatusKey(data.algalText);
+ const algalDisplay = algalKey ? t(algalKey) : cleanText(data.algalText) || "-";
+
+ // Swim-safety verdict derived from the bloom status (null when unmeasured)
+ const algalSafety = getAlgalSafety(algalKey);
+ const algalSafetyToneClass =
+ algalSafety?.tone === "safe"
+ ? "kpi-good"
+ : algalSafety?.tone === "caution"
+ ? "kpi-sufficient"
+ : "kpi-poor";
+
+ // Translate EU motive
+ const euMotiveKey = getEuMotiveKey(data.euMotive);
+ const euMotiveDisplay = euMotiveKey
+ ? t(euMotiveKey)
+ : cleanText(data.euMotive) || "-";
+ const hasContact =
+ Boolean(data.contactMail) ||
+ Boolean(data.contactPhone) ||
+ Boolean(data.contactUrl);
+ const hasDissuasion = Boolean(data.dissuasion && data.dissuasion.length > 0);
+ // An active advisory can coexist with a clean last algae sample (the advisory
+ // is precautionary and stays until conditions clearly improve). Flag that case
+ // so we can explain it instead of showing a contradicting "safe to swim".
+ const algaeCleanUnderAdvisory =
+ hasDissuasion && algalSafety?.tone === "safe";
+ const swimVerdictClass = hasDissuasion
+ ? "kpi-poor"
+ : algalSafety
+ ? algalSafetyToneClass
+ : "kpi-unknown";
+ const swimVerdictText = hasDissuasion
+ ? t("beachDetail.swimAdvisory")
+ : algalSafety
+ ? t(algalSafety.key)
+ : t("beachDetail.checkLocalConditions");
+
+ async function handleFavoriteClick() {
+ if (status !== "authenticated") {
+ navigate("/login", {
+ replace: false,
+ state: { from: location, favoriteBeachId: id },
+ });
+ return;
+ }
+ try {
+ if (isFav) {
+ await rmFav.mutateAsync({ id: existingFav?._id, beachId: id });
+ toast.success(t("favorites.removed"));
+ } else {
+ await addFav.mutateAsync(id!);
+ toast.success(t("favorites.added"));
+ }
+ queryClient.invalidateQueries({ queryKey: ["favorites"] });
+ queryClient.invalidateQueries({ queryKey: ["beach", id] });
+ } catch (e: any) {
+ const errorMsg = apiErrorMessage(e, t, t("favorites.error"));
+ toast.error(errorMsg);
+ }
+ }
+
+ async function handleWatchClick() {
+ if (status !== "authenticated") {
+ navigate("/login", { replace: false, state: { from: location } });
+ return;
+ }
+ try {
+ if (isWatched) {
+ await rmWatch.mutateAsync(id!);
+ toast.success(t("watch.removed"));
+ } else {
+ await addWatch.mutateAsync(id!);
+ toast.success(t("watch.added"));
+ }
+ } catch (e: any) {
+ // At the abuse cap β explain the limit instead of a generic error.
+ if (e instanceof Error && e.message === "WatchLimitReached") {
+ toast(t("watch.limit"), { icon: "β¨" });
+ return;
+ }
+ toast.error(apiErrorMessage(e, t, t("watch.error")));
+ }
+ }
+
+ const detailLat =
+ typeof data.lat === "number"
+ ? data.lat
+ : typeof data.latitude === "number"
+ ? data.latitude
+ : undefined;
+ const detailLon =
+ typeof data.lon === "number"
+ ? data.lon
+ : typeof data.longitude === "number"
+ ? data.longitude
+ : undefined;
+
+ return (
+
+
+
+
+
+
+
+
+ {t("waterQuality")}
+
+
+ {qualityText}
+
+ {data.classificationYear && (
+
+ {t("latestYear")} {data.classificationYear}
+
+ )}
+
+
+
+
+ {hasDissuasion && (
+
+
+
+ {t("beachDetail.dissuasion")}
+
+
+ {data.dissuasion?.map((d, i) => {
+ // Defensive: tolerate a bare string (old shape / cache) or an object.
+ const entry =
+ typeof d === "string" ? { description: d } : d ?? {};
+ const label =
+ [entry.dissuasionTypeText, entry.description]
+ .map(cleanText)
+ .filter(Boolean)
+ .join(" β ") || t("beachDetail.dissuasion");
+ return (
+
+ {label}
+ {entry.startdate
+ ? ` (${formatDate(entry.startdate, "short")})`
+ : ""}
+
+ );
+ })}
+
+ {algaeCleanUnderAdvisory && (
+
+ {t("beachDetail.advisoryPrecautionaryNote")}
+
+ )}
+
+ )}
+
+
+
+
+ {algalDisplay}
+ {!hasDissuasion && algalSafety && (
+ <>
+ {" - "}
+
+ {t(algalSafety.key)}
+
+ >
+ )}
+
+ }
+ />
+
+
+
+
+
+
+ {t("beachDetail.sourceAndSafety")}
+
+
+ {t("beachDetail.safetyDisclaimer")}{" "}
+
+ {t("beachDetail.safetyDisclaimerSource")}
+
+ .
+
+
+
+
+
+
+ {isFav
+ ? t("beachDetail.removeFavorite")
+ : t("beachDetail.addFavorite")}
+
+
+
+ {isWatched ? (
+
+ ) : (
+
+ )}
+ {isWatched ? t("watch.watching") : t("watch.watch")}
+
+
+
+
{t("back")}
+
+
+
+
+ {id && (
+
+ )}
+
+ {id && }
+
+ {(data.bathInformation || hasContact) && (
+
+ {data.bathInformation && (
+
+
+ {t("beachDetail.bathInformation")}
+
+
+ {data.bathInformation}
+
+
+ )}
+
+ {hasContact && (
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/frontend/src/pages/ContactPage.tsx b/frontend/src/pages/ContactPage.tsx
new file mode 100644
index 0000000000..0d7cd5b4f8
--- /dev/null
+++ b/frontend/src/pages/ContactPage.tsx
@@ -0,0 +1,65 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import { ArrowLeft } from "@phosphor-icons/react";
+
+export default function ContactPage() {
+ const { t } = useTranslation();
+ return (
+
+ {t("pages.contact.title")}
+
+
+ {t("pages.contact.getInTouch")}
+
+ {t("pages.contact.getInTouchDesc")}
+
+
+
+
+
+
+ {t("pages.contact.feedback")}
+
+ {t("pages.contact.feedbackDesc")}
+
+
+
+
+ {t("pages.contact.accuracy")}
+
+ {t("pages.contact.accuracyDesc")}
+
+
+
+
+ {t("pages.contact.privacy")}
+
+ {t("pages.contact.privacyDesc")}
+
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/FavoritesPage.tsx b/frontend/src/pages/FavoritesPage.tsx
new file mode 100644
index 0000000000..53a61ebdbe
--- /dev/null
+++ b/frontend/src/pages/FavoritesPage.tsx
@@ -0,0 +1,259 @@
+import {
+ useFavorites,
+ useRemoveFavorite,
+ type Favorite,
+ useReorderFavorites,
+} from "@/api/favorites";
+import { useBeachDetails } from "@/api/useBeachDetails";
+import {
+ getQualityCategory,
+ classificationLabelKey,
+ qualityToneClass,
+} from "@/utils/quality";
+import { useTranslation } from "react-i18next";
+import { Link } from "react-router";
+import { useState, useMemo, useEffect } from "react";
+import {
+ DndContext,
+ closestCenter,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ DragEndEvent,
+} from "@dnd-kit/core";
+import {
+ SortableContext,
+ arrayMove,
+ sortableKeyboardCoordinates,
+ verticalListSortingStrategy,
+} from "@dnd-kit/sortable";
+import SortableFavorite from "@/components/SortableFavorite";
+
+const SORT_KEY = "favoritesSort"; // 'custom' | 'name' | 'municipality'
+
+export default function FavoritesPage() {
+ const { t } = useTranslation();
+ const { data: favorites, isLoading, isError, error } = useFavorites();
+ const ids = favorites?.map((f) => f.beachId);
+ const details = useBeachDetails(ids);
+ const rmFav = useRemoveFavorite();
+ const reorderFavs = useReorderFavorites();
+
+ // --- Sort mode with persistence
+ const [sortBy, setSortBy] = useState<"custom" | "name" | "municipality">(
+ () =>
+ (localStorage.getItem(SORT_KEY) as "custom" | "name" | "municipality") ??
+ "custom"
+ );
+ useEffect(() => {
+ localStorage.setItem(SORT_KEY, sortBy);
+ }, [sortBy]);
+
+ // --- Custom order state (array of beachIds), backed by the server response.
+ const [order, setOrder] = useState([]);
+ useEffect(() => {
+ if (!favorites) return;
+ setOrder(favorites.map((f) => f.beachId));
+ }, [favorites]);
+
+ // --- Enriched items map
+ const enriched = useMemo(() => {
+ const map = new Map<
+ string,
+ {
+ fav: Favorite;
+ name: string;
+ muni: string;
+ classificationText: string;
+ classificationClass: string;
+ }
+ >();
+
+ if (!favorites) return map;
+
+ for (const f of favorites) {
+ const info = details.byId.get(f.beachId);
+ const category = getQualityCategory(
+ info?.classification ?? info?.classificationText
+ );
+
+ map.set(f.beachId, {
+ fav: f,
+ name: info?.locationName ?? f.beachId,
+ muni: info?.locationArea ?? "",
+ classificationText: t(classificationLabelKey(category)),
+ classificationClass: qualityToneClass(category),
+ });
+ }
+ return map;
+ }, [favorites, details.byId, t]);
+
+ // --- Items to render (IDs in display order)
+ const displayIds = useMemo(() => {
+ if (!favorites) return [];
+ const favIds = favorites.map((f) => f.beachId);
+
+ if (sortBy === "custom") {
+ const ordered = order.length ? order : favIds;
+ return ordered.filter((id) => favIds.includes(id));
+ }
+
+ const list = favIds.slice();
+ const collator = new Intl.Collator(undefined, { sensitivity: "base" });
+ list.sort((a, b) => {
+ const A = enriched.get(a);
+ const B = enriched.get(b);
+ if (!A || !B) return 0;
+ if (sortBy === "name") return collator.compare(A.name, B.name);
+ return collator.compare(A.muni, B.muni);
+ });
+ return list;
+ }, [favorites, sortBy, order, enriched]);
+
+ // --- DnD sensors
+ const sensors = useSensors(
+ useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
+
+ function onDragEnd(event: DragEndEvent) {
+ if (sortBy !== "custom") return;
+ const { active, over } = event;
+ if (!over || active.id === over.id) return;
+
+ const oldIndex = order.indexOf(String(active.id));
+ const newIndex = order.indexOf(String(over.id));
+ if (oldIndex === -1 || newIndex === -1) return;
+
+ const next = arrayMove(order, oldIndex, newIndex);
+ setOrder(next);
+
+ // Call backend to persist
+ reorderFavs.mutate(next, {
+ onError: () => {
+ // Rollback on error
+ setOrder(order);
+ },
+ });
+ }
+
+ // --- Loading state
+ if (isLoading) {
+ return (
+
+ {t("favorites.title")}
+ {t("loadingBeaches")}
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
+
+ );
+ }
+
+ // --- Error state
+ if (isError) {
+ return (
+
+ {t("favorites.title")}
+
+ {(error as Error)?.message ?? t("loadError")}
+
+
+ );
+ }
+
+ // --- Normal render
+ return (
+
+
+
{t("favorites.title")}
+
+
+ {t("favorites.sortBy")}{" "}
+
+ setSortBy(e.target.value as "custom" | "name" | "municipality")
+ }
+ className="ml-1 border rounded px-2 py-1 text-sm"
+ aria-label={t("favorites.sortBy")}
+ >
+ {t("favorites.sortOptions.custom")}
+ {t("favorites.sortOptions.name")}
+ {t("favorites.sortOptions.municipality")}
+
+
+
+ {t("favorites.browseAll")}
+
+
+
+
+ {favorites && favorites.length === 0 && (
+
+
{t("favorites.empty")}
+
+ {t("favorites.addFavorites")}
+
+
+ {t("beachesList.modeDefault")}
+
+
+ )}
+
+ {/* Screen reader announcement for favorites count */}
+
+ {favorites && favorites.length > 0
+ ? `${favorites.length} ${t("favorites.title")}`
+ : t("favorites.empty")}
+
+
+
+
+
+ {displayIds.map((id) => {
+ const item = enriched.get(id);
+ if (!item) return null;
+ const { fav, name, muni, classificationText } = item;
+ const klass = item.classificationClass;
+
+ return (
+
+ rmFav
+ .mutateAsync({ id: fav._id, beachId: fav.beachId })
+ .then(() => {
+ setOrder((prev) => prev.filter((x) => x !== id));
+ })
+ }
+ />
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
new file mode 100644
index 0000000000..d4cbe35199
--- /dev/null
+++ b/frontend/src/pages/HomePage.tsx
@@ -0,0 +1,4 @@
+import BeachesList from "../components/BeachesList";
+export default function HomePage() {
+ return ;
+}
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
new file mode 100644
index 0000000000..808a04b950
--- /dev/null
+++ b/frontend/src/pages/LoginPage.tsx
@@ -0,0 +1,125 @@
+import { useMemo } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useTranslation } from "react-i18next";
+import { useNavigate, useLocation, Link } from "react-router";
+import toast from "react-hot-toast";
+import { useAuth } from "@/store/auth";
+import { apiErrorMessage, apiFetch } from "@/api/client";
+
+type FormData = {
+ email: string;
+ password: string;
+};
+
+export default function LoginPage() {
+ const { t } = useTranslation();
+ const refresh = useAuth((s) => s.refresh);
+ const navigate = useNavigate();
+ const location = useLocation();
+ const redirectState = location.state as
+ | { from?: { pathname?: string }; favoriteBeachId?: string }
+ | null;
+ const from = redirectState?.from?.pathname || "/";
+ const favoriteBeachId = redirectState?.favoriteBeachId;
+
+ const schema = useMemo(() => z.object({
+ email: z.string().email(t("auth.invalidEmail")),
+ password: z.string().min(8, t("auth.passwordTooShort")),
+ }), [t]);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ setError,
+ } = useForm({
+ resolver: zodResolver(schema),
+ });
+
+ async function onSubmit(values: FormData) {
+ try {
+ // The server sets the httpOnly session cookie; we then hydrate auth
+ // state from /auth/me. No token is handled in JS anymore.
+ await apiFetch("/auth/login", {
+ method: "POST",
+ body: JSON.stringify(values),
+ });
+ await refresh();
+ if (favoriteBeachId) {
+ try {
+ await apiFetch("/favorites", {
+ method: "POST",
+ body: JSON.stringify({ beachId: favoriteBeachId }),
+ });
+ toast.success(t("favorites.added"));
+ } catch (favoriteErr: any) {
+ if (favoriteErr?.message !== "AlreadyFavorited") {
+ toast.error(apiErrorMessage(favoriteErr, t, t("favorites.error")));
+ }
+ }
+ }
+ toast.success(t("auth.signInSuccess"));
+ navigate(from, { replace: true });
+ } catch (err: any) {
+ const errorMsg = apiErrorMessage(err, t, t("auth.signInFailed"));
+ setError("root", { message: errorMsg });
+ toast.error(errorMsg);
+ }
+ }
+
+ return (
+
+
+
{t("auth.signInTitle")}
+
+
+
+ {t("auth.noAccount")}{" "}
+
+ {t("auth.registerHere")}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/NotFoundPage.tsx b/frontend/src/pages/NotFoundPage.tsx
new file mode 100644
index 0000000000..c127a9bcc0
--- /dev/null
+++ b/frontend/src/pages/NotFoundPage.tsx
@@ -0,0 +1,21 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import { ArrowLeft } from "@phosphor-icons/react";
+
+export default function NotFoundPage() {
+ const { t } = useTranslation();
+ return (
+
+
+
{t("pages.notFound.title")}
+
{t("pages.notFound.desc")}
+
+
+
+ {t("pages.backToMap")}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/PrivacyPage.tsx b/frontend/src/pages/PrivacyPage.tsx
new file mode 100644
index 0000000000..2e09ff9509
--- /dev/null
+++ b/frontend/src/pages/PrivacyPage.tsx
@@ -0,0 +1,55 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import { ArrowLeft } from "@phosphor-icons/react";
+
+export default function PrivacyPage() {
+ const { t } = useTranslation();
+ return (
+
+ {t("pages.privacy.title")}
+
+
+ {t("pages.privacy.intro")}
+
+
+ {(
+ [
+ "dataWeCollect",
+ "location",
+ "howWeUse",
+ "sharing",
+ "storage",
+ "retention",
+ "rights",
+ ] as const
+ ).map((section) => (
+
+
+ {t(`pages.privacy.${section}`)}
+
+
+ {t(`pages.privacy.${section}Desc`)}
+
+
+ ))}
+
+
+ {t("pages.privacy.contact")}
+
+ {t("pages.privacy.contactDesc")}{" "}
+
+ {t("pages.privacy.contactUs")}
+
+ .
+
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx
new file mode 100644
index 0000000000..a253298c1d
--- /dev/null
+++ b/frontend/src/pages/ProfilePage.tsx
@@ -0,0 +1,79 @@
+import { Link, useNavigate } from "react-router";
+import { useTranslation } from "react-i18next";
+import { useAuth } from "@/store/auth";
+import { useQueryClient } from "@tanstack/react-query";
+import { ArrowLeft, Heart, SignOut, UserCircle } from "@phosphor-icons/react";
+
+export default function ProfilePage() {
+ const { t } = useTranslation();
+ const logout = useAuth((s) => s.logout);
+ const navigate = useNavigate();
+ const queryClient = useQueryClient();
+
+ const handleLogout = async () => {
+ await logout();
+ queryClient.clear();
+ navigate("/");
+ };
+
+ return (
+
+
+
+
+ {t("nav.account")}
+
+
{t("pages.profile.title")}
+
+
+
+
+ {t("pages.profile.accountInfo")}
+
+
+ {t("pages.profile.accountInfoDesc")}
+
+
+
+
+ {t("pages.profile.favorites")}
+
+ {t("pages.profile.favoritesDesc")}
+
+
+
+ {t("pages.profile.viewFavorites")}
+
+
+
+
+
+ {t("pages.profile.signOut")}
+
+
+ {t("pages.profile.signOutDesc")}
+
+
+
+ {t("pages.profile.signOut")}
+
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx
new file mode 100644
index 0000000000..952aad97e0
--- /dev/null
+++ b/frontend/src/pages/RegisterPage.tsx
@@ -0,0 +1,126 @@
+import { useMemo } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useTranslation } from "react-i18next";
+import { useNavigate, Link } from "react-router";
+import toast from "react-hot-toast";
+import { apiErrorMessage, apiFetch } from "@/api/client";
+
+type FormData = {
+ email: string;
+ password: string;
+ confirmPassword: string;
+};
+
+export default function RegisterPage() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ const schema = useMemo(() => z
+ .object({
+ email: z.string().email(t("auth.invalidEmail")),
+ password: z.string().min(8, t("auth.passwordTooShort")),
+ confirmPassword: z.string(),
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: t("auth.passwordsMustMatch"),
+ path: ["confirmPassword"],
+ }), [t]);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ setError,
+ } = useForm({
+ resolver: zodResolver(schema),
+ });
+
+ async function onSubmit(values: FormData) {
+ try {
+ await apiFetch("/auth/register", {
+ method: "POST",
+ body: JSON.stringify({
+ email: values.email,
+ password: values.password,
+ }),
+ });
+ toast.success(t("auth.registerSuccess"));
+ navigate("/login", { replace: true });
+ } catch (err: any) {
+ const errorMsg = apiErrorMessage(err, t, t("auth.registerFailed"));
+ setError("root", { message: errorMsg });
+ toast.error(errorMsg);
+ }
+ }
+
+ return (
+
+
+
{t("auth.registerTitle")}
+
+
+
+ {t("auth.alreadyHaveAccount")}{" "}
+
+ {t("auth.signInHere")}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx
new file mode 100644
index 0000000000..9738d0a1bf
--- /dev/null
+++ b/frontend/src/pages/SettingsPage.tsx
@@ -0,0 +1,144 @@
+import { useState } from "react";
+import { Link, useNavigate } from "react-router";
+import { useTranslation } from "react-i18next";
+import { useQueryClient } from "@tanstack/react-query";
+import toast from "react-hot-toast";
+import LanguageSwitcher from "@/components/LanguageSwitcher";
+import { useAuth } from "@/store/auth";
+import { deleteAccount } from "@/api/account";
+import { ArrowLeft, Moon, Sun, Translate, Trash } from "@phosphor-icons/react";
+import { useToggleDarkMode } from "@/hooks/useToggleDarkMode";
+
+export default function SettingsPage() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const clearLocal = useAuth((s) => s.clearLocal);
+ const queryClient = useQueryClient();
+ const { isDark, setIsDark } = useToggleDarkMode();
+
+ const [confirming, setConfirming] = useState(false);
+ const [deleting, setDeleting] = useState(false);
+
+ async function handleDelete() {
+ setDeleting(true);
+ try {
+ await deleteAccount();
+ clearLocal();
+ queryClient.clear();
+ toast.success(t("pages.settings.deleteSuccess"));
+ navigate("/", { replace: true });
+ } catch {
+ toast.error(t("pages.settings.deleteError"));
+ setDeleting(false);
+ setConfirming(false);
+ }
+ }
+
+ return (
+
+
+
+ {isDark ? (
+
+ ) : (
+
+ )}
+ {t("pages.settings.appearance")}
+
+
{t("pages.settings.title")}
+
+
+
+
+
+ {t("pages.settings.language")}
+
+
+ {t("pages.settings.languageDescription")}
+
+
+
+
+
+
+ {isDark ? (
+
+ ) : (
+
+ )}
+ {t("pages.settings.theme")}
+
+
+ {t("pages.settings.themeDesc")}
+
+ setIsDark(!isDark)}
+ >
+ {isDark ? (
+
+ ) : (
+
+ )}
+ {isDark ? t("pages.settings.useLight") : t("pages.settings.useDark")}
+
+
+
+ {/* Account deletion (GDPR right to erasure) */}
+
+
+ {t("pages.settings.account")}
+
+ {t("pages.settings.deleteAccountDesc")}
+
+ {!confirming ? (
+ setConfirming(true)}
+ >
+
+ {t("pages.settings.deleteAccount")}
+
+ ) : (
+
+
{t("pages.settings.deleteConfirm")}
+
+
+ {deleting
+ ? t("pages.settings.deleting")
+ : t("pages.settings.deleteConfirmYes")}
+
+ setConfirming(false)}
+ disabled={deleting}
+ >
+ {t("pages.settings.cancel")}
+
+
+
+ )}
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/TermsPage.tsx b/frontend/src/pages/TermsPage.tsx
new file mode 100644
index 0000000000..8065110b54
--- /dev/null
+++ b/frontend/src/pages/TermsPage.tsx
@@ -0,0 +1,77 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import { ArrowLeft } from "@phosphor-icons/react";
+
+export default function TermsPage() {
+ const { t } = useTranslation();
+ return (
+
+ {t("pages.terms.title")}
+
+
+ {t("pages.terms.acceptance")}
+
+ {t("pages.terms.acceptanceDesc")}
+
+
+
+
+ {t("pages.terms.service")}
+
+ {t("pages.terms.serviceDesc")}
+
+
+
+
+ {t("pages.terms.disclaimer")}
+
+ {t("pages.terms.disclaimerDesc")}
+
+
+ {t("pages.terms.disclaimerDesc2")}
+
+
+
+
+ {t("pages.terms.accounts")}
+
+ {t("pages.terms.accountsDesc")}
+
+
+
+
+ {t("pages.terms.ip")}
+
+ {t("pages.terms.ipDesc")}
+
+
+
+
+ {t("pages.terms.changes")}
+
+ {t("pages.terms.changesDesc")}
+
+
+
+
+ {t("pages.terms.contact")}
+
+ {t("pages.terms.contactDesc")}{" "}
+
+ {t("pages.terms.contactUs")}
+ .
+
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/WhatIsEUBeachPage.tsx b/frontend/src/pages/WhatIsEUBeachPage.tsx
new file mode 100644
index 0000000000..7e05ad8481
--- /dev/null
+++ b/frontend/src/pages/WhatIsEUBeachPage.tsx
@@ -0,0 +1,93 @@
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import { ArrowLeft } from "@phosphor-icons/react";
+
+export default function WhatIsEUBeachPage() {
+ const { t } = useTranslation();
+ return (
+
+ {t("pages.whatIsEUBeach.title")}
+
+
+ {t("pages.whatIsEUBeach.directive")}
+
+ {t("pages.whatIsEUBeach.directiveDesc")}
+
+
+ {t("pages.whatIsEUBeach.directiveDesc2")}
+
+
+
+
+ {t("pages.whatIsEUBeach.classifications")}
+
+
+
+
+ {t("classification.excellent")}
+
+
+ {t("pages.whatIsEUBeach.excellentDesc")}
+
+
+
+
+
+ {t("classification.good")}
+
+
+ {t("pages.whatIsEUBeach.goodDesc")}
+
+
+
+
+
+ {t("classification.sufficient")}
+
+
+ {t("pages.whatIsEUBeach.sufficientDesc")}
+
+
+
+
+
+ {t("classification.poor")}
+
+
+ {t("pages.whatIsEUBeach.poorDesc")}
+
+
+
+
+
+ {t("classification.unknown")}
+
+
+ {t("pages.whatIsEUBeach.unknownDesc")}
+
+
+
+
+
+
+ {t("pages.whatIsEUBeach.monitoring")}
+
+ {t("pages.whatIsEUBeach.monitoringDesc")}
+
+
+ {t("pages.whatIsEUBeach.monitoringDesc2")}
+
+
+
+
+
+
+ {t("pages.backToMap")}
+
+
+
+ );
+}
diff --git a/frontend/src/routes/ProtectedRoute.tsx b/frontend/src/routes/ProtectedRoute.tsx
new file mode 100644
index 0000000000..634ef4d41c
--- /dev/null
+++ b/frontend/src/routes/ProtectedRoute.tsx
@@ -0,0 +1,29 @@
+import { Navigate, Outlet, useLocation } from "react-router";
+import { useTranslation } from "react-i18next";
+import { useAuth } from "@/store/auth";
+
+export default function ProtectedRoute() {
+ const location = useLocation();
+ const { t } = useTranslation();
+ const status = useAuth((s) => s.status);
+
+ // Wait for the initial /auth/me check before deciding, so an authenticated
+ // user isn't briefly bounced to /login on a hard refresh.
+ if (status === "loading") {
+ return (
+
+ {t("loading")}
+
+ );
+ }
+
+ if (status === "unauthenticated") {
+ return ;
+ }
+
+ return ;
+}
diff --git a/frontend/src/store/auth.ts b/frontend/src/store/auth.ts
new file mode 100644
index 0000000000..722d4e82f0
--- /dev/null
+++ b/frontend/src/store/auth.ts
@@ -0,0 +1,44 @@
+import { create } from "zustand";
+import { apiFetch } from "@/api/client";
+
+export type AuthUser = { sub: string; email: string };
+export type AuthStatus = "loading" | "authenticated" | "unauthenticated";
+
+// Auth is now driven by an httpOnly session cookie the JS can't read, so we no
+// longer hold a token. "Am I logged in?" is answered by asking the backend
+// (GET /api/auth/me), and reflected here as `status`.
+type AuthState = {
+ status: AuthStatus;
+ user: AuthUser | null;
+ /** Ask the backend whether the session cookie is valid; hydrate status/user. */
+ refresh: () => Promise;
+ /** Mark authenticated after a successful login (server already set the cookie). */
+ setAuthenticated: (user: AuthUser) => void;
+ /** Log out: clear the server cookie, then local state. */
+ logout: () => Promise;
+ /** Clear local state only β e.g. after account deletion already cleared the cookie. */
+ clearLocal: () => void;
+};
+
+export const useAuth = create((set) => ({
+ status: "loading",
+ user: null,
+ refresh: async () => {
+ try {
+ const { user } = await apiFetch<{ user: AuthUser }>("/auth/me");
+ set({ status: "authenticated", user });
+ } catch {
+ set({ status: "unauthenticated", user: null });
+ }
+ },
+ setAuthenticated: (user) => set({ status: "authenticated", user }),
+ logout: async () => {
+ try {
+ await apiFetch("/auth/logout", { method: "POST" });
+ } catch {
+ // Clear local state regardless of the network outcome.
+ }
+ set({ status: "unauthenticated", user: null });
+ },
+ clearLocal: () => set({ status: "unauthenticated", user: null }),
+}));
diff --git a/frontend/src/store/ui.ts b/frontend/src/store/ui.ts
new file mode 100644
index 0000000000..7710dd28a2
--- /dev/null
+++ b/frontend/src/store/ui.ts
@@ -0,0 +1,19 @@
+import { create } from "zustand";
+
+type UIState = {
+ search: string;
+ homeResetVersion: number;
+ setSearch: (v: string) => void;
+ resetHomeView: () => void;
+};
+
+export const useUI = create((set) => ({
+ search: "",
+ homeResetVersion: 0,
+ setSearch: (v) => set({ search: v }),
+ resetHomeView: () =>
+ set((state) => ({
+ search: "",
+ homeResetVersion: state.homeResetVersion + 1,
+ })),
+}));
diff --git a/frontend/src/types/beaches.ts b/frontend/src/types/beaches.ts
new file mode 100644
index 0000000000..3e15efe226
--- /dev/null
+++ b/frontend/src/types/beaches.ts
@@ -0,0 +1,100 @@
+// GeoJSON + domain types for beaches
+
+export type LngLat = [number, number]; // GeoJSON order: [lon, lat]
+
+// --- GeoJSON (list) ---
+export interface BeachFeatureProperties {
+ NUTSKOD: string; // stable beach ID
+ NAMN: string; // name
+ KMN_NAMN?: string; // municipality (optional)
+}
+
+export interface BeachFeature {
+ type: "Feature";
+ id?: string;
+ geometry?: { type: "Point"; coordinates: LngLat };
+ geometry_name?: string;
+ properties?: BeachFeatureProperties;
+}
+
+export interface BeachFeatureCollection {
+ type: "FeatureCollection";
+ features: BeachFeature[];
+}
+
+// --- Simplified list item for UI ---
+export interface BeachSummary {
+ id: string; // NUTSKOD
+ name: string; // NAMN
+ municipality: string; // KMN_NAMN (empty string if missing)
+ lat: number;
+ lon: number; // canonical longitude
+ lng?: number; // temporary alias (same as lon) to avoid breaking older code
+}
+
+/**
+ * Map a GeoJSON feature β BeachSummary.
+ * Returns null if coordinates are missing/invalid.
+ */
+export function featureToSummary(f: BeachFeature): BeachSummary | null {
+ const props = f.properties;
+ const coords = f.geometry?.coordinates;
+ if (!props || !coords) return null;
+
+ const [lon, lat] = coords;
+ if (typeof lat !== "number" || typeof lon !== "number") return null;
+
+ return {
+ id: props.NUTSKOD ?? f.id ?? "",
+ name: props.NAMN ?? "OkΓ€nd",
+ municipality: props.KMN_NAMN ?? "",
+ lat,
+ lon,
+ lng: lon, // alias for any code still using `lng`
+ };
+}
+
+// --- Detail shape (plain JSON from /detail/:id) ---
+export interface BeachQualityRating {
+ qualityRating: number; // 1..4
+ qualityRatingText: string; // "UtmΓ€rkt kvalitet" | "Bra kvalitet" | ...
+ ratingYear: number;
+}
+
+export interface BeachDissuasion {
+ description?: string; // free text, pre-localized (Swedish) by HaV
+ dissuasionTypeText?: string; // e.g. "OtjΓ€nligt badvattenprov"
+ startdate?: number; // epoch ms
+ type?: number; // HaV code: 1 = poor sample, 99 = whole-season advisory
+}
+
+export interface BeachDetail {
+ algalText?: string;
+ algalValue?: number;
+ bathInformation?: string;
+ classification?: number; // 1..4 (current year)
+ classificationText?: string; // e.g. "Bra kvalitet"
+ classificationYear?: number;
+ contactMail?: string;
+ contactPhone?: string;
+ contactUrl?: string;
+ dissuasion?: BeachDissuasion[];
+ euMotive?: string;
+ euType?: boolean;
+ lat?: number;
+ lon?: number;
+ latitude?: number;
+ longitude?: number;
+ locationArea?: string;
+ locationName?: string;
+ nutsCode: string;
+ qualityRating?: BeachQualityRating[];
+
+ // β¬οΈ Likely sample-date candidates weβll probe for:
+ latestSampleDate?: string;
+ sampleDate?: string;
+ lastSampleDate?: string;
+ // sometimes APIs nest:
+ samples?: Array<{ date?: string; [k: string]: unknown }>;
+ lastSample?: { date?: string; [k: string]: unknown };
+}
diff --git a/frontend/src/types/weather.ts b/frontend/src/types/weather.ts
new file mode 100644
index 0000000000..41f4c9d343
--- /dev/null
+++ b/frontend/src/types/weather.ts
@@ -0,0 +1,50 @@
+export interface DailyForecast {
+ /** ISO date (YYYY-MM-DD) in the beach's local timezone */
+ date: string;
+ weatherCode: number;
+ tempMax: number;
+ tempMin: number;
+ uvMax: number;
+ /** Max probability of precipitation for the day, 0β100 */
+ precipProbability: number | null;
+ /** Total precipitation for the day in mm (drives the swim-safety nowcast) */
+ precipSum: number | null;
+ /** Max 10 m wind speed for the day, km/h (calm favours blooms; wind breaks them up) */
+ windMax: number | null;
+ /** Max significant wave height for the day, metres. Null inland β the marine
+ * model only covers sea points, and most Swedish badplatser are lakes. */
+ waveMax: number | null;
+}
+
+export interface WeatherData {
+ temperature: number;
+ feelsLike: number;
+ uvIndex: number;
+ weatherCode: number;
+ waterTemperature: number | null;
+ fetchedAt: string;
+ /** Today + next days (planning ahead, not just "right now") */
+ forecast: DailyForecast[];
+ /**
+ * Total rainfall (mm) over the days immediately *before* today. Recent runoff
+ * is the strongest real-world driver of short-term bacterial contamination,
+ * so the swim-safety nowcast weighs it against the (often weeks-old) official
+ * sample. Null when Open-Meteo omits past-day precipitation.
+ */
+ recentRainMm: number | null;
+}
+
+export interface SunTimes {
+ sunrise: Date;
+ sunset: Date;
+ solarNoon: Date;
+ dayLengthSeconds: number;
+ goldenHourMorningEnd: Date;
+ goldenHourEveningStart: Date;
+ civilTwilightBegin: Date;
+ civilTwilightEnd: Date;
+ nauticalTwilightBegin: Date;
+ nauticalTwilightEnd: Date;
+ astronomicalTwilightBegin: Date;
+ astronomicalTwilightEnd: Date;
+}
diff --git a/frontend/src/utils/algal.ts b/frontend/src/utils/algal.ts
new file mode 100644
index 0000000000..28b85453aa
--- /dev/null
+++ b/frontend/src/utils/algal.ts
@@ -0,0 +1,72 @@
+/**
+ * Translates Swedish algal bloom status to translation key
+ * @param swedishText - The Swedish algal status from the API
+ * @returns Translation key for i18n
+ */
+export function getAlgalStatusKey(swedishText: string | undefined): string | null {
+ if (!swedishText) return null;
+
+ const normalized = swedishText.toLowerCase().trim();
+
+ // Map Swedish text to translation keys
+ const mapping: Record = {
+ 'ingen blomning': 'algalStatus.ingenBlomning',
+ 'blomning': 'algalStatus.blomning',
+ 'kraftig blomning': 'algalStatus.kraftigBlomning',
+ 'mΓ₯ttlig blomning': 'algalStatus.mattligBlomning',
+ 'mattlig blomning': 'algalStatus.mattligBlomning',
+ 'lindrig blomning': 'algalStatus.lindrigBlomning',
+ };
+
+ return mapping[normalized] || null;
+}
+
+/**
+ * Maps an algal-status translation key to a swim-safety verdict.
+ *
+ * Cyanobacteria blooms can be harmful even at low intensity (especially for
+ * children and pets), so anything beyond "no bloom" advises caution or worse.
+ * Returns null when the status is unknown/unmeasured β we never imply "safe"
+ * without data.
+ *
+ * @returns `{ key, tone }` where `key` is an i18n key under `algalSafety` and
+ * `tone` selects the colour (kpi-good / kpi-sufficient / kpi-poor).
+ */
+export function getAlgalSafety(
+ algalKey: string | null
+): { key: string; tone: "safe" | "caution" | "avoid" } | null {
+ switch (algalKey) {
+ case "algalStatus.ingenBlomning":
+ return { key: "algalSafety.safe", tone: "safe" };
+ case "algalStatus.lindrigBlomning":
+ case "algalStatus.mattligBlomning":
+ return { key: "algalSafety.caution", tone: "caution" };
+ case "algalStatus.blomning":
+ case "algalStatus.kraftigBlomning":
+ return { key: "algalSafety.avoid", tone: "avoid" };
+ default:
+ return null;
+ }
+}
+
+/**
+ * Translates Swedish EU motive to translation key
+ * @param swedishText - The Swedish EU motive from the API
+ * @returns Translation key for i18n
+ */
+export function getEuMotiveKey(swedishText: string | undefined): string | null {
+ if (!swedishText) return null;
+
+ const normalized = swedishText.toLowerCase().trim();
+
+ // Map Swedish text to translation keys
+ const mapping: Record = {
+ 'stor badbelastning': 'euMotive.storBadbelastning',
+ 'badbelastning': 'euMotive.badbelastning',
+ 'tradition': 'euMotive.tradition',
+ 'turistattraktion': 'euMotive.turistattraktion',
+ };
+
+ return mapping[normalized] || null;
+}
+
diff --git a/frontend/src/utils/format.ts b/frontend/src/utils/format.ts
new file mode 100644
index 0000000000..16ae245146
--- /dev/null
+++ b/frontend/src/utils/format.ts
@@ -0,0 +1,34 @@
+/**
+ * Format a date in a human readable way.
+ * If input is invalid, return as-is.
+ * If no input, return em dash.
+ *
+ * @param input Date input (string, number, Date)
+ * @param style "long" (default) or "short" (YYYY-MM-DD)
+ * @returns Formatted date string
+ */
+
+export function formatDate(
+ input?: string | number | Date,
+ style: "long" | "short" = "long"
+): string {
+ if (!input) return "β";
+ const d = new Date(input);
+ if (Number.isNaN(d.getTime())) return String(input);
+
+ if (style === "short") {
+ return d.toLocaleDateString("sv-SE", {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ });
+ }
+
+ const locale =
+ (typeof navigator !== "undefined" && navigator.language) || "sv-SE";
+ return d.toLocaleDateString(locale, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ });
+}
diff --git a/frontend/src/utils/geo.ts b/frontend/src/utils/geo.ts
new file mode 100644
index 0000000000..53c845eea7
--- /dev/null
+++ b/frontend/src/utils/geo.ts
@@ -0,0 +1,21 @@
+export function distanceKm(
+ a: { lat: number; lon: number },
+ b: { lat: number; lon: number }
+): number {
+ const R = 6371; // km
+ const dLat = ((b.lat - a.lat) * Math.PI) / 180;
+ const dLon = ((b.lon - a.lon) * Math.PI) / 180;
+ const lat1 = (a.lat * Math.PI) / 180;
+ const lat2 = (b.lat * Math.PI) / 180;
+
+ const s =
+ Math.sin(dLat / 2) ** 2 +
+ Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
+
+ return 2 * R * Math.asin(Math.min(1, Math.sqrt(s)));
+}
+
+export function formatKm(km: number): string {
+ if (km < 1) return `${Math.round(km * 1000)} m`;
+ return `${km.toFixed(km < 10 ? 1 : 0)} km`;
+}
diff --git a/frontend/src/utils/logger.ts b/frontend/src/utils/logger.ts
new file mode 100644
index 0000000000..c6f1af300f
--- /dev/null
+++ b/frontend/src/utils/logger.ts
@@ -0,0 +1,12 @@
+// Only ever log in a local dev server. import.meta.env.DEV is false for any
+// `vite build` (Netlify Production, Deploy Previews, Branch deploys), so API
+// debug logging β which includes request bodies such as login email/password β
+// can never run in a deployed build, regardless of how VITE_DEBUG_API is set
+// in the host environment. The flag only takes effect during local `vite dev`.
+const DEBUG = import.meta.env.DEV && import.meta.env.VITE_DEBUG_API === "true";
+
+export function logApi(...args: any[]) {
+ if (DEBUG) {
+ console.log("[API]", ...args);
+ }
+}
diff --git a/frontend/src/utils/quality.ts b/frontend/src/utils/quality.ts
new file mode 100644
index 0000000000..ced3960185
--- /dev/null
+++ b/frontend/src/utils/quality.ts
@@ -0,0 +1,83 @@
+/**
+ * HaV bathing-water quality classification.
+ *
+ * HaV uses numeric classification codes:
+ * 1 = UtmΓ€rkt kvalitet / Excellent quality
+ * 2 = Bra kvalitet / Good quality
+ * 3 = TillfredsstΓ€llande kvalitet / Sufficient quality
+ * 4 = DΓ₯lig kvalitet / Poor quality
+ *
+ * ...but it also sends free Swedish text (`classificationText`) and, for
+ * beaches not yet rated, phrases like "klassificering pΓ₯gΓ₯r" or
+ * "ej klassificerad". `getQualityCategory` is the single source of truth that
+ * normalizes any of those into one category. The label i18n key and the KPI
+ * color class are both derived from that category, so a beach's wording and
+ * its pill color can never disagree.
+ */
+
+export type QualityCategory =
+ | "excellent"
+ | "good"
+ | "sufficient"
+ | "poor"
+ | "inProgress"
+ | "notClassified"
+ | "unknown";
+
+/** Normalize a HaV numeric code or Swedish text into a single category. */
+export function getQualityCategory(input?: number | string): QualityCategory {
+ if (typeof input === "number") {
+ switch (input) {
+ case 1:
+ return "excellent";
+ case 2:
+ return "good";
+ case 3:
+ return "sufficient";
+ case 4:
+ return "poor";
+ default:
+ return "unknown";
+ }
+ }
+
+ if (typeof input === "string") {
+ const text = input.toLowerCase().trim();
+ if (text.includes("utmΓ€rkt") || text.includes("excellent"))
+ return "excellent";
+ if (text.includes("bra") || text.includes("good")) return "good";
+ if (text.includes("tillfreds") || text.includes("sufficient"))
+ return "sufficient";
+ if (text.includes("dΓ₯lig") || text.includes("poor")) return "poor";
+ if (text.includes("klassificering") && text.includes("pΓ₯gΓ₯r"))
+ return "inProgress";
+ if (text.includes("ej klassificerad") || text.includes("inte klassificerad"))
+ return "notClassified";
+ }
+
+ return "unknown";
+}
+
+/** i18n key for the category's label, e.g. "classification.poor". */
+export function classificationLabelKey(category: QualityCategory): string {
+ return `classification.${category}`;
+}
+
+/**
+ * KPI color class for the category. inProgress / notClassified / unknown all
+ * map to the neutral "kpi-unknown" tone.
+ */
+export function qualityToneClass(category: QualityCategory): string {
+ switch (category) {
+ case "excellent":
+ return "kpi-excellent";
+ case "good":
+ return "kpi-good";
+ case "sufficient":
+ return "kpi-sufficient";
+ case "poor":
+ return "kpi-poor";
+ default:
+ return "kpi-unknown";
+ }
+}
diff --git a/frontend/src/utils/swimForecast.test.ts b/frontend/src/utils/swimForecast.test.ts
new file mode 100644
index 0000000000..d35375a98e
--- /dev/null
+++ b/frontend/src/utils/swimForecast.test.ts
@@ -0,0 +1,322 @@
+import { describe, it, expect } from "vitest";
+import {
+ computeSwimForecast,
+ type SwimForecastInput,
+} from "./swimForecast";
+
+// Pin the clock so sample-age β confidence is deterministic.
+const NOW = new Date("2026-07-04T12:00:00Z");
+
+/** A clean baseline: excellent beach, dry, fresh sample, cool water. */
+function makeInput(over: Partial = {}): SwimForecastInput {
+ return {
+ classification: "excellent",
+ hasActiveAdvisory: false,
+ algalTone: "safe",
+ latestSampleDate: "2026-07-01", // 3 days old
+ recentRainMm: 0,
+ waterTemperature: 16,
+ days: [
+ { date: "2026-07-04", precipSum: 0 },
+ { date: "2026-07-05", precipSum: 0 },
+ { date: "2026-07-06", precipSum: 0 },
+ ],
+ now: NOW,
+ ...over,
+ };
+}
+
+describe("computeSwimForecast β classification baseline", () => {
+ it.each([
+ ["excellent", "good"],
+ ["good", "good"],
+ ["sufficient", "caution"],
+ ["poor", "avoid"],
+ ] as const)("maps %s classification to %s", (classification, level) => {
+ const f = computeSwimForecast(makeInput({ classification }));
+ expect(f?.overall).toBe(level);
+ });
+
+ it("returns null when there is no baseline and no live signal", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ classification: "notClassified",
+ algalTone: null,
+ recentRainMm: null,
+ hasActiveAdvisory: false,
+ })
+ );
+ expect(f).toBeNull();
+ });
+
+ it("always explains an avoid/caution set by classification alone", () => {
+ const f = computeSwimForecast(
+ makeInput({ classification: "poor", algalTone: null })
+ );
+ expect(f?.days[0].reasonKeys).toContain(
+ "swimForecast.reason.classification"
+ );
+ });
+});
+
+describe("computeSwimForecast β precedence", () => {
+ it("an active HaV advisory overrides everything to avoid", () => {
+ const f = computeSwimForecast(
+ makeInput({ classification: "excellent", hasActiveAdvisory: true })
+ );
+ expect(f?.overall).toBe("avoid");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.advisory");
+ });
+
+ it("the advisory applies to every day, not just today", () => {
+ const f = computeSwimForecast(makeInput({ hasActiveAdvisory: true }));
+ expect(f?.days.every((d) => d.level === "avoid")).toBe(true);
+ });
+
+ it("algal bloom = avoid downgrades a clean beach", () => {
+ const f = computeSwimForecast(makeInput({ algalTone: "avoid" }));
+ expect(f?.overall).toBe("avoid");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.algalAvoid");
+ });
+
+ it("mild algae = caution", () => {
+ const f = computeSwimForecast(makeInput({ algalTone: "caution" }));
+ expect(f?.overall).toBe("caution");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.algalCaution");
+ });
+});
+
+describe("computeSwimForecast β safety guardrails", () => {
+ it("rain alone NEVER produces an avoid (caps at caution)", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ recentRainMm: 100,
+ days: [{ date: "2026-07-04", precipSum: 100 }],
+ })
+ );
+ expect(f?.overall).toBe("caution");
+ });
+
+ it("never implies 'safe'/good without any data", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ classification: "inProgress",
+ algalTone: null,
+ recentRainMm: 2, // a live signal exists, so the card shows...
+ })
+ );
+ // ...but with no baseline and only trivial rain it stays unknown, not good.
+ expect(f?.overall).toBe("unknown");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.noData");
+ });
+
+ it("heavy rain downgrades a good day to caution", () => {
+ const f = computeSwimForecast(
+ makeInput({ days: [{ date: "2026-07-04", precipSum: 20 }] })
+ );
+ expect(f?.overall).toBe("caution");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.heavyRain");
+ });
+
+ it("light rain does NOT downgrade a clean day", () => {
+ const f = computeSwimForecast(
+ makeInput({ days: [{ date: "2026-07-04", precipSum: 3 }] })
+ );
+ expect(f?.overall).toBe("good");
+ });
+});
+
+describe("computeSwimForecast β rain thresholds (boundaries)", () => {
+ it("just under the light threshold (4.9mm) stays good", () => {
+ const f = computeSwimForecast(
+ makeInput({ recentRainMm: 0, days: [{ date: "2026-07-04", precipSum: 4.9 }] })
+ );
+ expect(f?.overall).toBe("good");
+ });
+
+ it("at the light threshold (5mm) nudges to caution", () => {
+ const f = computeSwimForecast(
+ makeInput({ recentRainMm: 0, days: [{ date: "2026-07-04", precipSum: 5 }] })
+ );
+ expect(f?.overall).toBe("caution");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.rain");
+ });
+
+ it("at the heavy threshold (15mm) reports heavy rain", () => {
+ const f = computeSwimForecast(
+ makeInput({ recentRainMm: 0, days: [{ date: "2026-07-04", precipSum: 15 }] })
+ );
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.heavyRain");
+ });
+});
+
+describe("computeSwimForecast β recent-rain carry-over", () => {
+ it("recent rainfall weighs on today but not future days", () => {
+ // 40mm recent * 0.5 weight = 20mm load today β caution; days 2+ are dry.
+ const f = computeSwimForecast(
+ makeInput({
+ recentRainMm: 40,
+ days: [
+ { date: "2026-07-04", precipSum: 0 },
+ { date: "2026-07-05", precipSum: 0 },
+ ],
+ })
+ );
+ expect(f?.days[0].level).toBe("caution");
+ expect(f?.days[1].level).toBe("good");
+ });
+
+ it("a day's rain spills partly into the next day", () => {
+ // Day 2 gets 20mm (caution); day 3 is dry but inherits 20*0.5=10mm β caution.
+ const f = computeSwimForecast(
+ makeInput({
+ days: [
+ { date: "2026-07-04", precipSum: 0 },
+ { date: "2026-07-05", precipSum: 20 },
+ { date: "2026-07-06", precipSum: 0 },
+ ],
+ })
+ );
+ expect(f?.days.map((d) => d.level)).toEqual(["good", "caution", "caution"]);
+ });
+});
+
+describe("computeSwimForecast β warm water", () => {
+ it("warm calm water flags bloom risk alongside mild algae", () => {
+ const f = computeSwimForecast(
+ makeInput({ algalTone: "caution", waterTemperature: 22 })
+ );
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.warmWater");
+ });
+
+ it("warm water alone never manufactures a downgrade", () => {
+ const f = computeSwimForecast(makeInput({ waterTemperature: 25 }));
+ expect(f?.overall).toBe("good");
+ });
+});
+
+describe("computeSwimForecast β waves", () => {
+ it("high waves downgrade a good day to caution", () => {
+ const f = computeSwimForecast(
+ makeInput({ days: [{ date: "2026-07-04", precipSum: 0, waveMax: 1.4 }] })
+ );
+ expect(f?.overall).toBe("caution");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.highWaves");
+ });
+
+ it("waves alone NEVER produce an avoid (caps at caution)", () => {
+ const f = computeSwimForecast(
+ makeInput({ days: [{ date: "2026-07-04", precipSum: 0, waveMax: 3.5 }] })
+ );
+ expect(f?.overall).toBe("caution");
+ });
+
+ it("calm water (0.4m) stays good", () => {
+ const f = computeSwimForecast(
+ makeInput({ days: [{ date: "2026-07-04", precipSum: 0, waveMax: 0.4 }] })
+ );
+ expect(f?.overall).toBe("good");
+ });
+
+ it("waves never upgrade an unknown verdict into a concrete one", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ classification: "inProgress",
+ algalTone: null,
+ recentRainMm: 2,
+ days: [{ date: "2026-07-04", precipSum: 0, waveMax: 2.0 }],
+ })
+ );
+ expect(f?.overall).toBe("unknown");
+ });
+
+ it("missing wave data (inland lake) changes nothing", () => {
+ const f = computeSwimForecast(
+ makeInput({ days: [{ date: "2026-07-04", precipSum: 0, waveMax: null }] })
+ );
+ expect(f?.overall).toBe("good");
+ });
+});
+
+describe("computeSwimForecast β wind vs blooms", () => {
+ it("warm + calm day keeps the bloom-risk note alongside mild algae", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ algalTone: "caution",
+ waterTemperature: 22,
+ days: [{ date: "2026-07-04", precipSum: 0, windMax: 10 }],
+ })
+ );
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.warmWater");
+ });
+
+ it("a windy day replaces the bloom-risk note with a dispersal note", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ algalTone: "caution",
+ waterTemperature: 22,
+ days: [{ date: "2026-07-04", precipSum: 0, windMax: 35 }],
+ })
+ );
+ expect(f?.days[0].reasonKeys).not.toContain("swimForecast.reason.warmWater");
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.windDisperses");
+ // Informational only: the algal caution level itself stays.
+ expect(f?.days[0].level).toBe("caution");
+ });
+
+ it("wind without any algal concern says nothing", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ waterTemperature: 22,
+ days: [{ date: "2026-07-04", precipSum: 0, windMax: 35 }],
+ })
+ );
+ expect(f?.overall).toBe("good");
+ expect(f?.days[0].reasonKeys).not.toContain(
+ "swimForecast.reason.windDisperses"
+ );
+ });
+
+ it("unknown wind falls back to the warm-water-only signal", () => {
+ const f = computeSwimForecast(
+ makeInput({ algalTone: "caution", waterTemperature: 22 })
+ );
+ expect(f?.days[0].reasonKeys).toContain("swimForecast.reason.warmWater");
+ });
+});
+
+describe("computeSwimForecast β sample-age confidence", () => {
+ it.each([
+ ["2026-07-01", "high"], // 3 days
+ ["2026-06-24", "medium"], // 10 days
+ ["2026-05-25", "low"], // 40 days
+ ] as const)("sample from %s β %s confidence", (latestSampleDate, confidence) => {
+ const f = computeSwimForecast(makeInput({ latestSampleDate }));
+ expect(f?.confidence).toBe(confidence);
+ });
+
+ it("no sample β low confidence, without changing the level", () => {
+ const f = computeSwimForecast(makeInput({ latestSampleDate: null }));
+ expect(f?.confidence).toBe("low");
+ expect(f?.overall).toBe("good"); // dry + excellent baseline still holds
+ expect(f?.confidenceKey).toBe("swimForecast.confidence.noSample");
+ });
+
+ it("tolerates an unparseable sample date", () => {
+ const f = computeSwimForecast(makeInput({ latestSampleDate: "not-a-date" }));
+ expect(f?.confidence).toBe("low");
+ });
+});
+
+describe("computeSwimForecast β null-safety", () => {
+ it("handles missing rain and water-temperature without throwing", () => {
+ const f = computeSwimForecast(
+ makeInput({
+ recentRainMm: null,
+ waterTemperature: null,
+ days: [{ date: "2026-07-04", precipSum: null }],
+ })
+ );
+ expect(f?.overall).toBe("good");
+ });
+});
diff --git a/frontend/src/utils/swimForecast.ts b/frontend/src/utils/swimForecast.ts
new file mode 100644
index 0000000000..dde6254f61
--- /dev/null
+++ b/frontend/src/utils/swimForecast.ts
@@ -0,0 +1,264 @@
+/**
+ * Swim-safety nowcast.
+ *
+ * Badlistan's official signals β the annual EU/HaV `classification` and the most
+ * recent lab `sample` β answer "is this beach *generally* clean?" but they are
+ * structurally stale: the classification is a multi-year verdict and a sample
+ * can be weeks old (HaV only samples ~JuneβAugust). Beachgoers actually ask a
+ * shorter-horizon question: "is it OK to swim *today / this weekend*?".
+ *
+ * This module blends the authoritative-but-slow signals with the fast,
+ * real-world drivers of short-term water quality:
+ *
+ * β’ Recent rainfall / runoff β the dominant cause of bacterial (E. coli)
+ * spikes at bathing waters, especially where storm drains discharge.
+ * β’ Forecast rainfall β same risk, projected onto each upcoming day.
+ * β’ Warm, calm sea water β favours cyanobacteria (algal) blooms; wind
+ * mixes the water column and breaks surface blooms up.
+ * β’ Wave height (coastal) β rough water is a bathing-safety hazard in
+ * its own right, independent of water quality.
+ * β’ Sample freshness β how much to trust the official baseline.
+ *
+ * It is deliberately a *heuristic advisory*, not a measurement. It never
+ * invents a "safe" verdict without data, it caps weather-only effects at
+ * "caution" (rain alone never fabricates an "avoid"), and an active HaV
+ * advisory always wins. The official HaV classification remains the source of
+ * truth shown elsewhere on the page.
+ */
+
+import type { QualityCategory } from "./quality";
+
+export type SwimLevel = "good" | "caution" | "avoid" | "unknown";
+export type SwimConfidence = "high" | "medium" | "low";
+
+/** Reason keys resolve to i18n under `swimForecast.reason.*`. */
+export interface SwimDayForecast {
+ /** ISO date (YYYY-MM-DD), local to the beach. */
+ date: string;
+ level: SwimLevel;
+ /** i18n keys explaining the verdict, most important first. */
+ reasonKeys: string[];
+}
+
+export interface SwimForecast {
+ /** Verdict for today (first day). */
+ overall: SwimLevel;
+ days: SwimDayForecast[];
+ confidence: SwimConfidence;
+ /** i18n key for a confidence-appropriate caveat line. */
+ confidenceKey: string;
+}
+
+export interface SwimForecastDayInput {
+ date: string;
+ /** Total forecast rainfall for the day, mm. */
+ precipSum: number | null;
+ /** Max 10 m wind speed for the day, km/h. Optional β null when unknown. */
+ windMax?: number | null;
+ /** Max significant wave height for the day, metres. Null inland. */
+ waveMax?: number | null;
+}
+
+export interface SwimForecastInput {
+ /** Annual EU/HaV classification category (the slow baseline). */
+ classification: QualityCategory;
+ /** An active HaV swimming advisory (`dissuasion`) β authoritative. */
+ hasActiveAdvisory: boolean;
+ /** Latest algal-bloom swim-safety tone, or null if unmeasured. */
+ algalTone: "safe" | "caution" | "avoid" | null;
+ /** ISO date of the most recent official sample, or null. */
+ latestSampleDate: string | null;
+ /** Rainfall (mm) over the days immediately before today. */
+ recentRainMm: number | null;
+ /** Current sea-surface temperature (Β°C), or null. */
+ waterTemperature: number | null;
+ /** Today-onward daily rainfall. */
+ days: SwimForecastDayInput[];
+ /** Injectable "now" for deterministic tests. Defaults to new Date(). */
+ now?: Date;
+}
+
+// Rainfall thresholds (mm). Below LIGHT is background noise; at/above HEAVY is
+// a meaningful runoff event that can wash contaminants into bathing water.
+const RAIN_LIGHT_MM = 5;
+const RAIN_HEAVY_MM = 15;
+// Recent runoff still matters today but decays; count only a fraction of it
+// toward "today" so a downpour three days ago isn't treated like one overnight.
+const RECENT_RAIN_WEIGHT = 0.5;
+// Sea temperature (Β°C) above which cyanobacteria blooms are favoured.
+const WARM_WATER_C = 18;
+// Wind (km/h, daily max) at/above which surface blooms get mixed and broken
+// up β a windy day defuses the "warm calm water" bloom signal.
+const DISPERSING_WIND_KMH = 29;
+// Significant wave height (m) at/above which bathing itself gets hazardous.
+// A pure weather signal: caps at "caution", never fabricates an "avoid".
+const WAVE_CAUTION_M = 1.0;
+
+const RANK: Record = {
+ unknown: -1,
+ good: 0,
+ caution: 1,
+ avoid: 2,
+};
+
+/** More-severe of two levels; `unknown` yields to any concrete level. */
+function worst(a: SwimLevel, b: SwimLevel): SwimLevel {
+ if (a === "unknown") return b;
+ if (b === "unknown") return a;
+ return RANK[a] >= RANK[b] ? a : b;
+}
+
+/** Map the annual classification to a baseline swim level. */
+function baselineLevel(c: QualityCategory): SwimLevel {
+ switch (c) {
+ case "excellent":
+ case "good":
+ return "good";
+ case "sufficient":
+ return "caution";
+ case "poor":
+ return "avoid";
+ default:
+ return "unknown"; // inProgress / notClassified / unknown
+ }
+}
+
+function daysBetween(a: Date, b: Date): number {
+ return Math.abs(a.getTime() - b.getTime()) / 86_400_000;
+}
+
+/** Sample-age β how much to trust the official baseline. */
+function assessConfidence(
+ latestSampleDate: string | null,
+ now: Date
+): { confidence: SwimConfidence; confidenceKey: string } {
+ if (!latestSampleDate) {
+ return { confidence: "low", confidenceKey: "swimForecast.confidence.noSample" };
+ }
+ const sampled = new Date(latestSampleDate);
+ if (Number.isNaN(sampled.getTime())) {
+ return { confidence: "low", confidenceKey: "swimForecast.confidence.noSample" };
+ }
+ const age = daysBetween(now, sampled);
+ if (age <= 7) {
+ return { confidence: "high", confidenceKey: "swimForecast.confidence.fresh" };
+ }
+ if (age <= 14) {
+ return { confidence: "medium", confidenceKey: "swimForecast.confidence.recent" };
+ }
+ return { confidence: "low", confidenceKey: "swimForecast.confidence.stale" };
+}
+
+/**
+ * Compute the swim-safety nowcast for today + upcoming days.
+ * Pure and deterministic (pass `now` to pin the clock).
+ */
+export function computeSwimForecast(input: SwimForecastInput): SwimForecast | null {
+ const now = input.now ?? new Date();
+
+ // Nothing to say if we have neither a baseline nor any live signal.
+ const base = baselineLevel(input.classification);
+ const hasAnySignal =
+ base !== "unknown" ||
+ input.hasActiveAdvisory ||
+ input.algalTone !== null ||
+ input.recentRainMm != null;
+ if (!hasAnySignal) return null;
+
+ const warmWater =
+ input.waterTemperature != null && input.waterTemperature >= WARM_WATER_C;
+
+ const days: SwimDayForecast[] = input.days.map((day, i) => {
+ const reasons: string[] = [];
+ let level = base;
+
+ // 1. Active HaV advisory β authoritative, overrides everything, all days.
+ if (input.hasActiveAdvisory) {
+ level = worst(level, "avoid");
+ reasons.push("swimForecast.reason.advisory");
+ }
+
+ // 2. Algal bloom status (applies while it's the current reading).
+ if (input.algalTone === "avoid") {
+ level = worst(level, "avoid");
+ reasons.push("swimForecast.reason.algalAvoid");
+ } else if (input.algalTone === "caution") {
+ level = worst(level, "caution");
+ reasons.push("swimForecast.reason.algalCaution");
+ }
+
+ // 3. Rainfall / runoff. Recent rain weighs on today; each day also carries
+ // its own forecast rain (plus a little spillover from the day before).
+ const todayRain = day.precipSum ?? 0;
+ const prevRain = i > 0 ? input.days[i - 1]?.precipSum ?? 0 : 0;
+ const recentContribution =
+ i === 0 ? (input.recentRainMm ?? 0) * RECENT_RAIN_WEIGHT : 0;
+ const rainLoad =
+ todayRain + recentContribution + prevRain * RECENT_RAIN_WEIGHT;
+
+ // Rain only downgrades a known baseline, and never worse than "caution".
+ if (level === "good" || level === "caution") {
+ if (rainLoad >= RAIN_HEAVY_MM) {
+ level = worst(level, "caution");
+ reasons.push("swimForecast.reason.heavyRain");
+ } else if (rainLoad >= RAIN_LIGHT_MM) {
+ // Nudge a pristine day to caution only when rain is non-trivial and
+ // the baseline isn't already excellent-clean-and-dry.
+ if (level === "good") {
+ level = "caution";
+ reasons.push("swimForecast.reason.rain");
+ }
+ }
+ }
+
+ // 4. Rough water. Waves are a bathing-safety hazard independent of water
+ // quality; like rain, they may nudge to caution but never to "avoid".
+ const waveMax = day.waveMax ?? null;
+ if (waveMax != null && waveMax >= WAVE_CAUTION_M && (level === "good" || level === "caution")) {
+ level = "caution";
+ reasons.push("swimForecast.reason.highWaves");
+ }
+
+ // 5. Warm water + wind, per day. Calm warm water favours blooms
+ // (informational nudge on borderline days); a windy day instead breaks
+ // surface blooms up, which is worth saying when algae is the concern.
+ // Unknown wind falls back to the warm-water-only signal.
+ const windMax = day.windMax ?? null;
+ const windy = windMax != null && windMax >= DISPERSING_WIND_KMH;
+ if (warmWater && !windy && (level === "caution" || input.algalTone === "caution")) {
+ reasons.push("swimForecast.reason.warmWater");
+ } else if (windy && input.algalTone === "caution") {
+ reasons.push("swimForecast.reason.windDisperses");
+ }
+
+ // 6. No usable signal for this beach at all.
+ if (level === "unknown" && reasons.length === 0) {
+ reasons.push("swimForecast.reason.noData");
+ }
+
+ // Fall back to explaining the official classification when it alone set the
+ // level, so an "avoid"/"caution" verdict is never left unexplained.
+ if ((level === "avoid" || level === "caution") && reasons.length === 0) {
+ reasons.push("swimForecast.reason.classification");
+ }
+
+ // Keep a positive note when nothing dragged a good day down.
+ if (level === "good" && reasons.length === 0) {
+ reasons.push("swimForecast.reason.clear");
+ }
+
+ return { date: day.date, level, reasonKeys: reasons };
+ });
+
+ const { confidence, confidenceKey } = assessConfidence(
+ input.latestSampleDate,
+ now
+ );
+
+ return {
+ overall: days[0]?.level ?? "unknown",
+ days,
+ confidence,
+ confidenceKey,
+ };
+}
diff --git a/frontend/src/utils/webgl.ts b/frontend/src/utils/webgl.ts
new file mode 100644
index 0000000000..4aec4443de
--- /dev/null
+++ b/frontend/src/utils/webgl.ts
@@ -0,0 +1,17 @@
+export function canUseWebGL() {
+ try {
+ const canvas = document.createElement("canvas");
+ const contextAttributes: WebGLContextAttributes = {
+ antialias: false,
+ failIfMajorPerformanceCaveat: false,
+ powerPreference: "high-performance",
+ };
+ const gl = canvas.getContext("webgl2", contextAttributes);
+
+ gl?.getExtension("WEBGL_lose_context")?.loseContext();
+
+ return Boolean(gl);
+ } catch {
+ return false;
+ }
+}
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000000..be3e2d2008
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1,16 @@
+///
+
+interface ImportMetaEnv {
+ readonly VITE_API_BASE: string;
+ readonly VITE_MAPTILER_KEY: string;
+}
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
+
+// SVGR React component types for `?react` imports
+declare module "*.svg?react" {
+ import * as React from "react";
+ const ReactComponent: React.FC>;
+ export default ReactComponent;
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000000..8c716a1077
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "lib": ["ES2022", "DOM"],
+ "types": ["vite/client"],
+ "baseUrl": "./src",
+ "paths": {
+ "@/*": ["*"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index 5a33944a9b..afae9a726d 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -1,7 +1,38 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import svgr from "vite-plugin-svgr";
+import path from "path";
-// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
-})
+ plugins: [react(), svgr()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+ publicDir: "public",
+ server: {
+ // Honor a tool-assigned port (Claude Code's autoPort passes $PORT);
+ // fall back to Vite's default otherwise.
+ port: Number(process.env.PORT) || 5173,
+ // Same-origin /api in dev (see .env.local) so the session cookie behaves
+ // like production topology A. Points at the local Express backend.
+ proxy: {
+ "/api": "http://localhost:3000",
+ },
+ },
+ build: {
+ rollupOptions: {
+ input: {
+ main: path.resolve(__dirname, "index.html"),
+ },
+ output: {
+ manualChunks: {
+ // maplibre-gl is large and only needed where the map renders; split
+ // it out of the main bundle so the initial JS payload is smaller.
+ maplibre: ["maplibre-gl"],
+ },
+ },
+ },
+ },
+});
diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts
new file mode 100644
index 0000000000..9540f97145
--- /dev/null
+++ b/frontend/vitest.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ // The units under test are pure functions β no DOM needed.
+ environment: "node",
+ include: ["src/**/*.test.ts"],
+ },
+});
diff --git a/package.json b/package.json
index 680d190772..12b2aa48b1 100644
--- a/package.json
+++ b/package.json
@@ -4,4 +4,4 @@
"scripts": {
"postinstall": "npm install --prefix backend"
}
-}
\ No newline at end of file
+}
diff --git a/scripts/build-beaches.mjs b/scripts/build-beaches.mjs
new file mode 100644
index 0000000000..c5d77f43a9
--- /dev/null
+++ b/scripts/build-beaches.mjs
@@ -0,0 +1,126 @@
+// Builds a slim, static snapshot of all EU bathing waters in Sweden.
+//
+// The beach LIST (locations + names) changes only a few times a year, so we
+// snapshot it at deploy time and serve it from the CDN instead of proxying
+// HaV on every first page load. This removes the serverless cold-start +
+// large-GeoJSON round trip from the critical path.
+//
+// The generated file is committed to the repo, so a deploy never depends on
+// HaV being up. A scheduled GitHub Action re-runs this daily and commits any
+// changes (see .github/workflows/refresh-beaches.yml).
+//
+// Run manually: `node scripts/build-beaches.mjs`
+
+import { writeFile, mkdir } from "node:fs/promises";
+import { dirname, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const HAV_BASE_URL =
+ process.env.HAV_BASE_URL ?? "https://badplatsen.havochvatten.se/badplatsen/api";
+const HAV_USER_AGENT =
+ process.env.HAV_USER_AGENT ?? "BADA-build/1.0 (https://github.com/govargas/bada)";
+const SITE_URL = (process.env.SITE_URL ?? "https://badaweb.netlify.app").replace(
+ /\/$/,
+ ""
+);
+
+const PUBLIC_DIR = resolve(
+ dirname(fileURLToPath(import.meta.url)),
+ "../frontend/public"
+);
+const OUT = resolve(PUBLIC_DIR, "beaches.json");
+const SITEMAP_OUT = resolve(PUBLIC_DIR, "sitemap.xml");
+
+// Static routes worth indexing alongside the per-beach pages
+const STATIC_PATHS = [
+ "/",
+ "/what-is-eu-beach",
+ "/about",
+ "/terms",
+ "/privacy",
+ "/contact",
+];
+
+function buildSitemap(beaches) {
+ const today = new Date().toISOString().split("T")[0];
+ const urls = [
+ ...STATIC_PATHS.map((p) => `${SITE_URL}${p}`),
+ ...beaches.map((b) => `${SITE_URL}/beach/${encodeURIComponent(b.id)}`),
+ ];
+ const body = urls
+ .map((loc) => ` \n ${loc} \n ${today} \n `)
+ .join("\n");
+ return `\n\n${body}\n \n`;
+}
+
+// HaV's upstream API occasionally returns a transient 4xx/5xx or times out
+// (e.g. a momentary 400 Bad Request). A single bad reply shouldn't fail the
+// whole scheduled run, so retry a few times with exponential backoff before
+// giving up.
+async function fetchFeatures(url, { attempts = 4, baseDelayMs = 2_000 } = {}) {
+ let lastErr;
+ for (let attempt = 1; attempt <= attempts; attempt++) {
+ try {
+ const res = await fetch(url, {
+ headers: { "User-Agent": HAV_USER_AGENT, Accept: "application/json" },
+ signal: AbortSignal.timeout(20_000),
+ });
+ if (!res.ok) {
+ throw new Error(`HaV feature endpoint: ${res.status} ${res.statusText}`);
+ }
+ return await res.json();
+ } catch (err) {
+ lastErr = err;
+ if (attempt < attempts) {
+ const delay = baseDelayMs * 2 ** (attempt - 1);
+ console.warn(
+ `Attempt ${attempt}/${attempts} failed (${err.message}); retrying in ${delay}ms`
+ );
+ await new Promise((r) => setTimeout(r, delay));
+ }
+ }
+ }
+ throw lastErr;
+}
+
+async function main() {
+ const url = `${HAV_BASE_URL}/feature/?format=json`;
+ console.log(`Fetching ${url}`);
+
+ const data = await fetchFeatures(url);
+ const beaches = (data.features ?? [])
+ .map((f) => {
+ const p = f.properties;
+ const coords = f.geometry?.coordinates;
+ if (!p || !coords) return null;
+ const [lon, lat] = coords;
+ if (typeof lat !== "number" || typeof lon !== "number") return null;
+ return {
+ id: p.NUTSKOD ?? f.id ?? "",
+ name: p.NAMN ?? "OkΓ€nd",
+ municipality: p.KMN_NAMN ?? "",
+ lat,
+ lon,
+ };
+ })
+ .filter(Boolean)
+ .sort((a, b) => a.name.localeCompare(b.name, "sv"));
+
+ if (beaches.length === 0) {
+ throw new Error("Refusing to write an empty snapshot");
+ }
+
+ await mkdir(PUBLIC_DIR, { recursive: true });
+ await writeFile(OUT, JSON.stringify(beaches));
+ console.log(`Wrote ${beaches.length} beaches β ${OUT}`);
+
+ await writeFile(SITEMAP_OUT, buildSitemap(beaches));
+ console.log(
+ `Wrote sitemap with ${STATIC_PATHS.length + beaches.length} URLs β ${SITEMAP_OUT}`
+ );
+}
+
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});