diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..c82fbb6
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,36 @@
+# Dependencies
+node_modules
+
+# Build output
+dist
+
+# Environment files
+.env
+.env.*
+!.env.example
+
+# Git
+.git
+.gitignore
+
+# IDE
+.vscode
+.idea
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Logs
+*.log
+logs
+
+# Test
+coverage
+.nyc_output
+
+# Misc
+*.md
+!README.md
+.husky
+.github
diff --git a/.env.docker b/.env.docker
new file mode 100644
index 0000000..778f842
--- /dev/null
+++ b/.env.docker
@@ -0,0 +1,18 @@
+# Docker environment configuration
+# Copy this to .env when using docker-compose
+
+# Application
+PORT=3000
+
+# Database (MySQL container)
+MYSQL_ROOT_PASSWORD=rootpassword
+MYSQL_DATABASE=openplace
+MYSQL_USER=openplace
+MYSQL_PASSWORD=openplacepassword
+MYSQL_PORT=3306
+
+# Database URL (used by Prisma)
+DATABASE_URL="mysql://openplace:openplacepassword@mysql:3306/openplace"
+
+# JWT Secret (CHANGE THIS IN PRODUCTION!)
+JWT_SECRET="your-secret-key-change-in-production"
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..b1d03e8
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,113 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Openplace is an unofficial open-source backend for wplace (a collaborative pixel art canvas), built with TypeScript, tinyhttp, Prisma, and MySQL. The system manages user authentication, pixel painting with charge-based rate limiting, alliances, leaderboards, and moderation features.
+
+## Development Commands
+
+### Essential Commands
+- `pnpm dev` - Run development server with hot reload (watches src/ directory)
+- `pnpm build` - Compile TypeScript to dist/
+- `pnpm start` - Run production build from dist/
+- `pnpm test` - Run tests once
+- `pnpm test:watch` - Run tests in watch mode
+- `pnpm lint` - Check for linting issues
+- `pnpm lint:fix` - Auto-fix linting issues
+
+### Database Commands
+- `pnpm db:push` - Push schema changes to database and regenerate Prisma client
+- `pnpm db:generate` - Regenerate Prisma client only
+- `pnpm db:migrate` - Create and run a new migration
+- `pnpm seed` - Seed database with initial data
+
+### Requirements
+- Node.js >= 24
+- pnpm >= 10
+- MySQL/MariaDB
+
+## Architecture
+
+### Backend Structure
+
+**Entry Point**: [src/index.ts](src/index.ts) - Sets up tinyhttp app with middleware (CORS, cookie parser, JSON body parsing, logging) and registers all route modules.
+
+**Authentication Flow**: JWT tokens stored in `j` cookie → validated by [src/middleware/auth.ts](src/middleware/auth.ts) → checks session validity in database → attaches `req.user` with `{ id, sessionId }`
+
+**Database**: Prisma ORM with MySQL. Global instance exported from [src/config/database.ts](src/config/database.ts) and injected into request via middleware.
+
+**Route Organization**: Each feature module exports a function that registers routes on the tinyhttp app:
+- [src/routes/auth.ts](src/routes/auth.ts) - Registration, login, logout
+- [src/routes/pixel.ts](src/routes/pixel.ts) - Pixel painting, tile generation, pixel info
+- [src/routes/alliance.ts](src/routes/alliance.ts) - Alliance CRUD, invites, bans, leaderboards
+- [src/routes/me.ts](src/routes/me.ts) - User profile, favorite locations, settings
+- [src/routes/admin.ts](src/routes/admin.ts) - User management, bans, timeouts, tickets
+- [src/routes/moderator.ts](src/routes/moderator.ts) - Moderation actions
+- [src/routes/leaderboard.ts](src/routes/leaderboard.ts) - Global leaderboards
+- [src/routes/store.ts](src/routes/store.ts) - Purchase colors and flags
+
+**Service Layer**: Business logic isolated in service classes:
+- [src/services/pixel.ts](src/services/pixel.ts) - `PixelService` handles pixel painting with charge validation, color unlocking checks, tile image generation using @napi-rs/canvas, and level calculation
+- [src/services/alliance.ts](src/services/alliance.ts) - `AllianceService` handles alliance creation, member management, bans
+- [src/services/user.ts](src/services/user.ts) - `UserService` handles user profile updates, favorites
+
+**Core Systems**:
+
+1. **Charge System** ([src/utils/charges.ts](src/utils/charges.ts)): Rate-limiting mechanism where users have `maxCharges` (default 20) that regenerate every `chargesCooldownMs` (default 30s). Painting consumes charges. Function `calculateChargeRecharge()` computes current charge based on time elapsed.
+
+2. **Bitmap System** ([src/utils/bitmap.ts](src/utils/bitmap.ts)): `WplaceBitMap` class stores boolean flags as packed bytes for efficient storage (used for unlocked colors, flags). Stored in database as Bytes, converted to base64 for API responses.
+
+3. **Color Palette** ([src/utils/colors.ts](src/utils/colors.ts)): Defines available colors with RGB values and whether they're paid. `checkColorUnlocked()` validates if user has purchased a color by checking bitmap.
+
+4. **Tile System**: Canvas divided into 1000x1000 tiles. Each pixel has coordinates `(tileX, tileY, x, y)`. Tile images dynamically generated on-demand from pixel data.
+
+5. **Regions** ([src/config/regions.ts](src/config/regions.ts)): Maps coordinates to geographic regions/countries. Users get 10% charge discount when painting in their equipped flag's region. **Currently returns placeholder data - implementation needed.**
+
+### Database Schema
+
+Key models in [prisma/schema.prisma](prisma/schema.prisma):
+
+- **User**: Core user data, charge state, pixels painted, level, alliance membership, equipped flag, unlocked colors bitmap
+- **Pixel**: Individual pixel with coordinates (tileX, tileY, x, y), colorId, paintedBy userId, timestamp
+- **Tile**: Metadata for 1000x1000 tile regions, has many Pixels
+- **Alliance**: Groups with members, bans, invites, HQ coordinates, total pixels painted
+- **Session**: JWT session tracking with expiration
+- **Ticket**: Moderation reports with evidence
+- **UserNote**: Moderator notes on users
+
+### Frontend
+
+Pre-built SvelteKit frontend in [frontend/](frontend/) directory (served as static files, not part of development workflow). Backend serves 404.html for unmatched routes.
+
+## Key Implementation Patterns
+
+1. **Route Pattern**: Routes validate input → call service method → return JSON or handle service errors via `handleServiceError()`
+
+2. **Service Pattern**: Services receive Prisma client in constructor, contain business logic, throw descriptive errors that are caught by error handler middleware
+
+3. **Bulk Pixel Insert**: Painting uses raw SQL `INSERT ... ON DUPLICATE KEY UPDATE` for performance when updating multiple pixels
+
+4. **Level Calculation**: `Math.floor(Math.sqrt(pixelsPainted / 100)) + 1`
+
+5. **Validation**: Separate validator functions in [src/validators/](src/validators/) for common input patterns (seasons, coordinates, pagination)
+
+6. **Error Responses**: Standardized via `createErrorResponse()` and HTTP_STATUS constants in [src/utils/response.ts](src/utils/response.ts)
+
+## Environment Setup
+
+Copy `.env.example` to `.env` and configure:
+- `DATABASE_URL` - MySQL connection string (format: `mysql://user:password@host/database`)
+- `JWT_SECRET` - Secret key for JWT signing
+- `PORT` - Server port (default 3000)
+
+## Important Notes
+
+- The project is a work-in-progress with incomplete features (see README.md warnings)
+- Region lookup system is stubbed and returns placeholder data - needs implementation
+- Authentication uses JWT cookies named `j`
+- All API responses use JSON format
+- The backend is designed to work with the wplace.live frontend protocol
+- Production deployment requires SSL/HTTPS (enforced by design)
+- Use `pnpm` as package manager (not npm)
diff --git a/DOCKER.md b/DOCKER.md
new file mode 100644
index 0000000..cc31160
--- /dev/null
+++ b/DOCKER.md
@@ -0,0 +1,222 @@
+# Docker Setup Guide
+
+This guide explains how to build and run Openplace using Docker.
+
+## Quick Start
+
+### Prerequisites
+- Docker Engine 20.10+
+- Docker Compose 2.0+
+
+### Basic Usage
+
+1. **Build and start all services:**
+ ```bash
+ docker-compose up -d
+ ```
+
+2. **View logs:**
+ ```bash
+ docker-compose logs -f app
+ ```
+
+3. **Stop services:**
+ ```bash
+ docker-compose down
+ ```
+
+4. **Stop and remove volumes (deletes database):**
+ ```bash
+ docker-compose down -v
+ ```
+
+## Configuration
+
+### Environment Variables
+
+The easiest way to configure the application is to copy `.env.docker` to `.env`:
+
+```bash
+cp .env.docker .env
+```
+
+Then edit `.env` to customize:
+
+- `PORT` - Application port (default: 3000)
+- `MYSQL_*` - Database configuration
+- `JWT_SECRET` - **IMPORTANT:** Change this in production!
+
+### Production Deployment
+
+For production, you should:
+
+1. **Change the JWT secret:**
+ ```env
+ JWT_SECRET="your-secure-random-secret-here"
+ ```
+
+2. **Change database passwords:**
+ ```env
+ MYSQL_ROOT_PASSWORD="secure-root-password"
+ MYSQL_PASSWORD="secure-app-password"
+ ```
+
+3. **Use a reverse proxy (nginx/traefik) for SSL/HTTPS**
+
+4. **Set up regular database backups**
+
+## Docker Commands
+
+### Building
+
+```bash
+# Build the application image
+docker-compose build
+
+# Build without cache
+docker-compose build --no-cache
+```
+
+#### Using the precompiled frontend
+
+If you want to serve the legacy bundle in `frontend-backup/` instead of rebuilding `frontend-src`, set the build argument while building the image:
+
+```bash
+USE_FRONTEND_BACKUP=true docker-compose build
+```
+
+With `USE_FRONTEND_BACKUP=true` the Dockerfile skips the frontend build step and copies the existing `frontend-backup/` files into `/app/frontend` inside the image.
+
+### Running
+
+```bash
+# Start in foreground
+docker-compose up
+
+# Start in background
+docker-compose up -d
+
+# Start only specific services
+docker-compose up -d mysql
+```
+
+### Monitoring
+
+```bash
+# View logs
+docker-compose logs -f
+
+# View app logs only
+docker-compose logs -f app
+
+# Check service status
+docker-compose ps
+```
+
+### Database Management
+
+```bash
+# Access MySQL shell
+docker-compose exec mysql mysql -u openplace -p
+
+# Run migrations
+docker-compose exec app pnpm db:push
+
+# Seed database
+docker-compose exec app pnpm seed
+
+# Backup database
+docker-compose exec mysql mysqldump -u openplace -popenplacepassword openplace > backup.sql
+
+# Restore database
+docker-compose exec -T mysql mysql -u openplace -popenplacepassword openplace < backup.sql
+```
+
+### Maintenance
+
+```bash
+# Restart services
+docker-compose restart
+
+# Restart specific service
+docker-compose restart app
+
+# View resource usage
+docker stats openplace-app openplace-mysql
+
+# Clean up unused images
+docker image prune
+```
+
+## Standalone Docker Build
+
+If you prefer to build without docker-compose:
+
+```bash
+# Build image
+docker build -t openplace:latest .
+
+# Run container (requires existing MySQL)
+docker run -d \
+ --name openplace \
+ -p 3000:3000 \
+ -e DATABASE_URL="mysql://user:pass@host:3306/openplace" \
+ -e JWT_SECRET="your-secret" \
+ openplace:latest
+```
+
+## Development with Docker
+
+For development, you may want to mount your source code:
+
+```bash
+docker-compose -f docker-compose.dev.yml up
+```
+
+Or use the regular local development setup:
+```bash
+pnpm install
+pnpm dev
+```
+
+## Troubleshooting
+
+### Database connection fails
+
+Wait a few seconds for MySQL to initialize on first run. Check logs:
+```bash
+docker-compose logs mysql
+```
+
+### Port already in use
+
+Change the port in `.env`:
+```env
+PORT=3001
+```
+
+### Permission issues
+
+On Linux, you may need to adjust file permissions:
+```bash
+sudo chown -R $(id -u):$(id -g) .
+```
+
+### Clear everything and restart
+
+```bash
+docker-compose down -v
+docker-compose up -d --build
+```
+
+## Architecture
+
+The Docker setup consists of:
+
+- **openplace-app**: Node.js application container running the backend
+- **openplace-mysql**: MySQL 8.0 database container
+- **mysql-data**: Persistent volume for database storage
+- **openplace-network**: Bridge network for container communication
+
+The application automatically runs database migrations on startup.
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..cfe174a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,79 @@
+ARG USE_FRONTEND_BACKUP=false
+
+# Build stage
+FROM node:24-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@10 --activate
+
+# Set working directory
+WORKDIR /app
+
+# Copy package files for backend
+COPY package.json pnpm-lock.yaml ./
+
+# Install backend dependencies
+RUN pnpm install --frozen-lockfile
+
+# Copy source code and prisma schema
+COPY . .
+
+# Generate Prisma client
+RUN pnpm db:generate
+
+# Build TypeScript backend
+RUN pnpm build
+
+# Build frontend (or reuse compiled backup)
+WORKDIR /app
+ARG USE_FRONTEND_BACKUP
+RUN if [ "$USE_FRONTEND_BACKUP" = "true" ]; then \
+ rm -rf /app/frontend && mkdir -p /app/frontend && cp -R /app/frontend-backup/. /app/frontend/; \
+ else \
+ cd frontend-src && npm install && npm run build; \
+ fi
+
+# Create login.html from join.html if it doesn't exist
+RUN if [ -f /app/frontend/join.html ] && [ ! -f /app/frontend/login.html ]; then \
+ cp /app/frontend/join.html /app/frontend/login.html; \
+ fi
+
+
+# Production stage
+FROM node:24-alpine
+
+ARG USE_FRONTEND_BACKUP
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@10 --activate
+
+# Set working directory
+WORKDIR /app
+
+# Copy package files
+COPY package.json pnpm-lock.yaml ./
+
+# Install production dependencies only
+RUN pnpm install --frozen-lockfile --prod
+
+# Copy prisma schema for migrations
+COPY prisma ./prisma
+
+# Generate Prisma client
+RUN pnpm db:generate
+
+# Copy built application from builder stage
+COPY --from=builder /app/dist ./dist
+
+# Copy built frontend from builder stage
+COPY --from=builder /app/frontend ./frontend
+
+# Expose port
+EXPOSE 3000
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD node -e "require('http').get('http://localhost:3000/api/v1/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
+
+# Start the application
+CMD ["node", "dist/index.js"]
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..7e51464
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,72 @@
+version: '3.8'
+
+services:
+ mysql:
+ image: mysql:8.0
+ container_name: openplace-mysql
+ restart: unless-stopped
+ environment:
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-openplace}
+ MYSQL_USER: ${MYSQL_USER:-openplace}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-openplacepassword}
+ # No ports exposed - only accessible within Docker network
+ volumes:
+ - mysql-data:/var/lib/mysql
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ networks:
+ - openplace-network
+
+ adminer:
+ image: adminer:latest
+ container_name: openplace-adminer
+ restart: unless-stopped
+ ports:
+ - "${ADMINER_PORT:-8080}:8080"
+ environment:
+ ADMINER_DEFAULT_SERVER: mysql
+ depends_on:
+ - mysql
+ networks:
+ - openplace-network
+
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ args:
+ USE_FRONTEND_BACKUP: ${USE_FRONTEND_BACKUP:-false}
+ container_name: openplace-app
+ restart: unless-stopped
+ ports:
+ - "${PORT:-3000}:3000"
+ environment:
+ PORT: 3000
+ DATABASE_URL: "mysql://${MYSQL_USER:-openplace}:${MYSQL_PASSWORD:-openplacepassword}@mysql:3306/${MYSQL_DATABASE:-openplace}"
+ JWT_SECRET: ${JWT_SECRET:-change-this-secret-in-production}
+ NODE_ENV: production
+ depends_on:
+ mysql:
+ condition: service_healthy
+ networks:
+ - openplace-network
+ command: >
+ sh -c "
+ echo 'Waiting for database to be ready...' &&
+ sleep 5 &&
+ echo 'Running database migrations...' &&
+ pnpm db:push &&
+ echo 'Starting application...' &&
+ node dist/index.js
+ "
+
+volumes:
+ mysql-data:
+
+networks:
+ openplace-network:
+ driver: bridge
diff --git a/frontend-backup/404.html b/frontend-backup/404.html
new file mode 100644
index 0000000..8a2637f
--- /dev/null
+++ b/frontend-backup/404.html
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version: 1759175263375
+
+
+
+
+
+
+
diff --git a/frontend/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2 b/frontend-backup/PixelifySans-latin.vdc2vUDH.woff2
similarity index 100%
rename from frontend/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2
rename to frontend-backup/PixelifySans-latin.vdc2vUDH.woff2
diff --git a/frontend-backup/TODO.md b/frontend-backup/TODO.md
new file mode 100644
index 0000000..84ea2d5
--- /dev/null
+++ b/frontend-backup/TODO.md
@@ -0,0 +1,1458 @@
+# Frontend Recreation TODO
+
+This document outlines all the necessary information to recreate the SvelteKit frontend for the Openplace/Wplace application.
+
+## Overview
+
+Openplace is a collaborative real-time pixel art canvas layered over a world map (similar to r/place). Users can paint pixels, join alliances, view leaderboards, and moderate content. The frontend is built with SvelteKit and integrates with the backend API documented below.
+
+## Tech Stack
+
+- **Framework**: SvelteKit (based on `index.html` module imports)
+- **Language**: TypeScript (inferred from backend consistency)
+- **Fonts**:
+ - PixelifySans (pixel art style font)
+ - Geist (modern sans-serif)
+ - NotoColorEmoji (for flag emojis)
+- **Map Library**: Likely Leaflet or Mapbox (for world map integration)
+- **PWA**: Progressive Web App with service worker
+- **Build Tool**: Vite (standard for SvelteKit)
+
+## Project Structure
+
+Based on compiled output, the frontend likely has these routes:
+
+```
+/ - Main canvas view
+/admin - Admin panel
+/admin/* - Various admin sub-pages
+/moderation - Moderation panel
+/maps - Map-related pages
+404.html - 404 error page
+```
+
+## API Endpoints Reference
+
+### Authentication (`/auth`)
+
+#### POST `/login`
+- **Body**: `{ username: string, password: string }`
+- **Response**: `{ success: boolean }`
+- **Cookie**: Sets `j` cookie (JWT token, HttpOnly, 30 day expiry)
+- **Note**: Auto-registers users if username doesn't exist
+
+#### POST `/auth/logout`
+- **Headers**: Requires JWT cookie
+- **Response**: `{ success: boolean }`
+- **Cookie**: Clears `j` cookie
+
+---
+
+### Pixel Operations (`/pixel`)
+
+#### GET `/:season/tile/random`
+- **Purpose**: Get random tile coordinates to start viewing
+- **Response**: `{ tileX: number, tileY: number }`
+
+#### GET `/:season/pixel/:tileX/:tileY?x=X&y=Y`
+- **Purpose**: Get information about a specific pixel
+- **Query Params**:
+ - `x` (0-999): pixel X within tile
+ - `y` (0-999): pixel Y within tile
+- **Response**:
+```typescript
+{
+ colorId: number,
+ paintedBy: number,
+ paintedAt: string,
+ user: {
+ id: number,
+ name: string,
+ level: number,
+ equippedFlag: number
+ }
+}
+```
+
+#### GET `/files/:season/tiles/:tileX/:tileY.png`
+- **Purpose**: Get rendered tile image (1000x1000 pixels)
+- **Response**: PNG image
+- **Cache**: 5 minutes (`Cache-Control: public, max-age=300`)
+
+#### POST `/:season/pixel/:tileX/:tileY`
+- **Purpose**: Paint one or more pixels
+- **Headers**: Requires JWT cookie
+- **Body**:
+```typescript
+{
+ colors: number[], // Array of colorIds (0-63)
+ coords: number[][] // Array of [x, y] coordinates within tile
+}
+```
+- **Response**:
+```typescript
+{
+ success: boolean,
+ currentCharges: number,
+ maxCharges: number,
+ chargesLastUpdatedAt: string,
+ pixelsPainted: number,
+ level: number
+}
+```
+- **Errors**:
+ - 400: Invalid coordinates or colors
+ - 403: Not enough charges, color not unlocked, or timed out
+
+---
+
+### User Profile (`/me`)
+
+#### GET `/me`
+- **Headers**: Requires JWT cookie
+- **Response**:
+```typescript
+{
+ id: number,
+ name: string,
+ discord: string | null,
+ country: string,
+ droplets: number,
+ currentCharges: number,
+ maxCharges: number,
+ chargesCooldownMs: number,
+ chargesLastUpdatedAt: string,
+ pixelsPainted: number,
+ level: number,
+ equippedFlag: number,
+ extraColorsBitmap: string, // base64 encoded
+ flagsBitmap: string | null, // base64 encoded
+ showLastPixel: boolean,
+ picture: string | null,
+ allianceId: number | null,
+ allianceRole: string,
+ alliance: {
+ id: number,
+ name: string,
+ description: string,
+ pixelsPainted: number
+ } | null
+}
+```
+
+#### POST `/me/update`
+- **Headers**: Requires JWT cookie
+- **Body**:
+```typescript
+{
+ name?: string, // 3-20 chars
+ showLastPixel?: boolean,
+ discord?: string // Optional Discord username
+}
+```
+- **Response**: Updated user profile (same as GET /me)
+
+#### GET `/me/profile-pictures`
+- **Headers**: Requires JWT cookie
+- **Response**:
+```typescript
+{
+ pictures: Array<{
+ id: number,
+ url: string
+ }>
+}
+```
+
+---
+
+### Alliance System (`/alliance`)
+
+#### GET `/alliance`
+- **Headers**: Requires JWT cookie
+- **Response**: User's current alliance details or error if not in alliance
+
+#### POST `/alliance`
+- **Purpose**: Create new alliance
+- **Headers**: Requires JWT cookie
+- **Body**: `{ name: string }` // 3-30 chars, unique
+- **Response**: Alliance details
+
+#### POST `/alliance/update-description`
+- **Headers**: Requires JWT cookie (must be alliance admin)
+- **Body**: `{ description: string }` // Max 500 chars
+- **Response**: Updated alliance
+
+#### GET `/alliance/invites`
+- **Headers**: Requires JWT cookie (must be alliance admin)
+- **Response**:
+```typescript
+{
+ invites: Array<{
+ id: string, // UUID
+ createdAt: string
+ }>
+}
+```
+
+#### GET `/alliance/join/:invite`
+- **Headers**: Requires JWT cookie
+- **Response**: Joins alliance via invite code
+
+#### POST `/alliance/update-headquarters`
+- **Headers**: Requires JWT cookie (must be alliance admin)
+- **Body**: `{ latitude: number, longitude: number }`
+- **Response**: Updated alliance
+
+#### GET `/alliance/members/:page`
+- **Headers**: Requires JWT cookie
+- **Params**: `page` (0-indexed)
+- **Response**:
+```typescript
+{
+ members: Array<{
+ id: number,
+ name: string,
+ pixelsPainted: number,
+ level: number,
+ role: string,
+ equippedFlag: number
+ }>,
+ total: number,
+ page: number,
+ pageSize: number
+}
+```
+
+#### GET `/alliance/members/banned/:page`
+- **Headers**: Requires JWT cookie (must be alliance admin)
+- **Response**: Paginated list of banned users
+
+#### POST `/alliance/give-admin`
+- **Headers**: Requires JWT cookie (must be alliance owner)
+- **Body**: `{ promotedUserId: number }`
+- **Response**: 200 OK
+
+#### POST `/alliance/ban`
+- **Headers**: Requires JWT cookie (must be alliance admin)
+- **Body**: `{ bannedUserId: number }`
+- **Response**: Updated alliance
+
+#### POST `/alliance/unban`
+- **Headers**: Requires JWT cookie (must be alliance admin)
+- **Body**: `{ unbannedUserId: number }`
+- **Response**: Updated alliance
+
+#### GET `/alliance/leaderboard/:mode`
+- **Headers**: Requires JWT cookie
+- **Params**: `mode` - "today" | "week" | "month" | "all-time"
+- **Response**: Top 50 alliances with pixel counts
+
+---
+
+### Leaderboards (`/leaderboard`)
+
+#### GET `/leaderboard/player/:mode`
+- **Params**: `mode` - "today" | "week" | "month" | "all-time"
+- **Response**:
+```typescript
+Array<{
+ id: number,
+ name: string,
+ allianceId: number,
+ allianceName: string,
+ equippedFlag: number,
+ pixelsPainted: number,
+ picture?: string,
+ discord: string
+}>
+```
+
+#### GET `/leaderboard/alliance/:mode`
+- **Params**: `mode` - "today" | "week" | "month" | "all-time"
+- **Response**:
+```typescript
+Array<{
+ id: number,
+ name: string,
+ pixelsPainted: number
+}>
+```
+
+#### GET `/leaderboard/country/:mode`
+- **Params**: `mode` - "today" | "week" | "month" | "all-time"
+- **Response**: Array of countries with pixel counts
+- **Note**: Currently returns mock data, needs implementation
+
+#### GET `/leaderboard/region/:mode/:country`
+- **Params**:
+ - `mode` - "today" | "week" | "month" | "all-time"
+ - `country` - Country ID number
+- **Response**: Array of regions with pixel counts
+- **Note**: Currently returns mock data, needs implementation
+
+#### GET `/leaderboard/region/players/:city/:mode`
+- **Params**:
+ - `city` - City ID
+ - `mode` - "today" | "week" | "month" | "all-time"
+- **Response**: Top 50 players in region
+- **Note**: City parameter currently unused
+
+#### GET `/leaderboard/region/alliances/:city/:mode`
+- **Params**:
+ - `city` - City ID
+ - `mode` - "today" | "week" | "month" | "all-time"
+- **Response**: Top 50 alliances in region
+- **Note**: City parameter currently unused
+
+---
+
+### Store (`/store`)
+
+#### POST `/purchase`
+- **Headers**: Requires JWT cookie
+- **Body**:
+```typescript
+{
+ product: {
+ id: 70 | 80 | 100 | 110,
+ amount?: number, // Quantity (default 1)
+ variant?: number // For colors (32-63) or flags (1-251)
+ }
+}
+```
+- **Product IDs**:
+ - 70: +5 Max Charges (500 droplets)
+ - 80: +30 Paint Charges (500 droplets)
+ - 100: Unlock Paid Color (2000 droplets) - requires `variant` (32-63)
+ - 110: Unlock Flag (20,000 droplets) - requires `variant` (1-251)
+- **Response**: `{ success: boolean }`
+- **Errors**: 403 if insufficient droplets
+
+#### POST `/flag/equip/:id`
+- **Headers**: Requires JWT cookie
+- **Params**: `id` - Flag ID (1-251)
+- **Response**: `{ success: boolean }`
+- **Errors**: 403 if flag not unlocked
+
+---
+
+### Admin Panel (`/admin/*`)
+
+**All admin endpoints require:**
+- JWT cookie authentication
+- User role = "admin"
+- Returns 403 Forbidden otherwise
+
+#### GET `/admin/users?id=USER_ID`
+- **Query**: `id` - User ID
+- **Response**:
+```typescript
+{
+ id: number,
+ name: string,
+ droplets: number,
+ picture: string | null,
+ role: string,
+ timeout_until: string,
+ ban_reason: null, // TODO: Not implemented
+ reported_times: 0, // TODO: Not implemented
+ timeouts_count: 0, // TODO: Not implemented
+ same_ip_accounts: 0, // TODO: Not implemented
+ alliance_id: number | null,
+ alliance_name: string | null,
+ pixels_painted: number,
+ phone_validated: false, // TODO: Not implemented
+ discord: string | null
+}
+```
+
+#### GET `/admin/users/notes?userId=USER_ID`
+- **Query**: `userId` - User ID
+- **Response**:
+```typescript
+{
+ notes: Array<{
+ id: number,
+ author: {
+ role: string,
+ id: number,
+ name: string
+ },
+ note: string,
+ createdAt: string
+ }>
+}
+```
+
+#### POST `/admin/users/notes`
+- **Body**: `{ userId: number, note: string }`
+- **Response**: `{}`
+
+#### GET `/admin/users/tickets?id=USER_ID`
+- **Query**: `id` - User ID
+- **Response**: `{}` // TODO: Not implemented
+
+#### GET `/admin/users/purchases?userId=USER_ID`
+- **Query**: `userId` - User ID
+- **Response**: `{}` // TODO: Not implemented
+
+#### POST `/admin/users/set-user-droplets`
+- **Body**: `{ userId: number, droplets: number }` // Adds droplets (can be negative)
+- **Response**: `{ success: boolean }`
+
+#### GET `/admin/tickets`
+- **Response**: Open tickets grouped by reported user
+```typescript
+{
+ tickets: Array<{
+ id: number, // Reported user ID
+ reportedUser: {
+ id: number,
+ name: string,
+ discord: string,
+ country: string,
+ banned: boolean
+ },
+ createdAt: string,
+ reports: Array<{
+ id: string, // Ticket ID (UUID)
+ latitude: number,
+ longitude: number,
+ zoom: number,
+ reason: string,
+ notes: string,
+ image: string,
+ createdAt: string
+ }>
+ }>,
+ status: 200
+}
+```
+
+#### GET `/admin/closed-tickets`
+- **Response**: Same as `/admin/tickets` but for resolved tickets
+
+#### GET `/admin/open-tickets-count`
+- **Response**: `{ tickets: number }`
+
+#### POST `/admin/severe-open-tickets-count`
+- **Response**: `{ tickets: number }`
+
+#### POST `/admin/assign-new-tickets`
+- **Response**: `{ newTicketsIds: [] }` // TODO: Not implemented
+
+#### GET `/admin/count-all-tickets`
+- **Response**:
+```typescript
+{
+ doxxing: number,
+ inappropriate_content: number,
+ hate_speech: number,
+ bot: number,
+ other: number,
+ griefing: number,
+ total_open_tickets: number
+}
+```
+
+#### GET `/admin/count-all-reports`
+- **Response**: Same as `/admin/count-all-tickets` // TODO: Uses same data
+
+#### GET `/admin/alliances/:id`
+- **Params**: `id` - Alliance ID
+- **Response**:
+```typescript
+{
+ id: number,
+ name: string,
+ pixelsPainted: number
+}
+```
+
+#### GET `/admin/alliances/:id/full`
+- **Params**: `id` - Alliance ID
+- **Response**: Full alliance details including members, bans, etc.
+
+#### GET `/admin/alliances/search?q=QUERY`
+- **Query**: `q` - Search by name or ID
+- **Response**: `{ results: Alliance[] }` // Top 20 results
+
+---
+
+### Moderation Panel (`/moderator/*`)
+
+**All moderator endpoints require:**
+- JWT cookie authentication
+- User role = "moderator" or "admin"
+- Returns 403 Forbidden otherwise
+
+#### GET `/moderator/tickets`
+- **Response**: Same format as `/admin/tickets`
+
+#### GET `/moderator/users/tickets?userId=USER_ID`
+- **Query**: `userId` - User ID
+- **Response**: All tickets for a specific user
+
+#### GET `/moderator/open-tickets-count`
+- **Response**: `{ tickets: number }`
+
+#### POST `/moderator/severe-open-tickets-count`
+- **Response**: `{ tickets: number }`
+
+#### POST `/moderator/assign-new-tickets`
+- **Response**: `{ newTicketsIds: [] }` // TODO: Not implemented
+
+#### GET `/moderator/count-my-tickets`
+- **Response**: `0` // TODO: Not implemented
+
+---
+
+## Core Frontend Features to Implement
+
+### 1. Authentication System
+
+**Components:**
+- Login form (see `LoginForm` CSS asset)
+- Registration flow (combined with login)
+- Session management using JWT cookie
+- Auto-redirect to login if unauthorized
+
+**Key Implementation Details:**
+- Cookie name: `j`
+- Cookie is HttpOnly (not accessible via JavaScript)
+- 30-day expiration
+- Auto-create account on first login with username/password
+
+---
+
+### 2. Main Canvas View
+
+**Components:**
+- Interactive world map (Leaflet/Mapbox)
+- Tile-based pixel rendering system
+- Zoom controls
+- Color picker palette (32 free colors + 32 paid colors)
+- Brush/paint tool
+- Pixel info tooltip on hover/click
+- Charge indicator (shows current/max charges)
+- Level display
+
+**Technical Requirements:**
+- Tiles are 1000x1000 pixels
+- Fetch tiles as PNG images: `/files/:season/tiles/:tileX/:tileY.png`
+- Cache tiles appropriately (5 min cache header)
+- Calculate global coordinates: `globalX = tileX * 1000 + x`, `globalY = tileY * 1000 + y`
+- Map global coordinates to lat/lng for world map overlay
+- Handle painting multiple pixels in one request
+- Show charge regeneration countdown (default: 1 charge per 30 seconds)
+- Disable paid colors unless unlocked (check `extraColorsBitmap`)
+
+**Charge System:**
+- Default: 20 max charges
+- Regenerates 1 charge every 30 seconds (configurable per user)
+- Painting consumes charges
+- Must calculate current charges: `currentCharges + floor((now - lastUpdate) / cooldownMs)`
+- 10% discount when painting in equipped flag's region (TODO: region system not implemented)
+
+**Color Palette (0-63):**
+```typescript
+// Colors 0-31: Free
+// Colors 32-63: Paid (require purchase)
+// Color 0: Transparent
+// Check if color unlocked: extraColorsBitmap & (1 << (colorId - 32))
+```
+
+Full color palette available in backend: `src/utils/colors.ts`
+
+---
+
+### 3. User Profile Page
+
+**Components:**
+- Profile avatar with level indicator (see `ProfileAvatarWithLevel` CSS asset)
+- Username (editable)
+- Discord username (editable)
+- Show last pixel toggle
+- Droplets balance
+- Charges indicator
+- Pixels painted count
+- Level display
+- Equipped flag display
+- Alliance affiliation
+
+**Features:**
+- Edit profile settings
+- View unlocked colors
+- View unlocked flags
+- View alliance info
+- View favorite locations (TODO: not implemented in backend)
+
+---
+
+### 4. Alliance System
+
+**Components:**
+- Alliance creation dialog
+- Alliance info panel
+- Member list (paginated, 50 per page)
+- Admin controls (for alliance admins)
+- Invite system
+- Ban management
+- Headquarters map marker
+
+**Features:**
+- Create alliance (requires no current alliance)
+- Join alliance via invite link
+- Leave alliance
+- Update description (admins only)
+- Set headquarters location on map (admins only)
+- Promote members to admin (owner only)
+- Ban/unban members (admins only)
+- View alliance leaderboard
+
+---
+
+### 5. Leaderboards
+
+**Views:**
+- Player leaderboard (top 50)
+- Alliance leaderboard (top 50)
+- Country leaderboard
+- Region leaderboard
+- Regional player leaderboard
+- Regional alliance leaderboard
+
+**Time Filters:**
+- Today
+- Week (last 7 days)
+- Month (current month)
+- All-time
+
+**Display Fields:**
+- Rank (1-50)
+- Player name / Alliance name
+- Equipped flag icon
+- Pixels painted
+- Alliance affiliation (for players)
+
+---
+
+### 6. Store System
+
+**Products:**
+1. **+5 Max Charges** (500 droplets)
+ - Increases maxCharges by 5
+ - Can purchase multiple
+
+2. **+30 Paint Charges** (500 droplets)
+ - Adds 30 to currentCharges (up to max)
+ - Can purchase multiple
+
+3. **Unlock Paid Color** (2000 droplets each)
+ - Unlocks one of colors 32-63
+ - Must select color variant
+ - Updates `extraColorsBitmap`
+
+4. **Unlock Flag** (20,000 droplets each)
+ - Unlocks one of 251 country flags
+ - Must select flag variant (1-251)
+ - Updates `flagsBitmap`
+
+**Implementation:**
+- Display droplet balance
+- Show which colors/flags are already unlocked
+- Disable purchase if insufficient droplets
+- Confirmation dialog before purchase
+- Update UI after successful purchase
+
+**Flag Equipping:**
+- Separate endpoint to equip owned flag
+- Can only equip flags that are unlocked
+- Equipped flag shown on profile and leaderboards
+
+---
+
+### 7. Admin Panel
+
+**Pages:**
+- User management
+- Ticket management (reports)
+- Alliance management
+- Statistics dashboard
+
+**User Management:**
+- Search users by ID
+- View user details
+- View user notes
+- Add moderator notes
+- Set droplets (add/subtract)
+- View user tickets
+- View purchase history (TODO)
+
+**Ticket Management:**
+- View open tickets
+- View closed tickets
+- Tickets grouped by reported user
+- Show ticket details (location, reason, image evidence)
+- Assign tickets to moderators (TODO)
+- Count tickets by reason
+
+**Alliance Management:**
+- Search alliances
+- View alliance details
+- View full alliance info (members, bans)
+
+---
+
+### 8. Moderation Panel
+
+**Features:**
+- View assigned tickets
+- View all open tickets
+- View user ticket history
+- Count severe tickets
+- Count my assigned tickets (TODO)
+
+**Ticket Types:**
+- Doxxing
+- Inappropriate Content
+- Hate Speech
+- Bot
+- Griefing
+- Other
+
+**Ticket Details:**
+- Reporter info
+- Reported user info
+- Canvas location (lat/lng, zoom)
+- Reason
+- Notes
+- Evidence image
+- Timestamp
+
+---
+
+## Data Models
+
+### User
+```typescript
+{
+ id: number
+ name: string
+ discord: string | null
+ country: string
+ email: string | null
+ banned: boolean
+ timeoutUntil: Date
+ role: "user" | "moderator" | "admin"
+ pixelsPainted: number
+ droplets: number
+ maxCharges: number
+ currentCharges: number
+ chargesCooldownMs: number
+ chargesLastUpdatedAt: Date
+ extraColorsBitmap: number // Bitmask for unlocked paid colors
+ flagsBitmap: Bytes | null // Bitmap for unlocked flags
+ equippedFlag: number // Currently equipped flag (0 = none)
+ showLastPixel: boolean
+ picture: string | null
+ level: number // floor(sqrt(pixelsPainted / 100)) + 1
+ allianceId: number | null
+ allianceRole: "member" | "admin" | "owner"
+}
+```
+
+### Alliance
+```typescript
+{
+ id: number
+ name: string // Unique, 3-30 chars
+ description: string | null // Max 500 chars
+ hqLatitude: number | null
+ hqLongitude: number | null
+ pixelsPainted: number
+ members: User[]
+ bannedUsers: BannedUser[]
+ invites: AllianceInvite[]
+}
+```
+
+### Pixel
+```typescript
+{
+ id: number
+ tileX: number
+ tileY: number
+ x: number // 0-999
+ y: number // 0-999
+ colorId: number // 0-63
+ paintedBy: number // User ID
+ paintedAt: Date
+}
+```
+
+### Tile
+```typescript
+{
+ id: number
+ x: number // Tile X coordinate
+ y: number // Tile Y coordinate
+ imageData: Bytes | null // Cached PNG (if applicable)
+ pixels: Pixel[]
+}
+```
+
+### Ticket (Report)
+```typescript
+{
+ id: string // UUID
+ userId: number // Reporter
+ reportedUserId: number // Reported user
+ latitude: number // Canvas location
+ longitude: number
+ zoom: number
+ reason: "doxxing" | "inappropriate_content" | "hate_speech" | "bot" | "griefing" | "other"
+ notes: string
+ image: string // Evidence image URL/path
+ resolved: boolean
+ severe: boolean
+ createdAt: Date
+}
+```
+
+### Region
+```typescript
+{
+ id: number
+ cityId: number
+ name: string
+ number: number
+ countryId: number
+ flagId: number
+}
+```
+
+---
+
+## Constants and Configuration
+
+### Season
+- Default: `"s1"` (Season 1)
+- Used in pixel API endpoints: `/:season/pixel/...`
+
+### Color Palette
+- 64 total colors (0-63)
+- 0-31: Free colors
+- 32-63: Paid colors (2000 droplets each)
+- Color 0: Transparent/eraser
+
+### Flags
+- 251 total country flags (1-251)
+- 20,000 droplets each
+- Stored as bitmap in `flagsBitmap`
+
+### Charge System
+- Default max charges: 20
+- Default cooldown: 30,000ms (30 seconds)
+- Formula: `floor((now - lastUpdate) / cooldownMs)` charges regenerated
+
+### Level Calculation
+```typescript
+level = floor(sqrt(pixelsPainted / 100)) + 1
+```
+
+### Pagination
+- Default page size: 50
+- Pages are 0-indexed
+
+### Validation Rules
+- Username: 3-20 characters
+- Alliance name: 3-30 characters, unique
+- Alliance description: Max 500 characters
+- Coordinates: x, y must be 0-999 within tile
+- Color ID: 0-63
+
+---
+
+## State Management
+
+**Client-side state to manage:**
+
+1. **User State**
+ - Current user profile
+ - Authentication status
+ - Charge count (auto-update based on time)
+ - Droplets balance
+ - Unlocked colors/flags
+
+2. **Canvas State**
+ - Current map position (lat/lng)
+ - Zoom level
+ - Visible tiles
+ - Selected color
+ - Brush mode
+ - Cached tile images
+
+3. **Alliance State**
+ - Current alliance
+ - Member list
+ - Invites (if admin)
+ - Leaderboard
+
+4. **UI State**
+ - Active modal/dialog
+ - Sidebar open/closed
+ - Selected leaderboard mode
+ - Selected leaderboard time filter
+
+**Real-time considerations:**
+- Pixel updates from other users (consider WebSocket/polling)
+- Charge regeneration countdown
+- Leaderboard updates
+
+---
+
+## UI/UX Guidelines
+
+### Theme
+- Light theme only (from meta tag: `color-scheme: light only`)
+- Theme color: `#f8f4f0` (from webmanifest)
+- Background: `#ffffff`
+
+### Fonts
+- **PixelifySans**: Use for headings, canvas UI elements, retro aesthetic
+- **Geist**: Use for body text, modern UI
+- **NotoColorEmoji**: Use for flag rendering
+
+### Responsive Design
+- Mobile-first approach
+- PWA optimized
+- Touch-friendly controls for canvas
+- Separate mobile/desktop layouts for complex pages (admin panel)
+
+### Key Interactions
+- Hover over pixel: Show tooltip with painter info
+- Click pixel: Show detailed pixel info modal
+- Click map: Pan to location
+- Click color: Select for painting
+- Click canvas: Paint pixel(s) with selected color
+- Right-click/long-press: Color picker (pick color from canvas)
+
+---
+
+## Assets Required
+
+### Images
+- Favicon (multiple sizes)
+- App icons (192x192, 512x512)
+- PWA screenshots
+- Flag sprite sheet (flags.webp, flags@2x.webp @ 2x resolution)
+- OG image for social sharing
+
+### Audio
+- `notification.mp3` - For notification sounds
+
+### Existing Assets (in `/frontend` folder)
+- `/img/*` - Various images
+- `/maps/*` - Map-related assets
+- `/download.png`, `/download.svg` - Download icons
+- `PixelifySans-latin.vdc2vUDH.woff2` - Font file
+- `css2.css` - Likely Google Fonts CSS
+
+---
+
+## Service Worker & PWA
+
+**Features to implement:**
+- Offline canvas viewing (cache tiles)
+- Background sync for painted pixels
+- Push notifications for alliance updates
+- Install prompt handling (see `window.pwaInstallPrompt` in index.html)
+- Cache strategy for static assets
+- Network-first for API calls
+- Cache-first for tile images
+
+**Service Worker Registration:**
+```javascript
+if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.register('/service-worker.js');
+}
+```
+
+---
+
+## WebSocket / Real-time Updates (Recommended)
+
+While not currently implemented in the backend, the frontend should be designed to support real-time updates:
+
+**Potential WebSocket events:**
+- `pixel:painted` - Another user painted a pixel
+- `tile:updated` - Tile has new pixels
+- `alliance:member_joined` - New alliance member
+- `charge:regenerated` - Charge regenerated (client-side timer is fine too)
+- `leaderboard:updated` - Leaderboard changed
+
+**Implementation approach:**
+1. Start with polling (GET tile images every 5 seconds for visible tiles)
+2. Design component architecture to easily swap in WebSocket later
+3. Use event emitter pattern for pixel updates
+
+---
+
+## Routing Structure (SvelteKit)
+
+```
+src/routes/
+├── +page.svelte # Main canvas view
+├── +layout.svelte # Root layout (auth check, header, etc.)
+├── admin/
+│ ├── +page.svelte # Admin dashboard
+│ ├── users/
+│ │ └── +page.svelte # User management
+│ ├── tickets/
+│ │ ├── +page.svelte # Open tickets
+│ │ └── closed/+page.svelte # Closed tickets
+│ └── alliances/
+│ └── +page.svelte # Alliance management
+├── moderation/
+│ ├── +page.svelte # Moderator dashboard
+│ └── tickets/+page.svelte # Assigned tickets
+├── leaderboard/
+│ └── +page.svelte # Leaderboard with tabs
+├── profile/
+│ └── +page.svelte # User profile
+├── alliance/
+│ ├── +page.svelte # Alliance view/create
+│ └── [inviteId]/+page.svelte # Join alliance via invite
+└── store/
+ └── +page.svelte # Store page
+```
+
+---
+
+## Component Architecture (Suggested)
+
+### Shared Components
+```
+src/lib/components/
+├── auth/
+│ ├── LoginForm.svelte
+│ └── AuthGuard.svelte
+├── canvas/
+│ ├── MapCanvas.svelte
+│ ├── TileLayer.svelte
+│ ├── ColorPicker.svelte
+│ ├── BrushTool.svelte
+│ ├── PixelInfo.svelte
+│ └── ChargeIndicator.svelte
+├── user/
+│ ├── ProfileAvatar.svelte
+│ ├── ProfileAvatarWithLevel.svelte # Existing CSS asset
+│ ├── UserCard.svelte
+│ └── UserStats.svelte
+├── alliance/
+│ ├── AllianceCard.svelte
+│ ├── AllianceMembers.svelte
+│ ├── AllianceInvite.svelte
+│ └── CreateAlliance.svelte
+├── leaderboard/
+│ ├── LeaderboardTable.svelte
+│ ├── LeaderboardFilters.svelte
+│ └── LeaderboardEntry.svelte
+├── store/
+│ ├── StoreItem.svelte
+│ ├── ColorUnlockGrid.svelte
+│ └── FlagSelector.svelte
+├── admin/
+│ ├── UserSearch.svelte
+│ ├── UserDetails.svelte
+│ ├── TicketList.svelte
+│ ├── TicketDetails.svelte
+│ └── AllianceSearch.svelte
+└── common/
+ ├── Button.svelte
+ ├── Modal.svelte
+ ├── Pagination.svelte
+ ├── Toast.svelte
+ └── Tooltip.svelte
+```
+
+---
+
+## Store (Svelte Stores)
+
+```typescript
+// src/lib/stores/auth.ts
+export const currentUser = writable(null);
+export const isAuthenticated = derived(currentUser, $user => !!$user);
+
+// src/lib/stores/canvas.ts
+export const selectedColor = writable(1);
+export const currentCharges = writable(20);
+export const canvasPosition = writable<{lat: number, lng: number, zoom: number}>();
+export const visibleTiles = writable>(); // "x,y" tile keys
+
+// src/lib/stores/alliance.ts
+export const currentAlliance = writable(null);
+
+// src/lib/stores/ui.ts
+export const activeModal = writable(null);
+export const sidebarOpen = writable(false);
+```
+
+---
+
+## API Client
+
+Create a typed API client for all backend endpoints:
+
+```typescript
+// src/lib/api/client.ts
+export class ApiClient {
+ private baseUrl = ''; // Same origin
+
+ // Auth
+ async login(username: string, password: string) { ... }
+ async logout() { ... }
+
+ // Pixels
+ async getRandomTile() { ... }
+ async getPixelInfo(tileX, tileY, x, y) { ... }
+ async paintPixels(tileX, tileY, colors, coords) { ... }
+ getTileImageUrl(tileX, tileY): string { ... }
+
+ // User
+ async getProfile() { ... }
+ async updateProfile(data) { ... }
+
+ // Alliance
+ async getAlliance() { ... }
+ async createAlliance(name) { ... }
+ // ... etc
+
+ // Leaderboards
+ async getPlayerLeaderboard(mode) { ... }
+ // ... etc
+
+ // Store
+ async purchase(productId, amount, variant?) { ... }
+ async equipFlag(flagId) { ... }
+
+ // Admin (requires admin role)
+ async getUser(userId) { ... }
+ // ... etc
+}
+
+export const api = new ApiClient();
+```
+
+---
+
+## Bitmap Utilities (Client-side)
+
+Implement bitmap helper for colors and flags:
+
+```typescript
+// src/lib/utils/bitmap.ts
+export class WplaceBitmap {
+ private bytes: Uint8Array;
+
+ constructor(base64?: string) {
+ if (base64) {
+ this.bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
+ } else {
+ this.bytes = new Uint8Array(0);
+ }
+ }
+
+ get(index: number): boolean {
+ const byteIndex = Math.floor(index / 8);
+ const bitIndex = index % 8;
+ if (byteIndex >= this.bytes.length) return false;
+ const realIndex = this.bytes.length - 1 - byteIndex;
+ return (this.bytes[realIndex] & (1 << bitIndex)) !== 0;
+ }
+
+ toBase64(): string {
+ return btoa(String.fromCharCode(...this.bytes));
+ }
+}
+
+export function isColorUnlocked(colorId: number, extraColorsBitmap: number): boolean {
+ if (colorId < 32) return true;
+ const mask = 1 << (colorId - 32);
+ return (extraColorsBitmap & mask) !== 0;
+}
+```
+
+---
+
+## Charge Calculation (Client-side)
+
+```typescript
+// src/lib/utils/charges.ts
+export function calculateCurrentCharges(
+ currentCharges: number,
+ maxCharges: number,
+ lastUpdate: Date,
+ cooldownMs: number
+): number {
+ if (currentCharges >= maxCharges) return currentCharges;
+
+ const timeSinceLastUpdate = Date.now() - lastUpdate.getTime();
+ const chargesGenerated = Math.floor(timeSinceLastUpdate / cooldownMs);
+
+ return Math.min(maxCharges, currentCharges + chargesGenerated);
+}
+
+export function getNextChargeTime(
+ currentCharges: number,
+ maxCharges: number,
+ lastUpdate: Date,
+ cooldownMs: number
+): Date | null {
+ if (currentCharges >= maxCharges) return null;
+
+ const timeSinceLastUpdate = Date.now() - lastUpdate.getTime();
+ const timeUntilNextCharge = cooldownMs - (timeSinceLastUpdate % cooldownMs);
+
+ return new Date(Date.now() + timeUntilNextCharge);
+}
+```
+
+---
+
+## Level Calculation (Client-side)
+
+```typescript
+// src/lib/utils/level.ts
+export function calculateLevel(pixelsPainted: number): number {
+ return Math.floor(Math.sqrt(pixelsPainted / 100)) + 1;
+}
+
+export function getPixelsForNextLevel(currentLevel: number): number {
+ return ((currentLevel + 1 - 1) ** 2) * 100;
+}
+
+export function getLevelProgress(pixelsPainted: number): number {
+ const currentLevel = calculateLevel(pixelsPainted);
+ const pixelsForCurrentLevel = ((currentLevel - 1) ** 2) * 100;
+ const pixelsForNextLevel = (currentLevel ** 2) * 100;
+ const pixelsInCurrentLevel = pixelsPainted - pixelsForCurrentLevel;
+ const pixelsNeededForLevel = pixelsForNextLevel - pixelsForCurrentLevel;
+
+ return pixelsInCurrentLevel / pixelsNeededForLevel;
+}
+```
+
+---
+
+## Color Palette (Client-side)
+
+```typescript
+// src/lib/constants/colors.ts
+export interface Color {
+ rgb: [number, number, number];
+ paid: boolean;
+}
+
+export const COLOR_PALETTE: Record = {
+ 0: { rgb: [0, 0, 0], paid: false }, // Transparent
+ 1: { rgb: [0, 0, 0], paid: false },
+ 2: { rgb: [60, 60, 60], paid: false },
+ // ... (copy from backend src/utils/colors.ts)
+ 63: { rgb: [205, 197, 158], paid: true }
+};
+
+export function getColorHex(colorId: number): string {
+ const color = COLOR_PALETTE[colorId];
+ if (!color) return '#000000';
+ const [r, g, b] = color.rgb;
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
+}
+```
+
+---
+
+## Testing Checklist
+
+### Authentication
+- [ ] Login with existing account
+- [ ] Register new account (auto-create on login)
+- [ ] Logout
+- [ ] Session persistence across page reloads
+- [ ] Redirect to login on 401
+
+### Canvas
+- [ ] Load random tile on first visit
+- [ ] Pan and zoom map
+- [ ] Render tile images correctly
+- [ ] Paint single pixel
+- [ ] Paint multiple pixels
+- [ ] Color picker selection
+- [ ] Charge deduction after painting
+- [ ] Charge regeneration countdown
+- [ ] Hover tooltip with pixel info
+- [ ] Cannot paint without charges
+- [ ] Cannot paint with locked color
+
+### Profile
+- [ ] View own profile
+- [ ] Edit username
+- [ ] Edit discord
+- [ ] Toggle show last pixel
+- [ ] View unlocked colors
+- [ ] View unlocked flags
+- [ ] Display correct level
+
+### Alliance
+- [ ] Create alliance
+- [ ] Join alliance via invite
+- [ ] Leave alliance
+- [ ] Update description (admin)
+- [ ] Set HQ location (admin)
+- [ ] View members list (paginated)
+- [ ] Promote member (owner only)
+- [ ] Ban member (admin)
+- [ ] Unban member (admin)
+- [ ] View alliance leaderboard
+
+### Leaderboards
+- [ ] Player leaderboard (all time modes)
+- [ ] Alliance leaderboard (all time modes)
+- [ ] Correct sorting by pixels painted
+- [ ] Display alliance affiliation for players
+- [ ] Display equipped flags
+
+### Store
+- [ ] Purchase max charges
+- [ ] Purchase paint charges
+- [ ] Purchase color unlock
+- [ ] Purchase flag unlock
+- [ ] Equip purchased flag
+- [ ] Cannot purchase without droplets
+- [ ] Cannot equip non-owned flag
+
+### Admin Panel
+- [ ] Search user by ID
+- [ ] View user details
+- [ ] Add user note
+- [ ] Set user droplets
+- [ ] View open tickets
+- [ ] View closed tickets
+- [ ] Count tickets by reason
+- [ ] Search alliances
+- [ ] View alliance details
+
+### Moderation Panel
+- [ ] View assigned tickets
+- [ ] View all open tickets
+- [ ] Count severe tickets
+- [ ] View user ticket history
+
+---
+
+## Known Limitations / TODOs
+
+**Backend TODOs (frontend should account for):**
+1. Region system returns placeholder data
+2. Country/region leaderboards not fully implemented
+3. Ticket assignment system not implemented
+4. Purchase history not tracked
+5. User ban/timeout system incomplete
+6. Phone verification not implemented
+7. Same IP account detection not implemented
+8. Report counts not implemented
+
+**Frontend recommendations:**
+- Add WebSocket support for real-time pixel updates
+- Implement efficient tile caching strategy
+- Add undo/redo for painting
+- Add eyedropper tool (pick color from canvas)
+- Add minimap for navigation
+- Add search functionality for map locations
+- Add notification system for alliance events
+- Add dark mode toggle (update meta tag)
+
+---
+
+## Development Setup
+
+1. **Initialize SvelteKit project:**
+ ```bash
+ npm create svelte@latest frontend
+ cd frontend
+ npm install
+ ```
+
+2. **Install dependencies:**
+ ```bash
+ npm install -D @sveltejs/adapter-static
+ npm install leaflet # or mapbox-gl
+ npm install @types/leaflet -D
+ ```
+
+3. **Configure for static build:**
+ Update `svelte.config.js` to use `adapter-static`
+
+4. **Environment variables:**
+ Create `.env`:
+ ```
+ PUBLIC_API_URL=http://localhost:3000
+ PUBLIC_SEASON=s1
+ ```
+
+5. **Development:**
+ ```bash
+ npm run dev
+ ```
+
+6. **Build:**
+ ```bash
+ npm run build
+ ```
+ Output to `build/` directory, copy to backend's `frontend/` folder
+
+---
+
+## API Response Error Handling
+
+All endpoints follow consistent error format:
+
+```typescript
+{
+ error: string, // Error message
+ status: number // HTTP status code
+}
+```
+
+**Common status codes:**
+- 400: Bad Request (validation error)
+- 401: Unauthorized (not logged in)
+- 403: Forbidden (insufficient permissions, banned, timed out, or not enough resources)
+- 404: Not Found
+- 500: Internal Server Error
+
+**Frontend should handle:**
+- Display error messages from `error` field
+- Redirect to login on 401
+- Show appropriate UI feedback for 403 (e.g., "You don't have permission")
+- Retry on 500 with exponential backoff
+
+---
+
+## Final Notes
+
+This TODO document provides a comprehensive reference for recreating the frontend. The backend API is fully functional and documented here. The frontend should be built as a SvelteKit static site that communicates with this backend via the documented API endpoints.
+
+Key priorities:
+1. Authentication and session management
+2. Main canvas view with painting functionality
+3. User profile and settings
+4. Alliance system
+5. Leaderboards
+6. Store
+7. Admin/moderation panels
+
+The compiled frontend in the current `frontend/` folder can serve as a reference for styling and UX patterns, but the source code needs to be recreated from scratch based on this documentation.
diff --git a/frontend/_app/immutable/assets/0.CmqRY0au.css b/frontend-backup/_app/immutable/assets/0.CmqRY0au.css
similarity index 100%
rename from frontend/_app/immutable/assets/0.CmqRY0au.css
rename to frontend-backup/_app/immutable/assets/0.CmqRY0au.css
diff --git a/frontend/_app/immutable/assets/0.DQCxyt33.css b/frontend-backup/_app/immutable/assets/0.DQCxyt33.css
similarity index 100%
rename from frontend/_app/immutable/assets/0.DQCxyt33.css
rename to frontend-backup/_app/immutable/assets/0.DQCxyt33.css
diff --git a/frontend/_app/immutable/assets/18.BD1hRFPA.css b/frontend-backup/_app/immutable/assets/18.BD1hRFPA.css
similarity index 100%
rename from frontend/_app/immutable/assets/18.BD1hRFPA.css
rename to frontend-backup/_app/immutable/assets/18.BD1hRFPA.css
diff --git a/frontend/_app/immutable/assets/2.BtKF873c.css b/frontend-backup/_app/immutable/assets/2.BtKF873c.css
similarity index 100%
rename from frontend/_app/immutable/assets/2.BtKF873c.css
rename to frontend-backup/_app/immutable/assets/2.BtKF873c.css
diff --git a/frontend/_app/immutable/assets/4.BtKF873c.css b/frontend-backup/_app/immutable/assets/4.BtKF873c.css
similarity index 100%
rename from frontend/_app/immutable/assets/4.BtKF873c.css
rename to frontend-backup/_app/immutable/assets/4.BtKF873c.css
diff --git a/frontend/_app/immutable/assets/Geist-latin.Dg_dQHbK.woff2 b/frontend-backup/_app/immutable/assets/Geist-latin.Dg_dQHbK.woff2
similarity index 100%
rename from frontend/_app/immutable/assets/Geist-latin.Dg_dQHbK.woff2
rename to frontend-backup/_app/immutable/assets/Geist-latin.Dg_dQHbK.woff2
diff --git a/frontend/_app/immutable/assets/LoginForm.CxMG0irz.css b/frontend-backup/_app/immutable/assets/LoginForm.CxMG0irz.css
similarity index 100%
rename from frontend/_app/immutable/assets/LoginForm.CxMG0irz.css
rename to frontend-backup/_app/immutable/assets/LoginForm.CxMG0irz.css
diff --git a/frontend/_app/immutable/assets/NotoColorEmoji-flags.ClvgErYz.woff2 b/frontend-backup/_app/immutable/assets/NotoColorEmoji-flags.ClvgErYz.woff2
similarity index 100%
rename from frontend/_app/immutable/assets/NotoColorEmoji-flags.ClvgErYz.woff2
rename to frontend-backup/_app/immutable/assets/NotoColorEmoji-flags.ClvgErYz.woff2
diff --git a/frontend-backup/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2 b/frontend-backup/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2
new file mode 100644
index 0000000..ea75ad5
Binary files /dev/null and b/frontend-backup/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2 differ
diff --git a/frontend/_app/immutable/assets/ProfileAvatarWithLevel.6dmPRSfx.css b/frontend-backup/_app/immutable/assets/ProfileAvatarWithLevel.6dmPRSfx.css
similarity index 100%
rename from frontend/_app/immutable/assets/ProfileAvatarWithLevel.6dmPRSfx.css
rename to frontend-backup/_app/immutable/assets/ProfileAvatarWithLevel.6dmPRSfx.css
diff --git a/frontend/_app/immutable/assets/flags.a2kmUSbF.webp b/frontend-backup/_app/immutable/assets/flags.a2kmUSbF.webp
similarity index 100%
rename from frontend/_app/immutable/assets/flags.a2kmUSbF.webp
rename to frontend-backup/_app/immutable/assets/flags.a2kmUSbF.webp
diff --git a/frontend/_app/immutable/assets/flags@2x.gR6KPp3x.webp b/frontend-backup/_app/immutable/assets/flags@2x.gR6KPp3x.webp
similarity index 100%
rename from frontend/_app/immutable/assets/flags@2x.gR6KPp3x.webp
rename to frontend-backup/_app/immutable/assets/flags@2x.gR6KPp3x.webp
diff --git a/frontend/_app/immutable/assets/notification.CPyrWqU1.mp3 b/frontend-backup/_app/immutable/assets/notification.CPyrWqU1.mp3
similarity index 100%
rename from frontend/_app/immutable/assets/notification.CPyrWqU1.mp3
rename to frontend-backup/_app/immutable/assets/notification.CPyrWqU1.mp3
diff --git a/frontend/_app/immutable/chunks/0.CnnlsrhC.js b/frontend-backup/_app/immutable/chunks/0.CnnlsrhC.js
similarity index 100%
rename from frontend/_app/immutable/chunks/0.CnnlsrhC.js
rename to frontend-backup/_app/immutable/chunks/0.CnnlsrhC.js
diff --git a/frontend/_app/immutable/chunks/07L1R_bo.js b/frontend-backup/_app/immutable/chunks/07L1R_bo.js
similarity index 100%
rename from frontend/_app/immutable/chunks/07L1R_bo.js
rename to frontend-backup/_app/immutable/chunks/07L1R_bo.js
diff --git a/frontend/_app/immutable/chunks/1lh-LSvX.js b/frontend-backup/_app/immutable/chunks/1lh-LSvX.js
similarity index 100%
rename from frontend/_app/immutable/chunks/1lh-LSvX.js
rename to frontend-backup/_app/immutable/chunks/1lh-LSvX.js
diff --git a/frontend/_app/immutable/chunks/2.BY7SdjrD.js b/frontend-backup/_app/immutable/chunks/2.BY7SdjrD.js
similarity index 100%
rename from frontend/_app/immutable/chunks/2.BY7SdjrD.js
rename to frontend-backup/_app/immutable/chunks/2.BY7SdjrD.js
diff --git a/frontend/_app/immutable/chunks/2CRhGZHc.js b/frontend-backup/_app/immutable/chunks/2CRhGZHc.js
similarity index 100%
rename from frontend/_app/immutable/chunks/2CRhGZHc.js
rename to frontend-backup/_app/immutable/chunks/2CRhGZHc.js
diff --git a/frontend/_app/immutable/chunks/4WsUhDWi.js b/frontend-backup/_app/immutable/chunks/4WsUhDWi.js
similarity index 100%
rename from frontend/_app/immutable/chunks/4WsUhDWi.js
rename to frontend-backup/_app/immutable/chunks/4WsUhDWi.js
diff --git a/frontend/_app/immutable/chunks/4k6DpCgf.js b/frontend-backup/_app/immutable/chunks/4k6DpCgf.js
similarity index 100%
rename from frontend/_app/immutable/chunks/4k6DpCgf.js
rename to frontend-backup/_app/immutable/chunks/4k6DpCgf.js
diff --git a/frontend/_app/immutable/chunks/5NasrULQ.js b/frontend-backup/_app/immutable/chunks/5NasrULQ.js
similarity index 100%
rename from frontend/_app/immutable/chunks/5NasrULQ.js
rename to frontend-backup/_app/immutable/chunks/5NasrULQ.js
diff --git a/frontend/_app/immutable/chunks/5mOJ66sL.js b/frontend-backup/_app/immutable/chunks/5mOJ66sL.js
similarity index 100%
rename from frontend/_app/immutable/chunks/5mOJ66sL.js
rename to frontend-backup/_app/immutable/chunks/5mOJ66sL.js
diff --git a/frontend/_app/immutable/chunks/6TAPgKgc.js b/frontend-backup/_app/immutable/chunks/6TAPgKgc.js
similarity index 100%
rename from frontend/_app/immutable/chunks/6TAPgKgc.js
rename to frontend-backup/_app/immutable/chunks/6TAPgKgc.js
diff --git a/frontend/_app/immutable/chunks/B1GmkH4o.js b/frontend-backup/_app/immutable/chunks/B1GmkH4o.js
similarity index 100%
rename from frontend/_app/immutable/chunks/B1GmkH4o.js
rename to frontend-backup/_app/immutable/chunks/B1GmkH4o.js
diff --git a/frontend/_app/immutable/chunks/B2cHk4HI.js b/frontend-backup/_app/immutable/chunks/B2cHk4HI.js
similarity index 100%
rename from frontend/_app/immutable/chunks/B2cHk4HI.js
rename to frontend-backup/_app/immutable/chunks/B2cHk4HI.js
diff --git a/frontend/_app/immutable/chunks/B4HM4TqG.js b/frontend-backup/_app/immutable/chunks/B4HM4TqG.js
similarity index 100%
rename from frontend/_app/immutable/chunks/B4HM4TqG.js
rename to frontend-backup/_app/immutable/chunks/B4HM4TqG.js
diff --git a/frontend/_app/immutable/chunks/BCONGQnO.js b/frontend-backup/_app/immutable/chunks/BCONGQnO.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BCONGQnO.js
rename to frontend-backup/_app/immutable/chunks/BCONGQnO.js
diff --git a/frontend/_app/immutable/chunks/BDALf20I.js b/frontend-backup/_app/immutable/chunks/BDALf20I.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BDALf20I.js
rename to frontend-backup/_app/immutable/chunks/BDALf20I.js
diff --git a/frontend/_app/immutable/chunks/BHr_eBwR.js b/frontend-backup/_app/immutable/chunks/BHr_eBwR.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BHr_eBwR.js
rename to frontend-backup/_app/immutable/chunks/BHr_eBwR.js
diff --git a/frontend/_app/immutable/chunks/BMKgGW48.js b/frontend-backup/_app/immutable/chunks/BMKgGW48.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BMKgGW48.js
rename to frontend-backup/_app/immutable/chunks/BMKgGW48.js
diff --git a/frontend/_app/immutable/chunks/BMfwGdZU.js b/frontend-backup/_app/immutable/chunks/BMfwGdZU.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BMfwGdZU.js
rename to frontend-backup/_app/immutable/chunks/BMfwGdZU.js
diff --git a/frontend/_app/immutable/chunks/BNZUboE0.js b/frontend-backup/_app/immutable/chunks/BNZUboE0.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BNZUboE0.js
rename to frontend-backup/_app/immutable/chunks/BNZUboE0.js
diff --git a/frontend/_app/immutable/chunks/BUhRjcOt.js b/frontend-backup/_app/immutable/chunks/BUhRjcOt.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BUhRjcOt.js
rename to frontend-backup/_app/immutable/chunks/BUhRjcOt.js
diff --git a/frontend/_app/immutable/chunks/Bke_korE.js b/frontend-backup/_app/immutable/chunks/Bke_korE.js
similarity index 100%
rename from frontend/_app/immutable/chunks/Bke_korE.js
rename to frontend-backup/_app/immutable/chunks/Bke_korE.js
diff --git a/frontend/_app/immutable/chunks/BpEsgMDn.js b/frontend-backup/_app/immutable/chunks/BpEsgMDn.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BpEsgMDn.js
rename to frontend-backup/_app/immutable/chunks/BpEsgMDn.js
diff --git a/frontend/_app/immutable/chunks/BpFpuxGr.js b/frontend-backup/_app/immutable/chunks/BpFpuxGr.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BpFpuxGr.js
rename to frontend-backup/_app/immutable/chunks/BpFpuxGr.js
diff --git a/frontend/_app/immutable/chunks/BrZ10JY-.js b/frontend-backup/_app/immutable/chunks/BrZ10JY-.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BrZ10JY-.js
rename to frontend-backup/_app/immutable/chunks/BrZ10JY-.js
diff --git a/frontend/_app/immutable/chunks/BtAj0icR.js b/frontend-backup/_app/immutable/chunks/BtAj0icR.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BtAj0icR.js
rename to frontend-backup/_app/immutable/chunks/BtAj0icR.js
diff --git a/frontend/_app/immutable/chunks/BtP6pfnb.js b/frontend-backup/_app/immutable/chunks/BtP6pfnb.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BtP6pfnb.js
rename to frontend-backup/_app/immutable/chunks/BtP6pfnb.js
diff --git a/frontend/_app/immutable/chunks/BuTItAOu.js b/frontend-backup/_app/immutable/chunks/BuTItAOu.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BuTItAOu.js
rename to frontend-backup/_app/immutable/chunks/BuTItAOu.js
diff --git a/frontend/_app/immutable/chunks/BvbG2Lay.js b/frontend-backup/_app/immutable/chunks/BvbG2Lay.js
similarity index 100%
rename from frontend/_app/immutable/chunks/BvbG2Lay.js
rename to frontend-backup/_app/immutable/chunks/BvbG2Lay.js
diff --git a/frontend/_app/immutable/chunks/ByKBPM-D.js b/frontend-backup/_app/immutable/chunks/ByKBPM-D.js
similarity index 100%
rename from frontend/_app/immutable/chunks/ByKBPM-D.js
rename to frontend-backup/_app/immutable/chunks/ByKBPM-D.js
diff --git a/frontend/_app/immutable/chunks/Bzak7iHL.js b/frontend-backup/_app/immutable/chunks/Bzak7iHL.js
similarity index 100%
rename from frontend/_app/immutable/chunks/Bzak7iHL.js
rename to frontend-backup/_app/immutable/chunks/Bzak7iHL.js
diff --git a/frontend/_app/immutable/chunks/C-Y7nmnD.js b/frontend-backup/_app/immutable/chunks/C-Y7nmnD.js
similarity index 100%
rename from frontend/_app/immutable/chunks/C-Y7nmnD.js
rename to frontend-backup/_app/immutable/chunks/C-Y7nmnD.js
diff --git a/frontend/_app/immutable/chunks/C2Ms0SfR.js b/frontend-backup/_app/immutable/chunks/C2Ms0SfR.js
similarity index 100%
rename from frontend/_app/immutable/chunks/C2Ms0SfR.js
rename to frontend-backup/_app/immutable/chunks/C2Ms0SfR.js
diff --git a/frontend/_app/immutable/chunks/C5GsJ62f.js b/frontend-backup/_app/immutable/chunks/C5GsJ62f.js
similarity index 100%
rename from frontend/_app/immutable/chunks/C5GsJ62f.js
rename to frontend-backup/_app/immutable/chunks/C5GsJ62f.js
diff --git a/frontend/_app/immutable/chunks/CAQlJ3np.js b/frontend-backup/_app/immutable/chunks/CAQlJ3np.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CAQlJ3np.js
rename to frontend-backup/_app/immutable/chunks/CAQlJ3np.js
diff --git a/frontend/_app/immutable/chunks/CBqzI9hL.js b/frontend-backup/_app/immutable/chunks/CBqzI9hL.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CBqzI9hL.js
rename to frontend-backup/_app/immutable/chunks/CBqzI9hL.js
diff --git a/frontend/_app/immutable/chunks/CDZgL_Bh.js b/frontend-backup/_app/immutable/chunks/CDZgL_Bh.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CDZgL_Bh.js
rename to frontend-backup/_app/immutable/chunks/CDZgL_Bh.js
diff --git a/frontend/_app/immutable/chunks/CMs8vKjq.js b/frontend-backup/_app/immutable/chunks/CMs8vKjq.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CMs8vKjq.js
rename to frontend-backup/_app/immutable/chunks/CMs8vKjq.js
diff --git a/frontend/_app/immutable/chunks/CQklNc9N.js b/frontend-backup/_app/immutable/chunks/CQklNc9N.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CQklNc9N.js
rename to frontend-backup/_app/immutable/chunks/CQklNc9N.js
diff --git a/frontend/_app/immutable/chunks/CVCd3urP.js b/frontend-backup/_app/immutable/chunks/CVCd3urP.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CVCd3urP.js
rename to frontend-backup/_app/immutable/chunks/CVCd3urP.js
diff --git a/frontend/_app/immutable/chunks/CYItkO2S.js b/frontend-backup/_app/immutable/chunks/CYItkO2S.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CYItkO2S.js
rename to frontend-backup/_app/immutable/chunks/CYItkO2S.js
diff --git a/frontend/_app/immutable/chunks/CZW2bcQi.js b/frontend-backup/_app/immutable/chunks/CZW2bcQi.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CZW2bcQi.js
rename to frontend-backup/_app/immutable/chunks/CZW2bcQi.js
diff --git a/frontend/_app/immutable/chunks/CeLr1p76.js b/frontend-backup/_app/immutable/chunks/CeLr1p76.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CeLr1p76.js
rename to frontend-backup/_app/immutable/chunks/CeLr1p76.js
diff --git a/frontend/_app/immutable/chunks/ChY_8ULT.js b/frontend-backup/_app/immutable/chunks/ChY_8ULT.js
similarity index 100%
rename from frontend/_app/immutable/chunks/ChY_8ULT.js
rename to frontend-backup/_app/immutable/chunks/ChY_8ULT.js
diff --git a/frontend/_app/immutable/chunks/ChoU6b3z.js b/frontend-backup/_app/immutable/chunks/ChoU6b3z.js
similarity index 100%
rename from frontend/_app/immutable/chunks/ChoU6b3z.js
rename to frontend-backup/_app/immutable/chunks/ChoU6b3z.js
diff --git a/frontend/_app/immutable/chunks/ClOhzjRc.js b/frontend-backup/_app/immutable/chunks/ClOhzjRc.js
similarity index 100%
rename from frontend/_app/immutable/chunks/ClOhzjRc.js
rename to frontend-backup/_app/immutable/chunks/ClOhzjRc.js
diff --git a/frontend/_app/immutable/chunks/CmAc-jwz.js b/frontend-backup/_app/immutable/chunks/CmAc-jwz.js
similarity index 100%
rename from frontend/_app/immutable/chunks/CmAc-jwz.js
rename to frontend-backup/_app/immutable/chunks/CmAc-jwz.js
diff --git a/frontend/_app/immutable/chunks/Cp3o644A.js b/frontend-backup/_app/immutable/chunks/Cp3o644A.js
similarity index 100%
rename from frontend/_app/immutable/chunks/Cp3o644A.js
rename to frontend-backup/_app/immutable/chunks/Cp3o644A.js
diff --git a/frontend/_app/immutable/chunks/D1ivTjwA.js b/frontend-backup/_app/immutable/chunks/D1ivTjwA.js
similarity index 100%
rename from frontend/_app/immutable/chunks/D1ivTjwA.js
rename to frontend-backup/_app/immutable/chunks/D1ivTjwA.js
diff --git a/frontend/_app/immutable/chunks/D2m5UD3G.js b/frontend-backup/_app/immutable/chunks/D2m5UD3G.js
similarity index 100%
rename from frontend/_app/immutable/chunks/D2m5UD3G.js
rename to frontend-backup/_app/immutable/chunks/D2m5UD3G.js
diff --git a/frontend/_app/immutable/chunks/D35KiPL1.js b/frontend-backup/_app/immutable/chunks/D35KiPL1.js
similarity index 100%
rename from frontend/_app/immutable/chunks/D35KiPL1.js
rename to frontend-backup/_app/immutable/chunks/D35KiPL1.js
diff --git a/frontend/_app/immutable/chunks/DCxPsWiR.js b/frontend-backup/_app/immutable/chunks/DCxPsWiR.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DCxPsWiR.js
rename to frontend-backup/_app/immutable/chunks/DCxPsWiR.js
diff --git a/frontend/_app/immutable/chunks/DFzO1c4b.js b/frontend-backup/_app/immutable/chunks/DFzO1c4b.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DFzO1c4b.js
rename to frontend-backup/_app/immutable/chunks/DFzO1c4b.js
diff --git a/frontend/_app/immutable/chunks/DM9nRpoa.js b/frontend-backup/_app/immutable/chunks/DM9nRpoa.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DM9nRpoa.js
rename to frontend-backup/_app/immutable/chunks/DM9nRpoa.js
diff --git a/frontend/_app/immutable/chunks/DS58drb5.js b/frontend-backup/_app/immutable/chunks/DS58drb5.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DS58drb5.js
rename to frontend-backup/_app/immutable/chunks/DS58drb5.js
diff --git a/frontend/_app/immutable/chunks/DS5O-Inb.js b/frontend-backup/_app/immutable/chunks/DS5O-Inb.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DS5O-Inb.js
rename to frontend-backup/_app/immutable/chunks/DS5O-Inb.js
diff --git a/frontend/_app/immutable/chunks/DUoKDNpf.js b/frontend-backup/_app/immutable/chunks/DUoKDNpf.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DUoKDNpf.js
rename to frontend-backup/_app/immutable/chunks/DUoKDNpf.js
diff --git a/frontend/_app/immutable/chunks/DV6L2nvf.js b/frontend-backup/_app/immutable/chunks/DV6L2nvf.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DV6L2nvf.js
rename to frontend-backup/_app/immutable/chunks/DV6L2nvf.js
diff --git a/frontend/_app/immutable/chunks/DXjtejww.js b/frontend-backup/_app/immutable/chunks/DXjtejww.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DXjtejww.js
rename to frontend-backup/_app/immutable/chunks/DXjtejww.js
diff --git a/frontend/_app/immutable/chunks/DdJK9GIy.js b/frontend-backup/_app/immutable/chunks/DdJK9GIy.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DdJK9GIy.js
rename to frontend-backup/_app/immutable/chunks/DdJK9GIy.js
diff --git a/frontend/_app/immutable/chunks/DffDvEhl.js b/frontend-backup/_app/immutable/chunks/DffDvEhl.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DffDvEhl.js
rename to frontend-backup/_app/immutable/chunks/DffDvEhl.js
diff --git a/frontend/_app/immutable/chunks/DhR_xAc4.js b/frontend-backup/_app/immutable/chunks/DhR_xAc4.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DhR_xAc4.js
rename to frontend-backup/_app/immutable/chunks/DhR_xAc4.js
diff --git a/frontend/_app/immutable/chunks/DkBFL3pa.js b/frontend-backup/_app/immutable/chunks/DkBFL3pa.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DkBFL3pa.js
rename to frontend-backup/_app/immutable/chunks/DkBFL3pa.js
diff --git a/frontend/_app/immutable/chunks/DklPLC_x.js b/frontend-backup/_app/immutable/chunks/DklPLC_x.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DklPLC_x.js
rename to frontend-backup/_app/immutable/chunks/DklPLC_x.js
diff --git a/frontend/_app/immutable/chunks/DnhglgUZ.js b/frontend-backup/_app/immutable/chunks/DnhglgUZ.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DnhglgUZ.js
rename to frontend-backup/_app/immutable/chunks/DnhglgUZ.js
diff --git a/frontend/_app/immutable/chunks/Dp1pzeXC.js b/frontend-backup/_app/immutable/chunks/Dp1pzeXC.js
similarity index 100%
rename from frontend/_app/immutable/chunks/Dp1pzeXC.js
rename to frontend-backup/_app/immutable/chunks/Dp1pzeXC.js
diff --git a/frontend/_app/immutable/chunks/Drv8f_fG.js b/frontend-backup/_app/immutable/chunks/Drv8f_fG.js
similarity index 100%
rename from frontend/_app/immutable/chunks/Drv8f_fG.js
rename to frontend-backup/_app/immutable/chunks/Drv8f_fG.js
diff --git a/frontend/_app/immutable/chunks/DsJqb9ei.js b/frontend-backup/_app/immutable/chunks/DsJqb9ei.js
similarity index 100%
rename from frontend/_app/immutable/chunks/DsJqb9ei.js
rename to frontend-backup/_app/immutable/chunks/DsJqb9ei.js
diff --git a/frontend/_app/immutable/chunks/EXYzlOI1.js b/frontend-backup/_app/immutable/chunks/EXYzlOI1.js
similarity index 100%
rename from frontend/_app/immutable/chunks/EXYzlOI1.js
rename to frontend-backup/_app/immutable/chunks/EXYzlOI1.js
diff --git a/frontend/_app/immutable/chunks/F0pgzfyy.js b/frontend-backup/_app/immutable/chunks/F0pgzfyy.js
similarity index 100%
rename from frontend/_app/immutable/chunks/F0pgzfyy.js
rename to frontend-backup/_app/immutable/chunks/F0pgzfyy.js
diff --git a/frontend/_app/immutable/chunks/GVP1MJz5.js b/frontend-backup/_app/immutable/chunks/GVP1MJz5.js
similarity index 100%
rename from frontend/_app/immutable/chunks/GVP1MJz5.js
rename to frontend-backup/_app/immutable/chunks/GVP1MJz5.js
diff --git a/frontend/_app/immutable/chunks/KvV259my.js b/frontend-backup/_app/immutable/chunks/KvV259my.js
similarity index 100%
rename from frontend/_app/immutable/chunks/KvV259my.js
rename to frontend-backup/_app/immutable/chunks/KvV259my.js
diff --git a/frontend/_app/immutable/chunks/U908S-6f.js b/frontend-backup/_app/immutable/chunks/U908S-6f.js
similarity index 100%
rename from frontend/_app/immutable/chunks/U908S-6f.js
rename to frontend-backup/_app/immutable/chunks/U908S-6f.js
diff --git a/frontend/_app/immutable/chunks/Y9es74tr.js b/frontend-backup/_app/immutable/chunks/Y9es74tr.js
similarity index 100%
rename from frontend/_app/immutable/chunks/Y9es74tr.js
rename to frontend-backup/_app/immutable/chunks/Y9es74tr.js
diff --git a/frontend/_app/immutable/chunks/ZzI7cLBE.js b/frontend-backup/_app/immutable/chunks/ZzI7cLBE.js
similarity index 100%
rename from frontend/_app/immutable/chunks/ZzI7cLBE.js
rename to frontend-backup/_app/immutable/chunks/ZzI7cLBE.js
diff --git a/frontend/_app/immutable/chunks/cUtKXcx3.js b/frontend-backup/_app/immutable/chunks/cUtKXcx3.js
similarity index 100%
rename from frontend/_app/immutable/chunks/cUtKXcx3.js
rename to frontend-backup/_app/immutable/chunks/cUtKXcx3.js
diff --git a/frontend/_app/immutable/chunks/fZ59cmjx.js b/frontend-backup/_app/immutable/chunks/fZ59cmjx.js
similarity index 100%
rename from frontend/_app/immutable/chunks/fZ59cmjx.js
rename to frontend-backup/_app/immutable/chunks/fZ59cmjx.js
diff --git a/frontend/_app/immutable/chunks/g8c1BvYP.js b/frontend-backup/_app/immutable/chunks/g8c1BvYP.js
similarity index 100%
rename from frontend/_app/immutable/chunks/g8c1BvYP.js
rename to frontend-backup/_app/immutable/chunks/g8c1BvYP.js
diff --git a/frontend/_app/immutable/chunks/hLPYzGnf.js b/frontend-backup/_app/immutable/chunks/hLPYzGnf.js
similarity index 100%
rename from frontend/_app/immutable/chunks/hLPYzGnf.js
rename to frontend-backup/_app/immutable/chunks/hLPYzGnf.js
diff --git a/frontend/_app/immutable/chunks/rLj4C5Bn.js b/frontend-backup/_app/immutable/chunks/rLj4C5Bn.js
similarity index 100%
rename from frontend/_app/immutable/chunks/rLj4C5Bn.js
rename to frontend-backup/_app/immutable/chunks/rLj4C5Bn.js
diff --git a/frontend/_app/immutable/chunks/sZ1mzRzK.js b/frontend-backup/_app/immutable/chunks/sZ1mzRzK.js
similarity index 100%
rename from frontend/_app/immutable/chunks/sZ1mzRzK.js
rename to frontend-backup/_app/immutable/chunks/sZ1mzRzK.js
diff --git a/frontend/_app/immutable/chunks/start.CJ_UwIBa.js b/frontend-backup/_app/immutable/chunks/start.CJ_UwIBa.js
similarity index 100%
rename from frontend/_app/immutable/chunks/start.CJ_UwIBa.js
rename to frontend-backup/_app/immutable/chunks/start.CJ_UwIBa.js
diff --git a/frontend/_app/immutable/chunks/x1RL6Wqy.js b/frontend-backup/_app/immutable/chunks/x1RL6Wqy.js
similarity index 100%
rename from frontend/_app/immutable/chunks/x1RL6Wqy.js
rename to frontend-backup/_app/immutable/chunks/x1RL6Wqy.js
diff --git a/frontend/_app/immutable/entry/app.CuVZ6Ons.js b/frontend-backup/_app/immutable/entry/app.CuVZ6Ons.js
similarity index 100%
rename from frontend/_app/immutable/entry/app.CuVZ6Ons.js
rename to frontend-backup/_app/immutable/entry/app.CuVZ6Ons.js
diff --git a/frontend/_app/immutable/entry/app.iDaujbEI.js b/frontend-backup/_app/immutable/entry/app.iDaujbEI.js
similarity index 100%
rename from frontend/_app/immutable/entry/app.iDaujbEI.js
rename to frontend-backup/_app/immutable/entry/app.iDaujbEI.js
diff --git a/frontend/_app/immutable/entry/start.CJ_UwIBa.js b/frontend-backup/_app/immutable/entry/start.CJ_UwIBa.js
similarity index 100%
rename from frontend/_app/immutable/entry/start.CJ_UwIBa.js
rename to frontend-backup/_app/immutable/entry/start.CJ_UwIBa.js
diff --git a/frontend/_app/immutable/entry/start.CqSbdZXc.js b/frontend-backup/_app/immutable/entry/start.CqSbdZXc.js
similarity index 100%
rename from frontend/_app/immutable/entry/start.CqSbdZXc.js
rename to frontend-backup/_app/immutable/entry/start.CqSbdZXc.js
diff --git a/frontend/_app/immutable/nodes/0.CnnlsrhC.js b/frontend-backup/_app/immutable/nodes/0.CnnlsrhC.js
similarity index 100%
rename from frontend/_app/immutable/nodes/0.CnnlsrhC.js
rename to frontend-backup/_app/immutable/nodes/0.CnnlsrhC.js
diff --git a/frontend/_app/immutable/nodes/0.DIpSCqpd.js b/frontend-backup/_app/immutable/nodes/0.DIpSCqpd.js
similarity index 100%
rename from frontend/_app/immutable/nodes/0.DIpSCqpd.js
rename to frontend-backup/_app/immutable/nodes/0.DIpSCqpd.js
diff --git a/frontend/_app/immutable/nodes/1.-aaO_7rD.js b/frontend-backup/_app/immutable/nodes/1.-aaO_7rD.js
similarity index 100%
rename from frontend/_app/immutable/nodes/1.-aaO_7rD.js
rename to frontend-backup/_app/immutable/nodes/1.-aaO_7rD.js
diff --git a/frontend/_app/immutable/nodes/1.DpC5h7KA.js b/frontend-backup/_app/immutable/nodes/1.DpC5h7KA.js
similarity index 100%
rename from frontend/_app/immutable/nodes/1.DpC5h7KA.js
rename to frontend-backup/_app/immutable/nodes/1.DpC5h7KA.js
diff --git a/frontend/_app/immutable/nodes/10.2PlMuzkM.js b/frontend-backup/_app/immutable/nodes/10.2PlMuzkM.js
similarity index 100%
rename from frontend/_app/immutable/nodes/10.2PlMuzkM.js
rename to frontend-backup/_app/immutable/nodes/10.2PlMuzkM.js
diff --git a/frontend/_app/immutable/nodes/11.7LNU-V2c.js b/frontend-backup/_app/immutable/nodes/11.7LNU-V2c.js
similarity index 100%
rename from frontend/_app/immutable/nodes/11.7LNU-V2c.js
rename to frontend-backup/_app/immutable/nodes/11.7LNU-V2c.js
diff --git a/frontend/_app/immutable/nodes/12.Dk7Cyr8v.js b/frontend-backup/_app/immutable/nodes/12.Dk7Cyr8v.js
similarity index 100%
rename from frontend/_app/immutable/nodes/12.Dk7Cyr8v.js
rename to frontend-backup/_app/immutable/nodes/12.Dk7Cyr8v.js
diff --git a/frontend/_app/immutable/nodes/13.DsAxPfo7.js b/frontend-backup/_app/immutable/nodes/13.DsAxPfo7.js
similarity index 100%
rename from frontend/_app/immutable/nodes/13.DsAxPfo7.js
rename to frontend-backup/_app/immutable/nodes/13.DsAxPfo7.js
diff --git a/frontend/_app/immutable/nodes/14.TE67n0On.js b/frontend-backup/_app/immutable/nodes/14.TE67n0On.js
similarity index 100%
rename from frontend/_app/immutable/nodes/14.TE67n0On.js
rename to frontend-backup/_app/immutable/nodes/14.TE67n0On.js
diff --git a/frontend/_app/immutable/nodes/15.BKIY6Gje.js b/frontend-backup/_app/immutable/nodes/15.BKIY6Gje.js
similarity index 100%
rename from frontend/_app/immutable/nodes/15.BKIY6Gje.js
rename to frontend-backup/_app/immutable/nodes/15.BKIY6Gje.js
diff --git a/frontend/_app/immutable/nodes/16.CKya8A82.js b/frontend-backup/_app/immutable/nodes/16.CKya8A82.js
similarity index 100%
rename from frontend/_app/immutable/nodes/16.CKya8A82.js
rename to frontend-backup/_app/immutable/nodes/16.CKya8A82.js
diff --git a/frontend/_app/immutable/nodes/17.C45_aAtw.js b/frontend-backup/_app/immutable/nodes/17.C45_aAtw.js
similarity index 100%
rename from frontend/_app/immutable/nodes/17.C45_aAtw.js
rename to frontend-backup/_app/immutable/nodes/17.C45_aAtw.js
diff --git a/frontend/_app/immutable/nodes/18.WvT7vRmm.js b/frontend-backup/_app/immutable/nodes/18.WvT7vRmm.js
similarity index 100%
rename from frontend/_app/immutable/nodes/18.WvT7vRmm.js
rename to frontend-backup/_app/immutable/nodes/18.WvT7vRmm.js
diff --git a/frontend/_app/immutable/nodes/19.Dqy7C9y2.js b/frontend-backup/_app/immutable/nodes/19.Dqy7C9y2.js
similarity index 100%
rename from frontend/_app/immutable/nodes/19.Dqy7C9y2.js
rename to frontend-backup/_app/immutable/nodes/19.Dqy7C9y2.js
diff --git a/frontend/_app/immutable/nodes/2.BY7SdjrD.js b/frontend-backup/_app/immutable/nodes/2.BY7SdjrD.js
similarity index 100%
rename from frontend/_app/immutable/nodes/2.BY7SdjrD.js
rename to frontend-backup/_app/immutable/nodes/2.BY7SdjrD.js
diff --git a/frontend/_app/immutable/nodes/2.DTTH4yjc.js b/frontend-backup/_app/immutable/nodes/2.DTTH4yjc.js
similarity index 100%
rename from frontend/_app/immutable/nodes/2.DTTH4yjc.js
rename to frontend-backup/_app/immutable/nodes/2.DTTH4yjc.js
diff --git a/frontend/_app/immutable/nodes/20.ppFj_8Kx.js b/frontend-backup/_app/immutable/nodes/20.ppFj_8Kx.js
similarity index 100%
rename from frontend/_app/immutable/nodes/20.ppFj_8Kx.js
rename to frontend-backup/_app/immutable/nodes/20.ppFj_8Kx.js
diff --git a/frontend/_app/immutable/nodes/21.PUjACzZY.js b/frontend-backup/_app/immutable/nodes/21.PUjACzZY.js
similarity index 100%
rename from frontend/_app/immutable/nodes/21.PUjACzZY.js
rename to frontend-backup/_app/immutable/nodes/21.PUjACzZY.js
diff --git a/frontend/_app/immutable/nodes/3.BjOx-5ND.js b/frontend-backup/_app/immutable/nodes/3.BjOx-5ND.js
similarity index 100%
rename from frontend/_app/immutable/nodes/3.BjOx-5ND.js
rename to frontend-backup/_app/immutable/nodes/3.BjOx-5ND.js
diff --git a/frontend/_app/immutable/nodes/4.DLrwqUeR.js b/frontend-backup/_app/immutable/nodes/4.DLrwqUeR.js
similarity index 100%
rename from frontend/_app/immutable/nodes/4.DLrwqUeR.js
rename to frontend-backup/_app/immutable/nodes/4.DLrwqUeR.js
diff --git a/frontend/_app/immutable/nodes/5.lvNarnfM.js b/frontend-backup/_app/immutable/nodes/5.lvNarnfM.js
similarity index 100%
rename from frontend/_app/immutable/nodes/5.lvNarnfM.js
rename to frontend-backup/_app/immutable/nodes/5.lvNarnfM.js
diff --git a/frontend/_app/immutable/nodes/6.DyKsgUf2.js b/frontend-backup/_app/immutable/nodes/6.DyKsgUf2.js
similarity index 100%
rename from frontend/_app/immutable/nodes/6.DyKsgUf2.js
rename to frontend-backup/_app/immutable/nodes/6.DyKsgUf2.js
diff --git a/frontend/_app/immutable/nodes/7.C4jrLY7T.js b/frontend-backup/_app/immutable/nodes/7.C4jrLY7T.js
similarity index 100%
rename from frontend/_app/immutable/nodes/7.C4jrLY7T.js
rename to frontend-backup/_app/immutable/nodes/7.C4jrLY7T.js
diff --git a/frontend/_app/immutable/nodes/8.DIMn846h.js b/frontend-backup/_app/immutable/nodes/8.DIMn846h.js
similarity index 100%
rename from frontend/_app/immutable/nodes/8.DIMn846h.js
rename to frontend-backup/_app/immutable/nodes/8.DIMn846h.js
diff --git a/frontend/_app/immutable/nodes/9.BhPlDH9q.js b/frontend-backup/_app/immutable/nodes/9.BhPlDH9q.js
similarity index 100%
rename from frontend/_app/immutable/nodes/9.BhPlDH9q.js
rename to frontend-backup/_app/immutable/nodes/9.BhPlDH9q.js
diff --git a/frontend-backup/_app/version.json b/frontend-backup/_app/version.json
new file mode 100644
index 0000000..23c16ca
--- /dev/null
+++ b/frontend-backup/_app/version.json
@@ -0,0 +1,3 @@
+{
+ "version": "1759175263375"
+}
\ No newline at end of file
diff --git a/frontend/admin/index.html b/frontend-backup/admin/index.html
similarity index 100%
rename from frontend/admin/index.html
rename to frontend-backup/admin/index.html
diff --git a/frontend-backup/css2.css b/frontend-backup/css2.css
new file mode 100644
index 0000000..9bbf474
--- /dev/null
+++ b/frontend-backup/css2.css
@@ -0,0 +1,108 @@
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3CWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3mWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm36WWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3KWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3OWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm32WWpCBC10.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
diff --git a/frontend-backup/download.png b/frontend-backup/download.png
new file mode 100644
index 0000000..83d23f6
Binary files /dev/null and b/frontend-backup/download.png differ
diff --git a/frontend-backup/download.svg b/frontend-backup/download.svg
new file mode 100644
index 0000000..751b034
--- /dev/null
+++ b/frontend-backup/download.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/frontend-backup/favicon.ico b/frontend-backup/favicon.ico
new file mode 100644
index 0000000..be576b1
Binary files /dev/null and b/frontend-backup/favicon.ico differ
diff --git a/frontend-backup/img/og-image.png b/frontend-backup/img/og-image.png
new file mode 100644
index 0000000..02eb56e
Binary files /dev/null and b/frontend-backup/img/og-image.png differ
diff --git a/frontend-backup/img/web-app-manifest-192x192.png b/frontend-backup/img/web-app-manifest-192x192.png
new file mode 100644
index 0000000..a6fddf3
Binary files /dev/null and b/frontend-backup/img/web-app-manifest-192x192.png differ
diff --git a/frontend-backup/index.html b/frontend-backup/index.html
new file mode 100644
index 0000000..8bb0fbe
--- /dev/null
+++ b/frontend-backup/index.html
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ openplace - Paint the world
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend-backup/login.html b/frontend-backup/login.html
new file mode 100644
index 0000000..bc7557e
--- /dev/null
+++ b/frontend-backup/login.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ Login - openplace
+
+
+
+
+
+
+
+
diff --git a/frontend-backup/maps/styles/fiord b/frontend-backup/maps/styles/fiord
new file mode 100644
index 0000000..1b1762c
--- /dev/null
+++ b/frontend-backup/maps/styles/fiord
@@ -0,0 +1,2871 @@
+{
+ "version": 8,
+ "sources": {
+ "ne2_shaded": {
+ "maxzoom": 6,
+ "tileSize": 256,
+ "tiles": [
+ "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"
+ ],
+ "type": "raster"
+ },
+ "openmaptiles": {
+ "type": "vector",
+ "url": "https://tiles.openfreemap.org/planet"
+ }
+ },
+ "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
+ "glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#45516E"
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "#38435C"
+ }
+ },
+ {
+ "id": "landcover_ice_shelf",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "maxzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "subclass"
+ ],
+ "ice_shelf"
+ ]
+ ],
+ "paint": {
+ "fill-color": "hsl(232,33%,34%)",
+ "fill-opacity": 0.4
+ }
+ },
+ {
+ "id": "landuse_residential",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "maxzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "subclass"
+ ],
+ "residential"
+ ]
+ ],
+ "paint": {
+ "fill-color": "rgb(234, 234, 230)",
+ "fill-opacity": [
+ "interpolate",
+ [
+ "exponential",
+ 0.6
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 0.8,
+ 9,
+ 0.6
+ ]
+ }
+ },
+ {
+ "id": "landcover_wood",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "minzoom": 10,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "wood"
+ ]
+ ],
+ "paint": {
+ "fill-color": "hsla(232,18%,30%,0.57)",
+ "fill-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 9,
+ 0,
+ 12,
+ 1
+ ]
+ }
+ },
+ {
+ "id": "park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": "hsl(204,17%,35%)",
+ "fill-opacity": 0.3
+ }
+ },
+ {
+ "id": "park_outline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "line-color": "hsl(205,49%,31%)",
+ "line-dasharray": [
+ 2,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "line-color": "hsl(232,23%,28%)",
+ "line-opacity": 0.6
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "minzoom": 12,
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "hsla(232,47%,18%,0.65)",
+ "fill-opacity": 0.25
+ }
+ },
+ {
+ "id": "tunnel_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#3C4357",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 5.8,
+ 0,
+ 6,
+ 3,
+ 20,
+ 40
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_inner",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,18%,21%)",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 2,
+ 6,
+ 1.3,
+ 20,
+ 30
+ ]
+ }
+ },
+ {
+ "id": "aeroway-taxiway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 12,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "taxiway"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.55
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 1.8,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "aeroway-runway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "runway"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.5
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 6,
+ 17,
+ 55
+ ]
+ }
+ },
+ {
+ "id": "aeroway-area",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "runway",
+ "taxiway"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "fill-color": "hsl(224,20%,29%)",
+ "fill-opacity": 1
+ }
+ },
+ {
+ "id": "aeroway-runway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "maxzoom": 24,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "runway"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,20%,29%)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.5
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 4,
+ 17,
+ 50
+ ]
+ }
+ },
+ {
+ "id": "road_area_pier",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "pier"
+ ]
+ ],
+ "paint": {
+ "fill-antialias": true,
+ "fill-color": "#45516E"
+ }
+ },
+ {
+ "id": "road_pier",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "pier"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#45516E",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 17,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway_path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "path"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(211,29%,38%)",
+ "line-dasharray": [
+ 2,
+ 2
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway_minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor",
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-opacity": 0.9,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.55
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 1.8,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "highway_major_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-dasharray": [
+ 12,
+ 0
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 10,
+ 3,
+ 20,
+ 23
+ ]
+ }
+ },
+ {
+ "id": "highway_major_inner",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#3C4357",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 10,
+ 2,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "highway_major_subtle",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "maxzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#3D4355",
+ "line-opacity": 0.6,
+ "line-width": 2
+ }
+ },
+ {
+ "id": "highway_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-dasharray": [
+ 2,
+ 0
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 5.8,
+ 0,
+ 6,
+ 3,
+ 20,
+ 40
+ ]
+ }
+ },
+ {
+ "id": "highway_motorway_inner",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 5.8,
+ "hsla(0,0%,85%,0.53)",
+ 6,
+ "hsl(224,20%,29%)"
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 2,
+ 6,
+ 1.3,
+ 20,
+ 30
+ ]
+ }
+ },
+ {
+ "id": "highway_motorway_subtle",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "maxzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsla(239,45%,69%,0.2)",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 2,
+ 6,
+ 1.3
+ ]
+ }
+ },
+ {
+ "id": "railway_transit",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "tunnel"
+ ],
+ false,
+ true
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(200,65%,11%)",
+ "line-width": 3
+ }
+ },
+ {
+ "id": "railway_transit_dashline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "tunnel"
+ ],
+ false,
+ true
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(193,63%,26%)",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-width": 2
+ }
+ },
+ {
+ "id": "railway_service",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "has",
+ "service"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(200,65%,11%)",
+ "line-width": 3
+ }
+ },
+ {
+ "id": "railway_service_dashline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "has",
+ "service"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(193,63%,26%)",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-width": 2
+ }
+ },
+ {
+ "id": "railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "!",
+ [
+ "has",
+ "service"
+ ]
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(200,10%,18%)",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 16,
+ 3,
+ 20,
+ 7
+ ]
+ }
+ },
+ {
+ "id": "railway_dashline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "!",
+ [
+ "has",
+ "service"
+ ]
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,20%,41%)",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 16,
+ 1.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "water_name",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 500,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "hsl(223,21%,52%)",
+ "text-halo-blur": 0,
+ "text-halo-color": "hsl(232,5%,19%)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway_name_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "filter": [
+ "all",
+ [
+ "!=",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-angle": 30,
+ "text-pitch-alignment": "viewport",
+ "text-rotation-alignment": "map",
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(223,31%,61%)",
+ "text-halo-blur": 0,
+ "text-halo-color": "hsl(232,9%,23%)",
+ "text-halo-width": 2,
+ "text-opacity": 1,
+ "text-translate": [
+ 0,
+ 0
+ ]
+ }
+ },
+ {
+ "id": "highway_ref",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-pitch-alignment": "viewport",
+ "text-rotation-alignment": "viewport",
+ "text-size": 10,
+ "visibility": "none"
+ },
+ "paint": {
+ "text-color": "hsl(215,57%,77%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsl(209,64%,19%)",
+ "text-halo-width": 1,
+ "text-opacity": 1,
+ "text-translate": [
+ 0,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "boundary_state",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 4
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-blur": 0.4,
+ "line-color": "hsla(195,47%,62%,0.26)",
+ "line-dasharray": [
+ 2,
+ 2
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 22,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "boundary_country_z0-4",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "maxzoom": 5,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 2
+ ],
+ [
+ "!",
+ [
+ "has",
+ "claimed_by"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-blur": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.4,
+ 22,
+ 4
+ ],
+ "line-color": "hsl(214,63%,76%)",
+ "line-opacity": 0.56,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.1
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 22,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "boundary_country_z5-",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "minzoom": 5,
+ "filter": [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 2
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-blur": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.4,
+ 22,
+ 4
+ ],
+ "line-color": "hsl(214,63%,76%)",
+ "line-opacity": 0.56,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.1
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 22,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "place_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "hamlet",
+ "isolated_dwelling",
+ "neighbourhood"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "center",
+ "text-offset": [
+ 0.5,
+ 0
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(195,37%,73%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1,
+ "text-opacity": 0.6
+ }
+ },
+ {
+ "id": "place_suburb",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "suburb"
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "center",
+ "text-offset": [
+ 0.5,
+ 0
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(195,41%,49%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_village",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "village"
+ ]
+ ],
+ "layout": {
+ "icon-size": 0.4,
+ "text-anchor": "left",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,41%,49%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_town",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "town"
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle-11",
+ 9,
+ ""
+ ],
+ "icon-size": 0.4,
+ "text-anchor": [
+ "step",
+ [
+ "zoom"
+ ],
+ "left",
+ 8,
+ "center"
+ ],
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,25%,76%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_city",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ],
+ [
+ "\u003E",
+ [
+ "get",
+ "rank"
+ ],
+ 3
+ ]
+ ]
+ ],
+ "layout": {
+ "icon-size": 0.4,
+ "text-anchor": [
+ "step",
+ [
+ "zoom"
+ ],
+ "left",
+ 8,
+ "center"
+ ],
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,25%,76%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_city_large",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "\u003C=",
+ [
+ "get",
+ "rank"
+ ],
+ 3
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ]
+ ]
+ ],
+ "layout": {
+ "icon-size": 0.4,
+ "text-anchor": [
+ "step",
+ [
+ "zoom"
+ ],
+ "left",
+ 8,
+ "center"
+ ],
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 14,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,25%,76%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_state",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "state"
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "rgb(113, 129, 144)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_country_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "!",
+ [
+ "has",
+ "iso_a2"
+ ]
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 9,
+ 6,
+ 11
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ "rgb(157,169,177)",
+ 4,
+ "rgb(153, 153, 153)"
+ ],
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": 1
+ }
+ },
+ {
+ "id": "place_country_minor",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "\u003E=",
+ [
+ "get",
+ "rank"
+ ],
+ 2
+ ],
+ [
+ "has",
+ "iso_a2"
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 6,
+ 12
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ "rgb(157,169,177)",
+ 4,
+ "rgb(153, 153, 153)"
+ ],
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": 1
+ }
+ },
+ {
+ "id": "place_country_major",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "\u003C=",
+ [
+ "get",
+ "rank"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "has",
+ "iso_a2"
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 3,
+ 12,
+ 4,
+ 14
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ "rgb(157,169,177)",
+ 4,
+ "rgb(153, 153, 153)"
+ ],
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": 1
+ }
+ },
+ {
+ "id": "place_continent",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "continent"
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 3,
+ 12,
+ 4,
+ 14
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(0,0%,100%)",
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.6,
+ 3,
+ 0
+ ]
+ }
+ }
+ ]
+}
diff --git a/frontend-backup/maps/styles/liberty b/frontend-backup/maps/styles/liberty
new file mode 100644
index 0000000..a1922f6
--- /dev/null
+++ b/frontend-backup/maps/styles/liberty
@@ -0,0 +1,6034 @@
+{
+ "version": 8,
+ "sources": {
+ "ne2_shaded": {
+ "maxzoom": 6,
+ "tileSize": 256,
+ "tiles": [
+ "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"
+ ],
+ "type": "raster"
+ },
+ "openmaptiles": {
+ "type": "vector",
+ "url": "https://tiles.openfreemap.org/planet"
+ }
+ },
+ "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
+ "glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#f8f4f0"
+ }
+ },
+ {
+ "id": "natural_earth",
+ "type": "raster",
+ "source": "ne2_shaded",
+ "maxzoom": 7,
+ "paint": {
+ "raster-opacity": [
+ "interpolate",
+ [
+ "exponential",
+ 1.5
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.6,
+ 6,
+ 0.1
+ ]
+ }
+ },
+ {
+ "id": "park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "paint": {
+ "fill-color": "#d8e8c8",
+ "fill-opacity": 0.7,
+ "fill-outline-color": "rgba(95, 208, 100, 1)"
+ }
+ },
+ {
+ "id": "park_outline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "paint": {
+ "line-color": "rgba(228, 241, 215, 1)",
+ "line-dasharray": [
+ 1,
+ 1.5
+ ]
+ }
+ },
+ {
+ "id": "landuse_residential",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "maxzoom": 12,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "residential"
+ ],
+ "paint": {
+ "fill-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 9,
+ "hsla(0,3%,85%,0.84)",
+ 12,
+ "hsla(35,57%,88%,0.49)"
+ ]
+ }
+ },
+ {
+ "id": "landcover_wood",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "wood"
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "hsla(98,61%,72%,0.7)",
+ "fill-opacity": 0.4
+ }
+ },
+ {
+ "id": "landcover_grass",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "grass"
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "rgba(176, 213, 154, 1)",
+ "fill-opacity": 0.3
+ }
+ },
+ {
+ "id": "landcover_ice",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "ice"
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "rgba(224, 236, 236, 1)",
+ "fill-opacity": 0.8
+ }
+ },
+ {
+ "id": "landcover_wetland",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "minzoom": 12,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "wetland"
+ ],
+ "paint": {
+ "fill-antialias": true,
+ "fill-opacity": 0.8,
+ "fill-pattern": "wetland_bg_11",
+ "fill-translate-anchor": "map"
+ }
+ },
+ {
+ "id": "landuse_pitch",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "pitch"
+ ],
+ "paint": {
+ "fill-color": "#DEE3CD"
+ }
+ },
+ {
+ "id": "landuse_track",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "track"
+ ],
+ "paint": {
+ "fill-color": "#DEE3CD"
+ }
+ },
+ {
+ "id": "landuse_cemetery",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "cemetery"
+ ],
+ "paint": {
+ "fill-color": "hsl(75,37%,81%)"
+ }
+ },
+ {
+ "id": "landuse_hospital",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "hospital"
+ ],
+ "paint": {
+ "fill-color": "#fde"
+ }
+ },
+ {
+ "id": "landuse_school",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "school"
+ ],
+ "paint": {
+ "fill-color": "rgb(236,238,204)"
+ }
+ },
+ {
+ "id": "waterway_tunnel",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-gap-width": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 20,
+ 6
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway_river",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "river"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round"
+ },
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway_other",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ [
+ "!=",
+ [
+ "get",
+ "class"
+ ],
+ "river"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round"
+ },
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": [
+ "!=",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ "paint": {
+ "fill-color": "rgb(158,189,255)"
+ }
+ },
+ {
+ "id": "landcover_sand",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "sand"
+ ],
+ "paint": {
+ "fill-color": "rgba(247, 239, 195, 1)"
+ }
+ },
+ {
+ "id": "aeroway_fill",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": "rgba(229, 228, 224, 1)",
+ "fill-opacity": 0.7
+ }
+ },
+ {
+ "id": "aeroway_runway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "runway"
+ ]
+ ],
+ "paint": {
+ "line-color": "#f0ede9",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 3,
+ 20,
+ 16
+ ]
+ }
+ },
+ {
+ "id": "aeroway_taxiway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "taxiway"
+ ]
+ ],
+ "paint": {
+ "line-color": "#f0ede9",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [
+ 0.5,
+ 0.25
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel_service_track_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-dasharray": [
+ 0.5,
+ 0.25
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "tunnel_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel_street_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "street",
+ "street_limited"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 12.5,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel_secondary_tertiary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "tunnel_trunk_primary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [
+ 0.5,
+ 0.25
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel_path_pedestrian",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,100%)",
+ "line-dasharray": [
+ 1,
+ 0.75
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_service_track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_secondary_tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "tunnel_trunk_primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#ffdaa6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel_major_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "rail"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "tunnel_major_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "tunnel_transit_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "transit"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "tunnel_transit_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "road_area_pattern",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-pattern": "pedestrian_polygon"
+ }
+ },
+ {
+ "id": "road_motorway_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "road_service_track_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "road_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "motorway",
+ "path",
+ "pedestrian",
+ "service",
+ "track"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "road_minor_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 12.5,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "road_secondary_tertiary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "road_trunk_primary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "road_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "road_path_pedestrian",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(0,0%,100%)",
+ "line-dasharray": [
+ 1,
+ 0.7
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 1,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "road_motorway_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "road_service_track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "road_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "motorway",
+ "path",
+ "pedestrian",
+ "service",
+ "track"
+ ],
+ false,
+ true
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "road_minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "road_secondary_tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 6.5,
+ 0,
+ 8,
+ 0.5,
+ 20,
+ 13
+ ]
+ }
+ },
+ {
+ "id": "road_trunk_primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "road_motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ "hsl(26,87%,62%)",
+ 6,
+ "#fc8"
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "road_major_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "road_major_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "road_transit_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "road_transit_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "road_one_way_arrow",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "==",
+ [
+ "get",
+ "oneway"
+ ],
+ 1
+ ],
+ "layout": {
+ "icon-image": "arrow",
+ "symbol-placement": "line"
+ }
+ },
+ {
+ "id": "road_one_way_arrow_opposite",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "==",
+ [
+ "get",
+ "oneway"
+ ],
+ -1
+ ],
+ "layout": {
+ "icon-image": "arrow",
+ "icon-rotate": 180,
+ "symbol-placement": "line"
+ }
+ },
+ {
+ "id": "bridge_motorway_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "bridge_service_track_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "bridge_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "link"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "bridge_street_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "street",
+ "street_limited"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(36,6%,74%)",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 12.5,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 25
+ ]
+ }
+ },
+ {
+ "id": "bridge_path_pedestrian_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(35,6%,80%)",
+ "line-dasharray": [
+ 1,
+ 0
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 1.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_secondary_tertiary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "bridge_trunk_primary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "bridge_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "bridge_path_pedestrian",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,100%)",
+ "line-dasharray": [
+ 1,
+ 0.3
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "bridge_motorway_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge_service_track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "bridge_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "link"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge_street",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_secondary_tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "bridge_trunk_primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_major_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "bridge_major_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "bridge_transit_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "bridge_transit_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "minzoom": 13,
+ "maxzoom": 14,
+ "paint": {
+ "fill-color": "hsl(35,8%,85%)",
+ "fill-outline-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ "hsla(35,6%,79%,0.32)",
+ 14,
+ "hsl(35,6%,79%)"
+ ]
+ }
+ },
+ {
+ "id": "building-3d",
+ "type": "fill-extrusion",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "minzoom": 14,
+ "paint": {
+ "fill-extrusion-base": [
+ "get",
+ "render_min_height"
+ ],
+ "fill-extrusion-color": "hsl(35,8%,85%)",
+ "fill-extrusion-height": [
+ "get",
+ "render_height"
+ ],
+ "fill-extrusion-opacity": 0.8
+ }
+ },
+ {
+ "id": "boundary_3",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ ">=",
+ [
+ "get",
+ "admin_level"
+ ],
+ 3
+ ],
+ [
+ "<=",
+ [
+ "get",
+ "admin_level"
+ ],
+ 6
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "maritime"
+ ],
+ 1
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "disputed"
+ ],
+ 1
+ ],
+ [
+ "!",
+ [
+ "has",
+ "claimed_by"
+ ]
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-dasharray": [
+ 1,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "linear",
+ 1
+ ],
+ [
+ "zoom"
+ ],
+ 7,
+ 1,
+ 11,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "boundary_2",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 2
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "maritime"
+ ],
+ 1
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "disputed"
+ ],
+ 1
+ ],
+ [
+ "!",
+ [
+ "has",
+ "claimed_by"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(248,1%,41%)",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.4,
+ 4,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 5,
+ 1.2,
+ 12,
+ 3
+ ]
+ }
+ },
+ {
+ "id": "boundary_disputed",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ [
+ "!=",
+ [
+ "get",
+ "maritime"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "disputed"
+ ],
+ 1
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(248,1%,41%)",
+ "line-dasharray": [
+ 1,
+ 2
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 5,
+ 1.2,
+ 12,
+ 3
+ ]
+ }
+ },
+ {
+ "id": "waterway_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "minzoom": 10,
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#74aee9",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_point_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 8,
+ 14
+ ]
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "poi_r20",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 17,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 20
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "florist",
+ "furniture"
+ ],
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_r7",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 7
+ ],
+ [
+ "<",
+ [
+ "get",
+ "rank"
+ ],
+ 20
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "florist",
+ "furniture"
+ ],
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_r1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 1
+ ],
+ [
+ "<",
+ [
+ "get",
+ "rank"
+ ],
+ 7
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "florist",
+ "furniture"
+ ],
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_transit",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "airport",
+ "bus",
+ "rail"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "icon-image": [
+ "to-string",
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "icon-size": 0.7,
+ "text-anchor": "left",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0.9,
+ 0
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#2e5a80",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-path",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15.5,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "path"
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 12,
+ 14,
+ 13
+ ]
+ },
+ "paint": {
+ "text-color": "hsl(30,23%,62%)",
+ "text-halo-color": "#f8f4f0",
+ "text-halo-width": 0.5
+ }
+ },
+ {
+ "id": "highway-name-minor",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor",
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 12,
+ 14,
+ 13
+ ]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-major",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 12.2,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 12,
+ 14,
+ 13
+ ]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-shield-non-us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ [
+ "<=",
+ [
+ "get",
+ "ref_length"
+ ],
+ 6
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "network"
+ ],
+ [
+ "us-highway",
+ "us-interstate",
+ "us-state"
+ ],
+ false,
+ true
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ "road_",
+ [
+ "get",
+ "ref_length"
+ ]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": [
+ "step",
+ [
+ "zoom"
+ ],
+ "point",
+ 11,
+ "line"
+ ],
+ "symbol-spacing": 200,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "highway-shield-us-interstate",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 7,
+ "filter": [
+ "all",
+ [
+ "<=",
+ [
+ "get",
+ "ref_length"
+ ],
+ 6
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "network"
+ ],
+ [
+ "us-interstate"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ [
+ "get",
+ "network"
+ ],
+ "_",
+ [
+ "get",
+ "ref_length"
+ ]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": [
+ "step",
+ [
+ "zoom"
+ ],
+ "point",
+ 7,
+ "line",
+ 8,
+ "line"
+ ],
+ "symbol-spacing": 200,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "road_shield_us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 9,
+ "filter": [
+ "all",
+ [
+ "<=",
+ [
+ "get",
+ "ref_length"
+ ],
+ 6
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "network"
+ ],
+ [
+ "us-highway",
+ "us-state"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ [
+ "get",
+ "network"
+ ],
+ "_",
+ [
+ "get",
+ "ref_length"
+ ]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": [
+ "step",
+ [
+ "zoom"
+ ],
+ "point",
+ 11,
+ "line"
+ ],
+ "symbol-spacing": 200,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "airport",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "aerodrome_label",
+ "minzoom": 10,
+ "filter": [
+ "all",
+ [
+ "has",
+ "iata"
+ ]
+ ],
+ "layout": {
+ "icon-image": "airport_11",
+ "icon-size": 1,
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-optional": true,
+ "text-padding": 2,
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 8,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "city",
+ "continent",
+ "country",
+ "state",
+ "town",
+ "village"
+ ],
+ false,
+ true
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.1,
+ "text-max-width": 9,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 9,
+ 12,
+ 10
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_village",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 9,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "village"
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 10,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 7,
+ 10,
+ 11,
+ 12
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_town",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 6,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "town"
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 10,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 7,
+ 12,
+ 11,
+ 14
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_state",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 5,
+ "maxzoom": 8,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "state"
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 9,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 10,
+ 8,
+ 14
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "capital"
+ ],
+ 2
+ ]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 9,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.4,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 8,
+ "text-offset": [
+ 0,
+ -0.1
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 11,
+ 7,
+ 13,
+ 11,
+ 18
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city_capital",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "capital"
+ ],
+ 2
+ ]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 9,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.5,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 8,
+ "text-offset": [
+ 0,
+ -0.2
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 12,
+ 7,
+ 14,
+ 11,
+ 20
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_3",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 2,
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 3
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 6.25,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 9,
+ 7,
+ 17
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_2",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "rank"
+ ],
+ 2
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 6.25,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 2,
+ 9,
+ 5,
+ 17
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "rank"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 6.25,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 1,
+ 9,
+ 4,
+ 17
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ }
+ ]
+}
diff --git a/frontend/moderation/index.html b/frontend-backup/moderation/index.html
similarity index 100%
rename from frontend/moderation/index.html
rename to frontend-backup/moderation/index.html
diff --git a/frontend-backup/service-worker.js b/frontend-backup/service-worker.js
new file mode 100644
index 0000000..21e1622
--- /dev/null
+++ b/frontend-backup/service-worker.js
@@ -0,0 +1,705 @@
+const ae = "files",
+ a = location.pathname.split("/").slice(0, -1).join("/"),
+ ne = [a + "/_app/immutable/entry/app.iDaujbEI.js", a + "/_app/immutable/nodes/0.CnnlsrhC.js", a + "/_app/immutable/assets/0.CmqRY0au.css", a + "/_app/immutable/assets/Geist-cyrillic.CHSlOQsW.woff2", a + "/_app/immutable/assets/Geist-latin-ext.DMtmJ5ZE.woff2", a + "/_app/immutable/assets/Geist-latin.Dg_dQHbK.woff2", a + "/_app/immutable/assets/GeistMono-cyrillic.BZdD_g9V.woff2", a + "/_app/immutable/assets/GeistMono-latin-ext.b6lpi8_2.woff2", a + "/_app/immutable/assets/GeistMono-latin.Cjtb1TV-.woff2", a + "/_app/immutable/assets/PixelifySans-cyrillic.CPPz0Qvd.woff2", a + "/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2", a + "/_app/immutable/assets/NotoColorEmoji-flags.ClvgErYz.woff2", a + "/_app/immutable/assets/flags.a2kmUSbF.webp", a + "/_app/immutable/assets/flags@2x.gR6KPp3x.webp", a + "/_app/immutable/nodes/1.DpC5h7KA.js", a + "/_app/immutable/nodes/10.C07JyVXo.js", a + "/_app/immutable/nodes/11.BVmrEev1.js", a + "/_app/immutable/assets/9.BD1hRFPA.css", a + "/_app/immutable/nodes/2.BY7SdjrD.js", a + "/_app/immutable/assets/2.BtKF873c.css", a + "/_app/immutable/nodes/3.DVSEiJTt.js", a + "/_app/immutable/nodes/4.CeYpVeIo.js", a + "/_app/immutable/nodes/5.CXeQMqhf.js", a + "/_app/immutable/nodes/6.DD7Zmm97.js", a + "/_app/immutable/nodes/7.DDuBPi09.js", a + "/_app/immutable/nodes/8.B8sOtsfv.js", a + "/_app/immutable/nodes/9.BQE9fbrM.js", a + "/_app/immutable/chunks/07L1R_bo.js", a + "/_app/immutable/chunks/1lh-LSvX.js", a + "/_app/immutable/chunks/1mTheT_N.js", a + "/_app/immutable/chunks/2CRhGZHc.js", a + "/_app/immutable/chunks/5NasrULQ.js", a + "/_app/immutable/chunks/B1GmkH4o.js", a + "/_app/immutable/chunks/BMKgGW48.js", a + "/_app/immutable/chunks/BtP6pfnb.js", a + "/_app/immutable/chunks/ByKBPM-D.js", a + "/_app/immutable/chunks/Bzak7iHL.js", a + "/_app/immutable/chunks/C5GsJ62f.js", a + "/_app/immutable/chunks/CBqzI9hL.js", a + "/_app/immutable/assets/ProfileAvatarWithLevel.6dmPRSfx.css", a + "/_app/immutable/chunks/CMs8vKjq.js", a + "/_app/immutable/chunks/CQklNc9N.js", a + "/_app/immutable/assets/LoginForm.CxMG0irz.css", a + "/_app/immutable/chunks/CeLr1p76.js", a + "/_app/immutable/chunks/Cp3o644A.js", a + "/_app/immutable/chunks/D1ivTjwA.js", a + "/_app/immutable/chunks/D2m5UD3G.js", a + "/_app/immutable/assets/notification.CPyrWqU1.mp3", a + "/_app/immutable/chunks/D35KiPL1.js", a + "/_app/immutable/chunks/DUoKDNpf.js", a + "/_app/immutable/chunks/DkBFL3pa.js", a + "/_app/immutable/chunks/Dp1pzeXC.js", a + "/_app/immutable/chunks/DsJqb9ei.js", a + "/_app/immutable/chunks/F0pgzfyy.js", a + "/_app/immutable/chunks/KvV259my.js", a + "/_app/immutable/chunks/U908S-6f.js", a + "/_app/immutable/chunks/Y9es74tr.js", a + "/_app/immutable/chunks/g8c1BvYP.js", a + "/_app/immutable/entry/start.CJ_UwIBa.js", a + "/_app/immutable/chunks/1FgtjJRR.js"],
+ ie = [a + "/.well-known/security.txt", a + "/26/2025/08/12/horse.png", a + "/favicon.ico", a + "/img/apple-touch-icon.png", a + "/img/favicon-96x96.png", a + "/img/logo-512x512.png", a + "/img/logo.png", a + "/img/og-image-mobile.png", a + "/img/og-image.png", a + "/img/pwa-country-leaderboard-mobile.png", a + "/img/pwa-kiev-mobile.png", a + "/img/pwa-paint-heart-mobile.png", a + "/img/pwa-void-mobile.png", a + "/img/web-app-manifest-192x192.png", a + "/img/web-app-manifest-512x512.png", a + "/site.webmanifest"],
+ oe = "1756230503892";
+let r;
+const J = typeof TextDecoder < "u" ? new TextDecoder("utf-8", {
+ ignoreBOM: !0,
+ fatal: !0
+}) : {
+ decode: () => {
+ throw Error("TextDecoder not available")
+ }
+};
+typeof TextDecoder < "u" && J.decode();
+let S = null;
+
+function K() {
+ return (S === null || S.byteLength === 0) && (S = new Uint8Array(r.memory.buffer)), S
+}
+
+function te(e, n) {
+ return e = e >>> 0, J.decode(K().subarray(e, e + n))
+}
+let C = null;
+
+function de() {
+ return (C === null || C.byteLength === 0) && (C = new Uint8ClampedArray(r.memory.buffer)), C
+}
+
+function le(e, n) {
+ return e = e >>> 0, de().subarray(e / 1, e / 1 + n)
+}
+const b = new Array(128).fill(void 0);
+b.push(void 0, null, !0, !1);
+let D = b.length;
+
+function se(e) {
+ D === b.length && b.push(b.length + 1);
+ const n = D;
+ return D = b[n], b[n] = e, n
+}
+let U = 0;
+
+function q(e, n) {
+ const i = n(e.length * 1, 1) >>> 0;
+ return K().set(e, i / 1), U = e.length, i
+}
+let M = null;
+
+function H() {
+ return (M === null || M.byteLength === 0) && (M = new Int32Array(r.memory.buffer)), M
+}
+
+function ce(e, n) {
+ return e = e >>> 0, K().subarray(e / 1, e / 1 + n)
+}
+
+function re(e, n, i) {
+ try {
+ const m = r.__wbindgen_add_to_stack_pointer(-16),
+ y = q(e, r.__wbindgen_malloc),
+ t = U;
+ r.encode(m, y, t, n, i);
+ var l = H()[m / 4 + 0],
+ s = H()[m / 4 + 1],
+ u = ce(l, s).slice();
+ return r.__wbindgen_free(l, s * 1, 1), u
+ } finally {
+ r.__wbindgen_add_to_stack_pointer(16)
+ }
+}
+
+function me(e) {
+ return b[e]
+}
+
+function ge(e) {
+ e < 132 || (b[e] = D, D = e)
+}
+
+function fe(e) {
+ const n = me(e);
+ return ge(e), n
+}
+
+function ue(e) {
+ const n = q(e, r.__wbindgen_malloc),
+ i = U,
+ l = r.decode(n, i);
+ return fe(l)
+}
+async function pe(e, n) {
+ if (typeof Response == "function" && e instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming == "function") try {
+ return await WebAssembly.instantiateStreaming(e, n)
+ } catch (l) {
+ if (e.headers.get("Content-Type") != "application/wasm") console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", l);
+ else throw l
+ }
+ const i = await e.arrayBuffer();
+ return await WebAssembly.instantiate(i, n)
+ } else {
+ const i = await WebAssembly.instantiate(e, n);
+ return i instanceof WebAssembly.Instance ? {
+ instance: i,
+ module: e
+ } : i
+ }
+}
+
+function be() {
+ const e = {};
+ return e.wbg = {}, e.wbg.__wbg_newwithownedu8clampedarrayandsh_91db5987993a08fb = function(n, i, l, s) {
+ var u = le(n, i).slice();
+ r.__wbindgen_free(n, i * 1, 1);
+ const m = new ImageData(u, l >>> 0, s >>> 0);
+ return se(m)
+ }, e.wbg.__wbindgen_throw = function(n, i) {
+ throw new Error(te(n, i))
+ }, e
+}
+
+function he(e, n) {
+ return r = e.exports, F.__wbindgen_wasm_module = n, M = null, S = null, C = null, r
+}
+async function F(e) {
+ if (r !== void 0) return r;
+ const n = be();
+ (typeof e == "string" || typeof Request == "function" && e instanceof Request || typeof URL == "function" && e instanceof URL) && (e = fetch(e));
+ const {
+ instance: i,
+ module: l
+ } = await pe(await e, n);
+ return he(i, l)
+}
+const we = globalThis.ServiceWorkerGlobalScope !== void 0,
+ ye = we && typeof self < "u" && globalThis.caches && globalThis.caches.default !== void 0,
+ _e = typeof process == "object" && process.release && process.release.name === "node";
+(ye || _e) && (globalThis.ImageData || (globalThis.ImageData = class {
+ constructor(n, i, l) {
+ this.data = n, this.width = i, this.height = l
+ }
+}), typeof self < "u" && self.location === void 0 && (self.location = {
+ href: ""
+}));
+let j;
+async function Se(e) {
+ return j || (j = F(e)), j
+}
+async function Ce(e) {
+ await Se();
+ const n = await ue(new Uint8Array(e));
+ if (!n) throw new Error("Encoding error.");
+ return n
+}
+let E;
+async function Y(e) {
+ return E || (E = F(e)), E
+}
+async function V(e) {
+ await Y();
+ const n = await re(e.data, e.width, e.height);
+ if (!n) throw new Error("Encoding error.");
+ return n.buffer
+}
+const Me = "" + new URL("_app/immutable/assets/squoosh_png_bg.BsfxGNEB.wasm", location.href).pathname;
+
+function z({
+ pixel: e,
+ season: n,
+ tile: i
+}) {
+ return `t=(${i[0]},${i[1]});p=(${e[0]},${e[1]});s=${n}`
+}
+const De = [{
+ tileSize: 1e3,
+ zoom: 11
+ }],
+ ke = 4,
+ Te = 6e3,
+ Be = [{
+ name: "Transparent",
+ rgb: [0, 0, 0]
+ }, {
+ name: "Black",
+ rgb: [0, 0, 0]
+ }, {
+ name: "Dark Gray",
+ rgb: [60, 60, 60]
+ }, {
+ name: "Gray",
+ rgb: [120, 120, 120]
+ }, {
+ name: "Light Gray",
+ rgb: [210, 210, 210]
+ }, {
+ name: "White",
+ rgb: [255, 255, 255]
+ }, {
+ name: "Deep Red",
+ rgb: [96, 0, 24]
+ }, {
+ name: "Red",
+ rgb: [237, 28, 36]
+ }, {
+ name: "Orange",
+ rgb: [255, 127, 39]
+ }, {
+ name: "Gold",
+ rgb: [246, 170, 9]
+ }, {
+ name: "Yellow",
+ rgb: [249, 221, 59]
+ }, {
+ name: "Light Yellow",
+ rgb: [255, 250, 188]
+ }, {
+ name: "Dark Green",
+ rgb: [14, 185, 104]
+ }, {
+ name: "Green",
+ rgb: [19, 230, 123]
+ }, {
+ name: "Light Green",
+ rgb: [135, 255, 94]
+ }, {
+ name: "Dark Teal",
+ rgb: [12, 129, 110]
+ }, {
+ name: "Teal",
+ rgb: [16, 174, 166]
+ }, {
+ name: "Light Teal",
+ rgb: [19, 225, 190]
+ }, {
+ name: "Dark Blue",
+ rgb: [40, 80, 158]
+ }, {
+ name: "Blue",
+ rgb: [64, 147, 228]
+ }, {
+ name: "Cyan",
+ rgb: [96, 247, 242]
+ }, {
+ name: "Indigo",
+ rgb: [107, 80, 246]
+ }, {
+ name: "Light Indigo",
+ rgb: [153, 177, 251]
+ }, {
+ name: "Dark Purple",
+ rgb: [120, 12, 153]
+ }, {
+ name: "Purple",
+ rgb: [170, 56, 185]
+ }, {
+ name: "Light Purple",
+ rgb: [224, 159, 249]
+ }, {
+ name: "Dark Pink",
+ rgb: [203, 0, 122]
+ }, {
+ name: "Pink",
+ rgb: [236, 31, 128]
+ }, {
+ name: "Light Pink",
+ rgb: [243, 141, 169]
+ }, {
+ name: "Dark Brown",
+ rgb: [104, 70, 52]
+ }, {
+ name: "Brown",
+ rgb: [149, 104, 42]
+ }, {
+ name: "Beige",
+ rgb: [248, 178, 119]
+ }, {
+ name: "Medium Gray",
+ rgb: [170, 170, 170]
+ }, {
+ name: "Dark Red",
+ rgb: [165, 14, 30]
+ }, {
+ name: "Light Red",
+ rgb: [250, 128, 114]
+ }, {
+ name: "Dark Orange",
+ rgb: [228, 92, 26]
+ }, {
+ name: "Light Tan",
+ rgb: [214, 181, 148]
+ }, {
+ name: "Dark Goldenrod",
+ rgb: [156, 132, 49]
+ }, {
+ name: "Goldenrod",
+ rgb: [197, 173, 49]
+ }, {
+ name: "Light Goldenrod",
+ rgb: [232, 212, 95]
+ }, {
+ name: "Dark Olive",
+ rgb: [74, 107, 58]
+ }, {
+ name: "Olive",
+ rgb: [90, 148, 74]
+ }, {
+ name: "Light Olive",
+ rgb: [132, 197, 115]
+ }, {
+ name: "Dark Cyan",
+ rgb: [15, 121, 159]
+ }, {
+ name: "Light Cyan",
+ rgb: [187, 250, 242]
+ }, {
+ name: "Light Blue",
+ rgb: [125, 199, 255]
+ }, {
+ name: "Dark Indigo",
+ rgb: [77, 49, 184]
+ }, {
+ name: "Dark Slate Blue",
+ rgb: [74, 66, 132]
+ }, {
+ name: "Slate Blue",
+ rgb: [122, 113, 196]
+ }, {
+ name: "Light Slate Blue",
+ rgb: [181, 174, 241]
+ }, {
+ name: "Light Brown",
+ rgb: [219, 164, 99]
+ }, {
+ name: "Dark Beige",
+ rgb: [209, 128, 81]
+ }, {
+ name: "Light Beige",
+ rgb: [255, 197, 165]
+ }, {
+ name: "Dark Peach",
+ rgb: [155, 82, 73]
+ }, {
+ name: "Peach",
+ rgb: [209, 128, 120]
+ }, {
+ name: "Light Peach",
+ rgb: [250, 182, 164]
+ }, {
+ name: "Dark Tan",
+ rgb: [123, 99, 82]
+ }, {
+ name: "Tan",
+ rgb: [156, 132, 107]
+ }, {
+ name: "Dark Slate",
+ rgb: [51, 57, 65]
+ }, {
+ name: "Slate",
+ rgb: [109, 117, 141]
+ }, {
+ name: "Light Slate",
+ rgb: [179, 185, 209]
+ }, {
+ name: "Dark Stone",
+ rgb: [109, 100, 63]
+ }, {
+ name: "Stone",
+ rgb: [148, 140, 107]
+ }, {
+ name: "Light Stone",
+ rgb: [205, 197, 158]
+ }],
+ Pe = {
+ needsPhoneVerification: "needs_phone_verification"
+ },
+ Ie = {
+ Droplet: {},
+ "Max. Charge": {},
+ "Paint Charge": {},
+ Color: {},
+ Flag: {},
+ "Profile Picture": {}
+ },
+ Ge = {
+ 10: {
+ name: "25,000 Droplets",
+ price: 500,
+ isDollar: !0,
+ lookupKey: "droplets_5",
+ items: [{
+ name: "Droplet",
+ amount: 25e3
+ }]
+ },
+ 20: {
+ name: "78,750 Droplets",
+ price: 1500,
+ isDollar: !0,
+ lookupKey: "droplets_15",
+ items: [{
+ name: "Droplet",
+ amount: 76750
+ }]
+ },
+ 30: {
+ name: "165,000 Droplets",
+ price: 3e3,
+ isDollar: !0,
+ lookupKey: "droplets_30",
+ items: [{
+ name: "Droplet",
+ amount: 165e3
+ }]
+ },
+ 40: {
+ name: "287,500 Droplets",
+ price: 5e3,
+ isDollar: !0,
+ lookupKey: "droplets_50",
+ items: [{
+ name: "Droplet",
+ amount: 287500
+ }]
+ },
+ 50: {
+ name: "450,000 Droplets",
+ price: 7500,
+ isDollar: !0,
+ lookupKey: "droplets_75",
+ items: [{
+ name: "Droplet",
+ amount: 45e4
+ }]
+ },
+ 60: {
+ name: "625,000 Droplets",
+ price: 1e4,
+ isDollar: !0,
+ lookupKey: "droplets_100",
+ items: [{
+ name: "Droplet",
+ amount: 625e3
+ }]
+ },
+ 70: {
+ name: "+5 Max. Charges",
+ price: 500,
+ isDollar: !1,
+ items: [{
+ name: "Max. Charge",
+ amount: 5
+ }]
+ },
+ 80: {
+ name: "+30 Paint Charges",
+ price: 500,
+ isDollar: !1,
+ items: [{
+ name: "Paint Charge",
+ amount: 30
+ }]
+ },
+ 100: {
+ name: "Unlock Color",
+ price: 2e3,
+ isDollar: !1,
+ items: [{
+ name: "Color",
+ amount: 1
+ }]
+ },
+ 110: {
+ name: "Flag",
+ price: 2e4,
+ isDollar: !1,
+ items: [{
+ name: "Flag",
+ amount: 1
+ }]
+ },
+ 120: {
+ name: "Profile Picture",
+ price: 2e4,
+ isDollar: !1,
+ items: [{
+ name: "Profile Picture",
+ amount: 1
+ }]
+ }
+ },
+ Le = JSON.parse(`[{"id":1,"name":"Afghanistan","code":"AF","flag":"🇦🇫"},{"id":2,"name":"Albania","code":"AL","flag":"🇦🇱"},{"id":3,"name":"Algeria","code":"DZ","flag":"🇩🇿"},{"id":4,"name":"American Samoa","code":"AS","flag":"🇦🇸"},{"id":5,"name":"Andorra","code":"AD","flag":"🇦🇩"},{"id":6,"name":"Angola","code":"AO","flag":"🇦🇴"},{"id":7,"name":"Anguilla","code":"AI","flag":"🇦🇮"},{"id":8,"name":"Antarctica","code":"AQ","flag":"🇦🇶"},{"id":9,"name":"Antigua and Barbuda","code":"AG","flag":"🇦🇬"},{"id":10,"name":"Argentina","code":"AR","flag":"🇦🇷"},{"id":11,"name":"Armenia","code":"AM","flag":"🇦🇲"},{"id":12,"name":"Aruba","code":"AW","flag":"🇦🇼"},{"id":13,"name":"Australia","code":"AU","flag":"🇦🇺"},{"id":14,"name":"Austria","code":"AT","flag":"🇦🇹"},{"id":15,"name":"Azerbaijan","code":"AZ","flag":"🇦🇿"},{"id":16,"name":"Bahamas","code":"BS","flag":"🇧🇸"},{"id":17,"name":"Bahrain","code":"BH","flag":"🇧🇭"},{"id":18,"name":"Bangladesh","code":"BD","flag":"🇧🇩"},{"id":19,"name":"Barbados","code":"BB","flag":"🇧🇧"},{"id":20,"name":"Belarus","code":"BY","flag":"🇧🇾"},{"id":21,"name":"Belgium","code":"BE","flag":"🇧🇪"},{"id":22,"name":"Belize","code":"BZ","flag":"🇧🇿"},{"id":23,"name":"Benin","code":"BJ","flag":"🇧🇯"},{"id":24,"name":"Bermuda","code":"BM","flag":"🇧🇲"},{"id":25,"name":"Bhutan","code":"BT","flag":"🇧🇹"},{"id":26,"name":"Bolivia","code":"BO","flag":"🇧🇴"},{"id":27,"name":"Bonaire","code":"BQ","flag":"🇧🇶"},{"id":28,"name":"Bosnia and Herzegovina","code":"BA","flag":"🇧🇦"},{"id":29,"name":"Botswana","code":"BW","flag":"🇧🇼"},{"id":30,"name":"Bouvet Island","code":"BV","flag":"🇧🇻"},{"id":31,"name":"Brazil","code":"BR","flag":"🇧🇷"},{"id":32,"name":"British Indian Ocean Territory","code":"IO","flag":"🇮🇴"},{"id":33,"name":"Brunei Darussalam","code":"BN","flag":"🇧🇳"},{"id":34,"name":"Bulgaria","code":"BG","flag":"🇧🇬"},{"id":35,"name":"Burkina Faso","code":"BF","flag":"🇧🇫"},{"id":36,"name":"Burundi","code":"BI","flag":"🇧🇮"},{"id":37,"name":"Cabo Verde","code":"CV","flag":"🇨🇻"},{"id":38,"name":"Cambodia","code":"KH","flag":"🇰🇭"},{"id":39,"name":"Cameroon","code":"CM","flag":"🇨🇲"},{"id":40,"name":"Canada","code":"CA","flag":"🇨🇦"},{"id":41,"name":"Cayman Islands","code":"KY","flag":"🇰🇾"},{"id":42,"name":"Central African Republic","code":"CF","flag":"🇨🇫"},{"id":43,"name":"Chad","code":"TD","flag":"🇹🇩"},{"id":44,"name":"Chile","code":"CL","flag":"🇨🇱"},{"id":45,"name":"China","code":"CN","flag":"🇨🇳"},{"id":46,"name":"Christmas Island","code":"CX","flag":"🇨🇽"},{"id":47,"name":"Cocos (Keeling) Islands","code":"CC","flag":"🇨🇨"},{"id":48,"name":"Colombia","code":"CO","flag":"🇨🇴"},{"id":49,"name":"Comoros","code":"KM","flag":"🇰🇲"},{"id":50,"name":"Congo","code":"CG","flag":"🇨🇬"},{"id":51,"name":"Cook Islands","code":"CK","flag":"🇨🇰"},{"id":52,"name":"Costa Rica","code":"CR","flag":"🇨🇷"},{"id":53,"name":"Croatia","code":"HR","flag":"🇭🇷"},{"id":54,"name":"Cuba","code":"CU","flag":"🇨🇺"},{"id":55,"name":"Curaçao","code":"CW","flag":"🇨🇼"},{"id":56,"name":"Cyprus","code":"CY","flag":"🇨🇾"},{"id":57,"name":"Czechia","code":"CZ","flag":"🇨🇿"},{"id":58,"name":"Côte d'Ivoire","code":"CI","flag":"🇨🇮"},{"id":59,"name":"Denmark","code":"DK","flag":"🇩🇰"},{"id":60,"name":"Djibouti","code":"DJ","flag":"🇩🇯"},{"id":61,"name":"Dominica","code":"DM","flag":"🇩🇲"},{"id":62,"name":"Dominican Republic","code":"DO","flag":"🇩🇴"},{"id":63,"name":"Ecuador","code":"EC","flag":"🇪🇨"},{"id":64,"name":"Egypt","code":"EG","flag":"🇪🇬"},{"id":65,"name":"El Salvador","code":"SV","flag":"🇸🇻"},{"id":66,"name":"Equatorial Guinea","code":"GQ","flag":"🇬🇶"},{"id":67,"name":"Eritrea","code":"ER","flag":"🇪🇷"},{"id":68,"name":"Estonia","code":"EE","flag":"🇪🇪"},{"id":69,"name":"Eswatini","code":"SZ","flag":"🇸🇿"},{"id":70,"name":"Ethiopia","code":"ET","flag":"🇪🇹"},{"id":71,"name":"Falkland Islands (Malvinas)","code":"FK","flag":"🇫🇰"},{"id":72,"name":"Faroe Islands","code":"FO","flag":"🇫🇴"},{"id":73,"name":"Fiji","code":"FJ","flag":"🇫🇯"},{"id":74,"name":"Finland","code":"FI","flag":"🇫🇮"},{"id":75,"name":"France","code":"FR","flag":"🇫🇷"},{"id":76,"name":"French Guiana","code":"GF","flag":"🇬🇫"},{"id":77,"name":"French Polynesia","code":"PF","flag":"🇵🇫"},{"id":78,"name":"French Southern Territories","code":"TF","flag":"🇹🇫"},{"id":79,"name":"Gabon","code":"GA","flag":"🇬🇦"},{"id":80,"name":"Gambia","code":"GM","flag":"🇬🇲"},{"id":81,"name":"Georgia","code":"GE","flag":"🇬🇪"},{"id":82,"name":"Germany","code":"DE","flag":"🇩🇪"},{"id":83,"name":"Ghana","code":"GH","flag":"🇬🇭"},{"id":84,"name":"Gibraltar","code":"GI","flag":"🇬🇮"},{"id":85,"name":"Greece","code":"GR","flag":"🇬🇷"},{"id":86,"name":"Greenland","code":"GL","flag":"🇬🇱"},{"id":87,"name":"Grenada","code":"GD","flag":"🇬🇩"},{"id":88,"name":"Guadeloupe","code":"GP","flag":"🇬🇵"},{"id":89,"name":"Guam","code":"GU","flag":"🇬🇺"},{"id":90,"name":"Guatemala","code":"GT","flag":"🇬🇹"},{"id":91,"name":"Guernsey","code":"GG","flag":"🇬🇬"},{"id":92,"name":"Guinea","code":"GN","flag":"🇬🇳"},{"id":93,"name":"Guinea-Bissau","code":"GW","flag":"🇬🇼"},{"id":94,"name":"Guyana","code":"GY","flag":"🇬🇾"},{"id":95,"name":"Haiti","code":"HT","flag":"🇭🇹"},{"id":96,"name":"Heard Island and McDonald Islands","code":"HM","flag":"🇭🇲"},{"id":97,"name":"Honduras","code":"HN","flag":"🇭🇳"},{"id":98,"name":"Hong Kong","code":"HK","flag":"🇭🇰"},{"id":99,"name":"Hungary","code":"HU","flag":"🇭🇺"},{"id":100,"name":"Iceland","code":"IS","flag":"🇮🇸"},{"id":101,"name":"India","code":"IN","flag":"🇮🇳"},{"id":102,"name":"Indonesia","code":"ID","flag":"🇮🇩"},{"id":103,"name":"Iran","code":"IR","flag":"🇮🇷"},{"id":104,"name":"Iraq","code":"IQ","flag":"🇮🇶"},{"id":105,"name":"Ireland","code":"IE","flag":"🇮🇪"},{"id":106,"name":"Isle of Man","code":"IM","flag":"🇮🇲"},{"id":107,"name":"Israel","code":"IL","flag":"🇮🇱"},{"id":108,"name":"Italy","code":"IT","flag":"🇮🇹"},{"id":109,"name":"Jamaica","code":"JM","flag":"🇯🇲"},{"id":110,"name":"Japan","code":"JP","flag":"🇯🇵"},{"id":111,"name":"Jersey","code":"JE","flag":"🇯🇪"},{"id":112,"name":"Jordan","code":"JO","flag":"🇯🇴"},{"id":113,"name":"Kazakhstan","code":"KZ","flag":"🇰🇿"},{"id":114,"name":"Kenya","code":"KE","flag":"🇰🇪"},{"id":115,"name":"Kiribati","code":"KI","flag":"🇰🇮"},{"id":116,"name":"Kosovo","code":"XK","flag":"🇽🇰"},{"id":117,"name":"Kuwait","code":"KW","flag":"🇰🇼"},{"id":118,"name":"Kyrgyzstan","code":"KG","flag":"🇰🇬"},{"id":119,"name":"Laos","code":"LA","flag":"🇱🇦"},{"id":120,"name":"Latvia","code":"LV","flag":"🇱🇻"},{"id":121,"name":"Lebanon","code":"LB","flag":"🇱🇧"},{"id":122,"name":"Lesotho","code":"LS","flag":"🇱🇸"},{"id":123,"name":"Liberia","code":"LR","flag":"🇱🇷"},{"id":124,"name":"Libya","code":"LY","flag":"🇱🇾"},{"id":125,"name":"Liechtenstein","code":"LI","flag":"🇱🇮"},{"id":126,"name":"Lithuania","code":"LT","flag":"🇱🇹"},{"id":127,"name":"Luxembourg","code":"LU","flag":"🇱🇺"},{"id":128,"name":"Macao","code":"MO","flag":"🇲🇴"},{"id":129,"name":"Madagascar","code":"MG","flag":"🇲🇬"},{"id":130,"name":"Malawi","code":"MW","flag":"🇲🇼"},{"id":131,"name":"Malaysia","code":"MY","flag":"🇲🇾"},{"id":132,"name":"Maldives","code":"MV","flag":"🇲🇻"},{"id":133,"name":"Mali","code":"ML","flag":"🇲🇱"},{"id":134,"name":"Malta","code":"MT","flag":"🇲🇹"},{"id":135,"name":"Marshall Islands","code":"MH","flag":"🇲🇭"},{"id":136,"name":"Martinique","code":"MQ","flag":"🇲🇶"},{"id":137,"name":"Mauritania","code":"MR","flag":"🇲🇷"},{"id":138,"name":"Mauritius","code":"MU","flag":"🇲🇺"},{"id":139,"name":"Mayotte","code":"YT","flag":"🇾🇹"},{"id":140,"name":"Mexico","code":"MX","flag":"🇲🇽"},{"id":141,"name":"Micronesia","code":"FM","flag":"🇫🇲"},{"id":142,"name":"Moldova","code":"MD","flag":"🇲🇩"},{"id":143,"name":"Monaco","code":"MC","flag":"🇲🇨"},{"id":144,"name":"Mongolia","code":"MN","flag":"🇲🇳"},{"id":145,"name":"Montenegro","code":"ME","flag":"🇲🇪"},{"id":146,"name":"Montserrat","code":"MS","flag":"🇲🇸"},{"id":147,"name":"Morocco","code":"MA","flag":"🇲🇦"},{"id":148,"name":"Mozambique","code":"MZ","flag":"🇲🇿"},{"id":149,"name":"Myanmar","code":"MM","flag":"🇲🇲"},{"id":150,"name":"Namibia","code":"NA","flag":"🇳🇦"},{"id":151,"name":"Nauru","code":"NR","flag":"🇳🇷"},{"id":152,"name":"Nepal","code":"NP","flag":"🇳🇵"},{"id":153,"name":"Netherlands","code":"NL","flag":"🇳🇱"},{"id":154,"name":"New Caledonia","code":"NC","flag":"🇳🇨"},{"id":155,"name":"New Zealand","code":"NZ","flag":"🇳🇿"},{"id":156,"name":"Nicaragua","code":"NI","flag":"🇳🇮"},{"id":157,"name":"Niger","code":"NE","flag":"🇳🇪"},{"id":158,"name":"Nigeria","code":"NG","flag":"🇳🇬"},{"id":159,"name":"Niue","code":"NU","flag":"🇳🇺"},{"id":160,"name":"Norfolk Island","code":"NF","flag":"🇳🇫"},{"id":161,"name":"North Korea","code":"KP","flag":"🇰🇵"},{"id":162,"name":"North Macedonia","code":"MK","flag":"🇲🇰"},{"id":163,"name":"Northern Mariana Islands","code":"MP","flag":"🇲🇵"},{"id":164,"name":"Norway","code":"NO","flag":"🇳🇴"},{"id":165,"name":"Oman","code":"OM","flag":"🇴🇲"},{"id":166,"name":"Pakistan","code":"PK","flag":"🇵🇰"},{"id":167,"name":"Palau","code":"PW","flag":"🇵🇼"},{"id":168,"name":"Palestine","code":"PS","flag":"🇵🇸"},{"id":169,"name":"Panama","code":"PA","flag":"🇵🇦"},{"id":170,"name":"Papua New Guinea","code":"PG","flag":"🇵🇬"},{"id":171,"name":"Paraguay","code":"PY","flag":"🇵🇾"},{"id":172,"name":"Peru","code":"PE","flag":"🇵🇪"},{"id":173,"name":"Philippines","code":"PH","flag":"🇵🇭"},{"id":174,"name":"Pitcairn","code":"PN","flag":"🇵🇳"},{"id":175,"name":"Poland","code":"PL","flag":"🇵🇱"},{"id":176,"name":"Portugal","code":"PT","flag":"🇵🇹"},{"id":177,"name":"Puerto Rico","code":"PR","flag":"🇵🇷"},{"id":178,"name":"Qatar","code":"QA","flag":"🇶🇦"},{"id":179,"name":"Republic of the Congo","code":"CD","flag":"🇨🇩"},{"id":180,"name":"Romania","code":"RO","flag":"🇷🇴"},{"id":181,"name":"Russia","code":"RU","flag":"🇷🇺"},{"id":182,"name":"Rwanda","code":"RW","flag":"🇷🇼"},{"id":183,"name":"Réunion","code":"RE","flag":"🇷🇪"},{"id":184,"name":"Saint Barthélemy","code":"BL","flag":"🇧🇱"},{"id":185,"name":"Saint Helena","code":"SH","flag":"🇸🇭"},{"id":186,"name":"Saint Kitts and Nevis","code":"KN","flag":"🇰🇳"},{"id":187,"name":"Saint Lucia","code":"LC","flag":"🇱🇨"},{"id":188,"name":"Saint Martin (French part)","code":"MF","flag":"🇲🇫"},{"id":189,"name":"Saint Pierre and Miquelon","code":"PM","flag":"🇵🇲"},{"id":190,"name":"Saint Vincent and the Grenadines","code":"VC","flag":"🇻🇨"},{"id":191,"name":"Samoa","code":"WS","flag":"🇼🇸"},{"id":192,"name":"San Marino","code":"SM","flag":"🇸🇲"},{"id":193,"name":"Sao Tome and Principe","code":"ST","flag":"🇸🇹"},{"id":194,"name":"Saudi Arabia","code":"SA","flag":"🇸🇦"},{"id":195,"name":"Senegal","code":"SN","flag":"🇸🇳"},{"id":196,"name":"Serbia","code":"RS","flag":"🇷🇸"},{"id":197,"name":"Seychelles","code":"SC","flag":"🇸🇨"},{"id":198,"name":"Sierra Leone","code":"SL","flag":"🇸🇱"},{"id":199,"name":"Singapore","code":"SG","flag":"🇸🇬"},{"id":200,"name":"Sint Maarten (Dutch part)","code":"SX","flag":"🇸🇽"},{"id":201,"name":"Slovakia","code":"SK","flag":"🇸🇰"},{"id":202,"name":"Slovenia","code":"SI","flag":"🇸🇮"},{"id":203,"name":"Solomon Islands","code":"SB","flag":"🇸🇧"},{"id":204,"name":"Somalia","code":"SO","flag":"🇸🇴"},{"id":205,"name":"South Africa","code":"ZA","flag":"🇿🇦"},{"id":206,"name":"South Georgia and the South Sandwich Islands","code":"GS","flag":"🇬🇸"},{"id":207,"name":"South Korea","code":"KR","flag":"🇰🇷"},{"id":208,"name":"South Sudan","code":"SS","flag":"🇸🇸"},{"id":209,"name":"Spain","code":"ES","flag":"🇪🇸"},{"id":210,"name":"Sri Lanka","code":"LK","flag":"🇱🇰"},{"id":211,"name":"Sudan","code":"SD","flag":"🇸🇩"},{"id":212,"name":"Suriname","code":"SR","flag":"🇸🇷"},{"id":213,"name":"Svalbard and Jan Mayen","code":"SJ","flag":"🇸🇯"},{"id":214,"name":"Sweden","code":"SE","flag":"🇸🇪"},{"id":215,"name":"Switzerland","code":"CH","flag":"🇨🇭"},{"id":216,"name":"Syrian Arab Republic","code":"SY","flag":"🇸🇾"},{"id":217,"name":"Taiwan Province of China","code":"TW","flag":"🇨🇳"},{"id":218,"name":"Tajikistan","code":"TJ","flag":"🇹🇯"},{"id":219,"name":"Tanzania","code":"TZ","flag":"🇹🇿"},{"id":220,"name":"Thailand","code":"TH","flag":"🇹🇭"},{"id":221,"name":"Timor-Leste","code":"TL","flag":"🇹🇱"},{"id":222,"name":"Togo","code":"TG","flag":"🇹🇬"},{"id":223,"name":"Tokelau","code":"TK","flag":"🇹🇰"},{"id":224,"name":"Tonga","code":"TO","flag":"🇹🇴"},{"id":225,"name":"Trinidad and Tobago","code":"TT","flag":"🇹🇹"},{"id":226,"name":"Tunisia","code":"TN","flag":"🇹🇳"},{"id":227,"name":"Turkmenistan","code":"TM","flag":"🇹🇲"},{"id":228,"name":"Turks and Caicos Islands","code":"TC","flag":"🇹🇨"},{"id":229,"name":"Tuvalu","code":"TV","flag":"🇹🇻"},{"id":230,"name":"Türkiye","code":"TR","flag":"🇹🇷"},{"id":231,"name":"Uganda","code":"UG","flag":"🇺🇬"},{"id":232,"name":"Ukraine","code":"UA","flag":"🇺🇦"},{"id":233,"name":"United Arab Emirates","code":"AE","flag":"🇦🇪"},{"id":234,"name":"United Kingdom","code":"GB","flag":"🇬🇧"},{"id":235,"name":"United States","code":"US","flag":"🇺🇸"},{"id":236,"name":"United States Minor Outlying Islands","code":"UM","flag":"🇺🇲"},{"id":237,"name":"Uruguay","code":"UY","flag":"🇺🇾"},{"id":238,"name":"Uzbekistan","code":"UZ","flag":"🇺🇿"},{"id":239,"name":"Vanuatu","code":"VU","flag":"🇻🇺"},{"id":240,"name":"Vatican City","code":"VA","flag":"🇻🇦"},{"id":241,"name":"Venezuela","code":"VE","flag":"🇻🇪"},{"id":242,"name":"Viet Nam","code":"VN","flag":"🇻🇳"},{"id":243,"name":"Virgin Islands","code":"VG","flag":"🇻🇬"},{"id":244,"name":"Virgin Islands","code":"VI","flag":"🇻🇮"},{"id":245,"name":"Wallis and Futuna","code":"WF","flag":"🇼🇫"},{"id":246,"name":"Western Sahara","code":"EH","flag":"🇪🇭"},{"id":247,"name":"Yemen","code":"YE","flag":"🇾🇪"},{"id":248,"name":"Zambia","code":"ZM","flag":"🇿🇲"},{"id":249,"name":"Zimbabwe","code":"ZW","flag":"🇿🇼"},{"id":250,"name":"Åland Islands","code":"AX","flag":"🇦🇽"},{"id":251,"name":"Canary Islands","code":"IC","flag":"🇮🇨"}]`),
+ I = {
+ seasons: De,
+ regionSize: ke,
+ refreshIntervalMs: Te,
+ colors: Be,
+ errors: Pe,
+ items: Ie,
+ products: Ge,
+ countries: Le
+ },
+ B = I,
+ Z = I.seasons.length - 1;
+I.seasons[Z].zoom;
+I.seasons[Z].tileSize;
+const Ae = Y(Me),
+ v = `cache-${oe}`,
+ Re = new Set([...ne, ...ie]),
+ k = self,
+ P = new Map;
+let w = [];
+self.addEventListener("install", event => {
+ event.waitUntil(Promise.resolve());
+});
+k.addEventListener("activate", e => {
+ async function n() {
+ for (const i of await caches.keys()) i !== v && await caches.delete(i)
+ }
+ e.waitUntil(n())
+});
+k.addEventListener("fetch", e => {
+ if (e.request.method !== "GET") return;
+ async function n() {
+ const l = new URL(e.request.url);
+ try {
+ return await i(l)
+ } catch (s) {
+ const m = await (await caches.open(v)).match(e.request);
+ if (m) return m;
+ throw s
+ }
+ }
+ async function i(l) {
+ var m, y;
+ const s = e.request.url.startsWith(ae) && l.pathname.match(/^.*\/s(\d+).*\/tiles\/(\d+)\/(\d+).png$/);
+ if (s) {
+ const t = P.get(e.clientId);
+ if (t || w.length) {
+ const _ = parseInt(s[1]),
+ G = parseInt(s[2]),
+ L = parseInt(s[3]),
+ W = Date.now(),
+ Q = 1.9 * B.refreshIntervalMs;
+ w = w.filter(o => W - o.time.getTime() < Q);
+ const $ = w.filter(({
+ data: o
+ }) => G === o.tile[0] && L === o.tile[1] && o.season === _).map(({
+ data: o
+ }) => ({
+ ...o
+ })),
+ X = ((m = t == null ? void 0 : t.data) == null ? void 0 : m.filter(o => G === o.tile[0] && L === o.tile[1] && o.season === _)) ?? [],
+ x = $.concat(X);
+ if (x.length || t) {
+ await Ae;
+ let o, A;
+ const T = je(G, L, _),
+ f = await ((y = t == null ? void 0 : t.cachedTiles) == null ? void 0 : y.get(T)),
+ O = f && W - f.time.getTime() < B.refreshIntervalMs;
+ if (O) o = structuredClone(f.png), A = f.init;
+ else {
+ let g = f;
+ if (t)
+ if (f === void 0) {
+ t.cachedTiles.set(T, p());
+ const c = await t.cachedTiles.get(T);
+ c && (g = c)
+ } else !O && !f.refreshing && (f.refreshing = !0, setTimeout(async () => {
+ try {
+ const c = await p();
+ t.cachedTiles.set(T, new Promise(h => h(c)));
+ const d = await k.clients.get(e == null ? void 0 : e.clientId);
+ d == null || d.postMessage({
+ type: "refreshPixelArt"
+ })
+ } catch {
+ f.refreshing = !1
+ }
+ }));
+ g || (g = await p()), o = structuredClone(g.png), A = g.init;
+ async function p() {
+ try {
+ const c = await fetch(e == null ? void 0 : e.request);
+ if (c && c.status !== 404) {
+ const d = await c.blob();
+ return {
+ png: await Ce(await d.arrayBuffer()),
+ init: {
+ headers: c.headers,
+ status: c.status,
+ statusText: c.statusText
+ },
+ time: new Date,
+ refreshing: !1
+ }
+ } else {
+ console.warn("painting 404 tile");
+ const d = B.seasons[_].tileSize;
+ return {
+ png: N(d, d),
+ init: {
+ headers: {
+ "Content-Type": "image/png"
+ },
+ status: 200
+ },
+ time: new Date,
+ refreshing: !1
+ }
+ }
+ } catch (c) {
+ if (console.error("Error while fetching in servicer worker: ", c), f) return f;
+ {
+ const d = B.seasons[_].tileSize;
+ return {
+ png: N(d, d),
+ init: {
+ headers: {
+ "Content-Type": "image/png"
+ },
+ status: 200
+ },
+ time: new Date,
+ refreshing: !1
+ }
+ }
+ }
+ }
+ }
+ const R = new Map;
+ for (const g of x) {
+ const [p, c] = g.pixel, d = p + c * o.width << 2, h = g.color;
+ R.get(d) || R.set(d, [o.data[d], o.data[d + 1], o.data[d + 2], o.data[d + 3]]), o.data[d] = h.r, o.data[d + 1] = h.g, o.data[d + 2] = h.b, o.data[d + 3] = h.a
+ }
+ const ee = await V(o);
+ for (const [g, p] of R.entries()) o.data[g] = p[0], o.data[g + 1] = p[1], o.data[g + 2] = p[2], o.data[g + 3] = p[3];
+ return new Response(ee, A)
+ }
+ }
+ }
+ const u = await fetch(e == null ? void 0 : e.request);
+ if (s && u.status === 404) {
+ const t = await V(N(1, 1));
+ return new Response(t, {
+ headers: {
+ "Content-Type": "image/png"
+ },
+ status: 200
+ })
+ }
+ return u
+ }
+ e.respondWith(n())
+});
+k.addEventListener("message", e => {
+ var i, l;
+ const n = e.data;
+ try {
+ const s = ((i = e.source) == null ? void 0 : i.id) ?? "none";
+ switch (n == null ? void 0 : n.type) {
+ case "previewPixels":
+ const u = n.data,
+ m = P.get(s);
+ m ? m.data = u : P.set(s, {
+ data: u,
+ cachedTiles: new Map
+ });
+ break;
+ case "clearPixelPreview":
+ P.delete(s);
+ break;
+ case "paintPixels":
+ w.push(...n.data.map(t => ({
+ data: t,
+ time: new Date
+ })));
+ break;
+ case "unpaintPixels":
+ const y = new Set(n.data.map(t => z(t)));
+ w = w.filter(({
+ data: t
+ }) => !y.has(z(t)));
+ break
+ }
+ } finally {
+ (l = e.source) == null || l.postMessage({
+ id: n.id
+ })
+ }
+});
+
+function je(e, n, i) {
+ return `t=(${e},${n});s=${i}`
+}
+
+function N(e, n) {
+ return {
+ data: new Uint8ClampedArray(e * n * 4),
+ width: e,
+ height: n,
+ colorSpace: "srgb"
+ }
+}
\ No newline at end of file
diff --git a/frontend-backup/site.webmanifest b/frontend-backup/site.webmanifest
new file mode 100644
index 0000000..dad99be
--- /dev/null
+++ b/frontend-backup/site.webmanifest
@@ -0,0 +1,53 @@
+{
+ "name": "Wplace",
+ "short_name": "Wplace",
+ "description": "Wplace is a collaborative, real-time pixel canvas layered over the world map, where anyone can paint and create art together.",
+ "start_url": "/",
+ "theme_color": "#f8f4f0",
+ "background_color": "#ffffff",
+ "display": "standalone",
+ "icons": [
+ {
+ "src": "/img/web-app-manifest-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/img/web-app-manifest-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "screenshots": [
+ {
+ "src": "/img/pwa-void-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/pwa-kiev-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/pwa-paint-heart-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/pwa-country-leaderboard-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/og-image.png",
+ "type": "image/png",
+ "sizes": "1200x630",
+ "form_factor": "wide"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend-src/.env.example b/frontend-src/.env.example
new file mode 100644
index 0000000..0d6550b
--- /dev/null
+++ b/frontend-src/.env.example
@@ -0,0 +1,19 @@
+# Backend API URL
+PUBLIC_BACKEND_URL=http://localhost:3000
+
+# Files/Assets URL
+PUBLIC_FILES_URL=http://localhost:3000/files
+
+# Map tile server URL
+PUBLIC_MAP_URL=https://maps.wplace.live/styles/liberty
+
+# Environment (dev, prod)
+PUBLIC_ENV=dev
+
+# Maintenance mode
+PUBLIC_MAINTENANCE=false
+
+# Cloudflare Turnstile (bot protection)
+PUBLIC_TURNSTILE_ENABLED=false
+PUBLIC_TURNSTILE_SITE_KEY_LOGIN=0x4AAAAAABpHqZ-6i7uL0nmG
+PUBLIC_TURNSTILE_SITE_KEY_PAINT=0x4AAAAAABpqJe8FO0N84q0F
diff --git a/frontend-src/.gitignore b/frontend-src/.gitignore
new file mode 100644
index 0000000..6635cf5
--- /dev/null
+++ b/frontend-src/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
diff --git a/frontend-src/BUILD.md b/frontend-src/BUILD.md
new file mode 100644
index 0000000..b5baf3b
--- /dev/null
+++ b/frontend-src/BUILD.md
@@ -0,0 +1,294 @@
+# Build Instructions
+
+## Development
+
+### Setup
+```bash
+# Install dependencies
+pnpm install
+
+# Copy environment file
+cp .env.example .env
+
+# Edit .env as needed
+# For local development with parent backend:
+# PUBLIC_API_URL=http://localhost:3000
+# PUBLIC_SEASON=s1
+# PUBLIC_ENABLE_TURNSTILE=false
+```
+
+### Run Dev Server
+```bash
+pnpm dev
+```
+
+App runs on http://localhost:5173
+
+**Note**: Dev server proxies `/api/*` to `http://localhost:3000` (see [vite.config.ts](vite.config.ts))
+
+---
+
+## Production Build
+
+### Build for Parent Backend
+
+The build is configured to output directly to the parent backend's `frontend/` folder:
+
+```bash
+pnpm build
+```
+
+**Output location**: `../frontend/` (replaces existing compiled frontend)
+
+### What Gets Built
+
+```
+../frontend/
+├── _app/ # SvelteKit app chunks
+│ ├── immutable/
+│ │ ├── assets/ # CSS files
+│ │ ├── chunks/ # JS chunks
+│ │ ├── nodes/ # Route components
+│ │ └── entry/ # App entry points
+│ └── version.json
+├── index.html # Main page
+├── 404.html # 404 fallback
+├── admin/
+│ └── index.html
+├── moderation/
+│ └── index.html
+├── join/
+│ └── index.html
+└── ... (other static files)
+```
+
+### Backend Integration
+
+The parent backend (in `../src/index.ts`) serves the built frontend:
+
+```typescript
+// Serve frontend static files
+app.use('/', sirv('./frontend', {
+ maxAge: 31536000, // 1 year for immutable files
+ immutable: true
+}));
+
+// SPA fallback - serve 404.html for unmatched routes
+app.use((req, res) => {
+ res.setHeader('Content-Type', 'text/html');
+ res.sendFile(path.join(__dirname, '../frontend/404.html'));
+});
+```
+
+**All API routes** (`/me`, `/auth/*`, `/alliance/*`, etc.) are handled by the backend first, then unmatched routes serve the frontend.
+
+---
+
+## Environment Variables
+
+### Build Time (PUBLIC_*)
+
+These are embedded into the built JavaScript:
+
+- `PUBLIC_API_URL` - API base URL (default: `''` = same origin)
+- `PUBLIC_SEASON` - Current season (default: `'s1'`)
+- `PUBLIC_ENABLE_TURNSTILE` - Enable Cloudflare Turnstile (default: `'false'`)
+- `PUBLIC_TURNSTILE_SITE_KEY` - Turnstile site key
+
+### Development vs Production
+
+**Development** (`.env`):
+```env
+PUBLIC_API_URL=http://localhost:3000
+PUBLIC_SEASON=s1
+PUBLIC_ENABLE_TURNSTILE=false
+```
+
+**Production** (backend serves frontend from same origin):
+```env
+PUBLIC_API_URL=
+PUBLIC_SEASON=s1
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAABpqJe8FO0N84q0F
+```
+
+---
+
+## Turnstile Configuration
+
+### Disable Turnstile (Development)
+
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+```
+
+- No Turnstile widget rendered
+- Login buttons work immediately
+- OAuth URLs don't include `token` parameter
+- Backend should handle auth without Turnstile
+
+### Enable Turnstile (Production)
+
+```env
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
+```
+
+- Turnstile widget loads on login page
+- Login buttons disabled until captcha completes
+- OAuth URLs include `?token=...` parameter
+- Backend validates Turnstile token
+
+---
+
+## Build Process
+
+### Clean Build
+
+```bash
+# Remove old build
+rm -rf ../frontend/_app ../frontend/index.html
+
+# Fresh build
+pnpm build
+```
+
+### Verify Build
+
+After building, check the parent backend:
+
+```bash
+cd ..
+pnpm start
+
+# Open browser to http://localhost:3000
+# Should see the new frontend
+```
+
+---
+
+## Backend Requirements
+
+The parent backend must:
+
+1. **Serve static files** from `./frontend` directory
+2. **Handle API routes** first (before static file serving)
+3. **Serve 404.html** for SPA fallback on unmatched routes
+4. **Set appropriate headers** (CORS, CSP, etc.)
+
+Example setup in `../src/index.ts`:
+
+```typescript
+import sirv from 'sirv';
+import path from 'path';
+
+// API routes first
+app.get('/me', authMiddleware, ...);
+app.post('/auth/logout', ...);
+// ... all other API routes
+
+// Serve frontend static files
+app.use('/', sirv('./frontend', {
+ maxAge: 31536000,
+ immutable: true
+}));
+
+// SPA fallback
+app.use((req, res) => {
+ res.setHeader('Content-Type', 'text/html');
+ res.sendFile(path.join(__dirname, '../frontend/404.html'));
+});
+```
+
+---
+
+## Troubleshooting
+
+### Build fails with "Cannot find module"
+```bash
+rm -rf node_modules
+pnpm install
+```
+
+### Changes not appearing
+```bash
+# Clear browser cache
+# Or use incognito mode
+```
+
+### API calls return 404
+- Check `PUBLIC_API_URL` in `.env`
+- For production, set to empty string (same origin)
+- Backend API routes must be registered before static file serving
+
+### Turnstile not working
+- Check `PUBLIC_ENABLE_TURNSTILE` is `'true'` (string, not boolean)
+- Verify `PUBLIC_TURNSTILE_SITE_KEY` is correct
+- Check browser console for errors
+
+---
+
+## CI/CD Integration
+
+### Example GitHub Actions
+
+```yaml
+name: Build Frontend
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'frontend-src/**'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 10
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 24
+ cache: 'pnpm'
+ cache-dependency-path: frontend-src/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: cd frontend-src && pnpm install
+
+ - name: Build
+ run: cd frontend-src && pnpm build
+ env:
+ PUBLIC_API_URL: ''
+ PUBLIC_SEASON: 's1'
+ PUBLIC_ENABLE_TURNSTILE: 'true'
+ PUBLIC_TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: frontend-build
+ path: frontend/
+```
+
+---
+
+## File Structure Reference
+
+```
+openplace/
+├── frontend/ # ← Build output (served by backend)
+│ ├── _app/
+│ ├── index.html
+│ └── ...
+├── frontend-src/ # ← Source code (this directory)
+│ ├── src/
+│ ├── static/
+│ ├── package.json
+│ ├── svelte.config.js # Output to ../frontend
+│ └── BUILD.md # This file
+└── src/ # Backend source
+ └── index.ts # Serves ./frontend
+```
diff --git a/frontend-src/BUILD_STATUS.md b/frontend-src/BUILD_STATUS.md
new file mode 100644
index 0000000..dc83757
--- /dev/null
+++ b/frontend-src/BUILD_STATUS.md
@@ -0,0 +1,202 @@
+# Frontend Build Status
+
+## ✅ Completed (Phase 1)
+
+### Core Infrastructure
+- [x] **Package Management**: Full dependency setup with SvelteKit, TailwindCSS, DaisyUI, Leaflet
+- [x] **Build Configuration**: Vite, TypeScript, PostCSS, Tailwind properly configured
+- [x] **Project Structure**: Clean organization following SvelteKit conventions
+
+### API & Data Layer
+- [x] **API Client** (`src/lib/api/client.ts`): Complete implementation with 50+ endpoints
+ - Auth endpoints (logout)
+ - User endpoints (getMe, updateMe, profilePictures)
+ - Pixel endpoints (paint, getInfo, getTile)
+ - Alliance endpoints (CRUD, members, invites, bans)
+ - Leaderboard endpoints (player, alliance, country, region)
+ - Store endpoints (purchase, equipFlag)
+ - Admin endpoints (users, tickets, alliances)
+ - Moderation endpoints (tickets, counts)
+
+### State Management
+- [x] **Global Store** (`src/lib/stores/global.ts`): Turnstile, language, time updates
+- [x] **User Store** (`src/lib/stores/user.ts`): Authentication, profile, derived charges
+- [x] **Canvas Store** (`src/lib/stores/canvas.ts`): Selected color, position, pixels
+- [x] **Alliance Store** (`src/lib/stores/alliance.ts`): Alliance management
+- [x] **Toast Store** (`src/lib/stores/toast.ts`): Notification system (global `W` object)
+
+### Utilities
+- [x] **Colors** (`src/lib/constants/colors.ts`): 64-color palette with hex/rgb converters
+- [x] **Bitmap** (`src/lib/utils/bitmap.ts`): Unlocked colors/flags bitmap handling
+- [x] **Charges** (`src/lib/utils/charges.ts`): Charge regeneration calculations
+- [x] **Level** (`src/lib/utils/level.ts`): Level/XP system with progress tracking
+- [x] **Config** (`src/lib/constants/config.ts`): All app constants (API URL, store items, etc.)
+
+### Internationalization
+- [x] **i18n System** (`src/lib/i18n/`): Translation infrastructure
+- [x] **English Translations** (`src/lib/i18n/en.ts`): Complete EN translations
+- [x] **Portuguese Translations** (`src/lib/i18n/pt.ts`): Complete PT translations
+- [x] **Auto Language Detection**: Browser language detection
+
+### UI Components
+- [x] **Toast Component** (`src/lib/components/common/Toast.svelte`): Notifications with animations
+- [x] **Modal Component** (`src/lib/components/common/Modal.svelte`): Reusable modal dialog
+- [x] **Button Component** (`src/lib/components/common/Button.svelte`): Styled button variants
+- [x] **Logo Component** (`src/lib/components/layout/Logo.svelte`): Brand logo with sizing
+- [x] **Header Component** (`src/lib/components/layout/Header.svelte`): Navigation header
+
+### Authentication
+- [x] **Turnstile Component** (`src/lib/components/auth/Turnstile.svelte`): Cloudflare captcha
+- [x] **Login Form** (`src/lib/components/auth/LoginForm.svelte`): OAuth login (Google/Twitch)
+
+### Routes & Pages
+- [x] **Root Layout** (`src/routes/+layout.svelte`): Global layout with user fetch
+- [x] **Home Page** (`src/routes/+page.svelte`): Main canvas placeholder
+- [x] **Join/Login Page** (`src/routes/join/+page.svelte`): Authentication page
+- [x] **404 Page** (`src/routes/404/+page.svelte`): Not found page
+- [x] **Admin Page** (`src/routes/admin/+page.svelte`): Admin dashboard skeleton
+- [x] **Moderation Page** (`src/routes/moderation/+page.svelte`): Mod panel skeleton
+
+### HTML & Styles
+- [x] **app.html**: Complete HTML template with meta tags, PWA support, CSP
+- [x] **app.css**: Global styles with TailwindCSS, DaisyUI, Leaflet CSS
+- [x] **Pixelated Rendering**: CSS class for crisp pixel art
+
+---
+
+## 📊 Statistics
+
+**Total Files Created**: 35+
+
+### Breakdown by Category:
+- **Configuration**: 7 files (package.json, configs, etc.)
+- **API & Stores**: 6 files
+- **Constants & Utils**: 5 files
+- **i18n**: 3 files
+- **Components**: 7 files
+- **Routes**: 6 files
+- **Documentation**: 3 files
+
+**Lines of Code**: ~3,000+ (estimated)
+
+---
+
+## 🚧 Remaining Work (Phase 2)
+
+### High Priority
+- [ ] **Canvas Component**: Leaflet map integration for pixel painting
+- [ ] **Color Picker**: 64-color palette selector with locked states
+- [ ] **Charge Indicator**: Real-time charge display with countdown
+- [ ] **Pixel Info Modal**: Show pixel details on click
+
+### Medium Priority
+- [ ] **Alliance Pages**: Create, view, manage alliance
+- [ ] **Leaderboard Pages**: Player, alliance, country, region leaderboards
+- [ ] **Store Page**: Purchase colors, flags, charges
+- [ ] **Profile Page**: View/edit user profile
+
+### Low Priority
+- [ ] **Service Worker**: Offline support and caching
+- [ ] **Admin Panel**: Full user/alliance/ticket management
+- [ ] **Moderation Panel**: Ticket review and actions
+- [ ] **Terms Pages**: ToS, Privacy Policy, Return Policy
+
+---
+
+## 🎯 Next Steps
+
+### To Run Development Server:
+```bash
+cd frontend-src
+pnpm install
+cp .env.example .env
+# Edit .env with your API URL
+pnpm dev
+```
+
+### To Build for Production:
+```bash
+pnpm build
+# Output in build/ directory
+```
+
+---
+
+## 📝 Implementation Notes
+
+### What Works Now:
+1. **Authentication Flow**: Login with Google/Twitch via Turnstile → OAuth → Cookie
+2. **User State**: Automatic user fetch on load, reactive charge updates
+3. **Toast Notifications**: Global `W` object for toast.error(), toast.success(), etc.
+4. **i18n**: Language auto-detection and translations
+5. **Routing**: Protected routes redirect to login
+6. **Styling**: Full DaisyUI theme with TailwindCSS utilities
+
+### Key Patterns Used:
+- **API Calls**: `import { api } from '$lib/api/client'` → `await api.getMe()`
+- **State**: `import { currentUser } from '$lib/stores/user'` → `$currentUser`
+- **Translations**: `import { t } from '$lib/i18n'` → `$t('login')`
+- **Toasts**: `import { toast } from '$lib/stores/toast'` → `toast.success('Done!')`
+
+### Matching Original Frontend:
+✅ Same tech stack (SvelteKit + DaisyUI)
+✅ Same API endpoints and patterns
+✅ Same OAuth providers (Google/Twitch)
+✅ Same Turnstile integration
+✅ Same global state structure
+✅ Same color palette and constants
+✅ Same i18n approach (EN/PT)
+✅ Same styling conventions
+
+---
+
+## 🔍 Code Quality
+
+- **TypeScript**: Full type safety across the project
+- **Linting**: ESLint ready (can add rules)
+- **Formatting**: Prettier compatible
+- **Accessibility**: ARIA labels, semantic HTML
+- **Performance**: Lazy loading, code splitting via SvelteKit
+- **SEO**: Meta tags, OG tags, structured data
+
+---
+
+## 📚 Documentation
+
+- [README.md](README.md) - Quick start guide
+- [IMPLEMENTATION_NOTES.md](IMPLEMENTATION_NOTES.md) - Analysis findings from compiled frontend
+- [BUILD_STATUS.md](BUILD_STATUS.md) - This file
+
+---
+
+## 🎨 Design System
+
+**Colors**: DaisyUI light theme
+**Typography**:
+- Default: System fonts (Geist equivalent via Tailwind)
+- Pixel: PixelifySans for retro elements
+
+**Components Follow**:
+- DaisyUI conventions (btn, modal, alert, etc.)
+- Consistent spacing (Tailwind scale)
+- Responsive design (mobile-first)
+
+---
+
+## ✨ Ready for Development
+
+The foundation is **production-ready** and follows best practices:
+- Clean separation of concerns
+- Reusable components
+- Type-safe API client
+- Scalable state management
+- Maintainable code structure
+
+**You can now**:
+1. Start the dev server and see the login page
+2. Authenticate with Google/Twitch
+3. View your profile (once logged in)
+4. Navigate between pages
+5. See toast notifications
+
+**Next developer**: Focus on implementing the canvas/Leaflet integration and filling out the remaining pages!
diff --git a/frontend-src/CONFIGURATION_SUMMARY.md b/frontend-src/CONFIGURATION_SUMMARY.md
new file mode 100644
index 0000000..80268bf
--- /dev/null
+++ b/frontend-src/CONFIGURATION_SUMMARY.md
@@ -0,0 +1,153 @@
+# Configuration Summary
+
+Quick reference for all configuration options.
+
+## ✅ Completed Setup
+
+### Turnstile Configuration
+- ✅ **Optional**: Can be completely disabled via environment variable
+- ✅ **Build-time flag**: `PUBLIC_ENABLE_TURNSTILE=false` for dev, `true` for production
+- ✅ **No code changes needed**: All handled via `.env`
+
+### Build Output
+- ✅ **Configured**: Builds directly to `../frontend/` (parent backend folder)
+- ✅ **Integration**: Parent backend serves built files from `./frontend`
+- ✅ **SPA fallback**: 404.html for unmatched routes
+
+## Environment Variables
+
+### Required
+```env
+PUBLIC_API_URL=http://localhost:3000
+PUBLIC_SEASON=s1
+```
+
+### Optional (Turnstile)
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAABpqJe8FO0N84q0F
+```
+
+## Quick Commands
+
+### Development
+```bash
+# Setup
+cd frontend-src
+pnpm install
+cp .env.example .env
+
+# Run
+pnpm dev # http://localhost:5173
+```
+
+### Production Build
+```bash
+# Build to ../frontend/
+pnpm build
+
+# Run parent backend
+cd ..
+pnpm start # http://localhost:3000
+```
+
+## Configuration Files
+
+| File | Purpose |
+|------|---------|
+| `.env` | Environment variables (not committed) |
+| `.env.example` | Template with defaults |
+| `svelte.config.js` | Build output to `../frontend/` |
+| `vite.config.ts` | Dev server proxy to backend |
+| `tailwind.config.js` | DaisyUI + light theme |
+| `tsconfig.json` | TypeScript configuration |
+
+## Turnstile Modes
+
+### Mode 1: Disabled (Development)
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+```
+- ✅ No captcha widget
+- ✅ Login works immediately
+- ✅ No external scripts
+- ✅ Works offline
+
+### Mode 2: Enabled (Production)
+```env
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=your_key
+```
+- 🔒 Captcha required
+- 🔒 Loads Cloudflare script
+- 🔒 Bot protection
+- 🔒 Requires internet
+
+See [README_TURNSTILE.md](README_TURNSTILE.md) for full details.
+
+## Backend Integration
+
+The parent backend must:
+1. Serve API routes first
+2. Serve static files from `./frontend`
+3. Fallback to `404.html` for SPA routing
+
+Example (tinyhttp):
+```typescript
+// API routes
+app.get('/me', authMiddleware, ...);
+// ...
+
+// Static files
+app.use('/', sirv('./frontend', { maxAge: 31536000 }));
+
+// SPA fallback
+app.use((req, res) => {
+ res.sendFile(path.join(__dirname, '../frontend/404.html'));
+});
+```
+
+## File Structure
+```
+openplace/
+├── frontend/ # ← Built output (served by backend)
+│ ├── _app/
+│ ├── index.html
+│ └── 404.html
+├── frontend-src/ # ← Source code
+│ ├── src/
+│ ├── .env # Your config (git ignored)
+│ ├── .env.example # Template
+│ └── svelte.config.js # Outputs to ../frontend
+└── src/ # Backend
+ └── index.ts # Serves ./frontend
+```
+
+## Documentation Files
+
+- **[QUICKSTART.md](QUICKSTART.md)** - 5-minute setup guide
+- **[BUILD.md](BUILD.md)** - Full build & deployment guide
+- **[README_TURNSTILE.md](README_TURNSTILE.md)** - Turnstile configuration
+- **[README.md](README.md)** - Project overview
+- **[BUILD_STATUS.md](BUILD_STATUS.md)** - Implementation status
+- **[IMPLEMENTATION_NOTES.md](IMPLEMENTATION_NOTES.md)** - Analysis findings
+
+## Key Features
+
+✅ **Optional Turnstile** - Can be disabled for development
+✅ **Backend Integration** - Builds to parent folder
+✅ **i18n Support** - English & Portuguese
+✅ **Type Safe** - Full TypeScript
+✅ **Modern Stack** - SvelteKit + TailwindCSS + DaisyUI
+✅ **API Client** - All 50+ endpoints implemented
+✅ **State Management** - Svelte stores
+✅ **PWA Ready** - Service worker support
+
+## Next Steps
+
+1. Configure `.env` for your needs
+2. Run `pnpm dev` to start development
+3. Build with `pnpm build` when ready
+4. Deploy built files from `../frontend/`
+
+For questions, see the documentation files listed above!
diff --git a/frontend-src/IMPLEMENTATION_NOTES.md b/frontend-src/IMPLEMENTATION_NOTES.md
new file mode 100644
index 0000000..47dd3bc
--- /dev/null
+++ b/frontend-src/IMPLEMENTATION_NOTES.md
@@ -0,0 +1,202 @@
+# Frontend Implementation Notes
+
+Based on analysis of compiled frontend in `/frontend` folder.
+
+## Key Findings from Compiled Code
+
+### Tech Stack (Confirmed)
+- **SvelteKit** 2.x with adapter-static
+- **TailwindCSS** + **DaisyUI** 4.x for styling
+- **Leaflet** for map/canvas
+- **Cloudflare Turnstile** for captcha/bot protection
+- **No additional state libraries** - uses Svelte stores
+
+### API Client Pattern
+```javascript
+// Found in chunks/1lh-LSvX.js
+class ApiClient {
+ async request(endpoint, options) {
+ const response = await fetch(`${this.url}${endpoint}`, options);
+ this.online = true;
+ return response;
+ }
+}
+```
+
+**Endpoints found:**
+- `/me` - GET user profile
+- `/auth/logout` - POST logout
+- `/me/update` - POST update profile
+- `/alliance` - GET/POST alliance operations
+- `/alliance/update-description` - POST
+- `/alliance/update-headquarters` - POST
+- `/alliance/invites` - GET
+- `/alliance/give-admin` - POST
+- `/alliance/ban` - POST
+- `/alliance/unban` - POST
+- `/purchase` - POST store purchases
+- `/favorite-location` - POST
+- `/favorite-location/update` - POST
+- `/moderator/tickets` - GET
+- `/moderator/severe-open-tickets-count` - GET
+- `/me/profile-pictures` - GET
+- `/otp/cooldown`, `/otp/send`, `/otp/verify` - OTP system (phone verification)
+
+### Authentication
+- **OAuth providers**: Google, Twitch
+- Auth redirects to: `${API_URL}/auth/{provider}?token={turnstile_token}&r={redirect}`
+- JWT stored in **`j` cookie** (HttpOnly)
+- Turnstile captcha required before OAuth
+
+### Global State (from chunks/1lh-LSvX.js)
+```javascript
+let globalState = {
+ dropletsDialogOpen: false,
+ muted: false,
+ language: getLanguage(), // 'en' or 'pt' detected from navigator
+ captcha: undefined, // Turnstile token
+ now: Date.now(), // Updates every 500ms
+ turnstatileLoaded: false
+};
+
+setInterval(() => {
+ globalState.now = Date.now()
+}, 500);
+```
+
+### Cloudflare Turnstile
+- Site key: `0x4AAAAAABpqJe8FO0N84q0F` (from constant `qt`)
+- Script: `https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit`
+- Required before login/OAuth
+
+### Routes (from app.iDaujbEI.js dictionary)
+```
+/ - Main canvas (node 2)
+/404 - 404 page (node 3)
+/join - Join/onboarding (node 4)
+/admin - Admin panel (node 5)
+/offline - Offline page (node 6)
+/payment/success - Payment success (node 7)
+/profile-picture - Profile picture selector (node 8)
+/terms/privacy - Privacy policy (node 9)
+/terms/return - Return policy (node 10)
+/terms/terms-of-service - Terms of service (node 11)
+/moderation - Moderation panel (node 14)
+```
+
+### Styling Patterns
+- **Base classes**: `bg-base-100`, `bg-base-200`, `text-base-content`
+- **Buttons**: `btn`, `btn-ghost`, `btn-circle`, `btn-sm`, `btn-lg`, `btn-error`
+- **Modals**: `modal`, `modal-box`, `modal-backdrop`
+- **Tabs**: `tabs`, `tabs-border`, `tab`
+- **Loading**: `loading loading-spinner loading-md`
+- **Tooltips**: `tooltip tooltip-bottom`
+- **Layout**: Uses Tailwind grid/flex extensively
+
+### Report/Moderation System
+**Report reasons** (from fZ59cmjx.js):
+- `inappropriate-content` - "+18, inappropriate link, highly suggestive content"
+- `hate-speech` - "Racism, homophobia, hate groups"
+- `doxxing` - "Released personal information"
+- `bot` - "Automated painting software"
+- `griefing` - "Messed up artworks for no reason"
+- `other` - "Other reason"
+
+**Report form endpoint**: `${API_URL}/report-user`
+**Timeout endpoint**: `${API_URL}/moderator/timeout-user`
+**Ban endpoint**: `${API_URL}/admin/ban-user`
+
+### Internationalization
+- Supports English (`en`) and Portuguese (`pt`) minimum
+- Language detection from `navigator.languages` or `navigator.language`
+- Translation functions: `(params, {locale}) => locale === "en" ? englishText : portugueseText`
+
+### Toast Notifications
+- Global `W` object with `W.error()`, `W.info()` methods
+- Aria-live region: `aria-label="Notifications alt+T"`
+
+### PWA Features
+- Service worker: `/service-worker.js`
+- Install prompt: `window.pwaInstallPrompt`
+- Manifest: `/site.webmanifest`
+
+### CSP Header
+```
+script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://challenges.cloudflare.com blob:
+```
+
+### Constants
+- `Nt = "files"` - Files base path
+- Season: Default `s1`
+- Version displayed as span: `Version: 1759175263375`
+
+## Implementation Priority
+
+1. **Core Setup** (DONE)
+ - [x] Package.json with DaisyUI + Tailwind
+ - [x] Svelte config
+ - [x] PostCSS/Tailwind config
+
+2. **Next Steps**
+ - [ ] API client (`src/lib/api/client.ts`)
+ - [ ] Global stores (`src/lib/stores/`)
+ - [ ] i18n utilities (`src/lib/i18n/`)
+ - [ ] Toast notification system
+ - [ ] Turnstile component
+ - [ ] Layout components
+ - [ ] Auth pages (login with OAuth)
+ - [ ] Main canvas route
+ - [ ] Admin/moderation routes
+
+3. **File Structure**
+```
+src/
+├── lib/
+│ ├── api/
+│ │ └── client.ts # API client class
+│ ├── stores/
+│ │ ├── global.ts # Global state (turnstile, language, etc.)
+│ │ ├── user.ts # Current user
+│ │ ├── canvas.ts # Canvas state
+│ │ └── alliance.ts # Alliance state
+│ ├── i18n/
+│ │ ├── index.ts # Translation utilities
+│ │ ├── en.ts # English translations
+│ │ └── pt.ts # Portuguese translations
+│ ├── components/
+│ │ ├── common/
+│ │ │ ├── Button.svelte
+│ │ │ ├── Modal.svelte
+│ │ │ └── Toast.svelte
+│ │ ├── auth/
+│ │ │ ├── LoginForm.svelte # OAuth buttons + Turnstile
+│ │ │ └── Turnstile.svelte # Cloudflare Turnstile
+│ │ ├── canvas/
+│ │ │ └── LeafletMap.svelte
+│ │ └── layout/
+│ │ ├── Header.svelte
+│ │ └── Logo.svelte
+│ ├── constants/
+│ │ ├── colors.ts # Color palette
+│ │ └── config.ts # API URLs, etc.
+│ └── utils/
+│ ├── bitmap.ts # Bitmap helpers
+│ ├── charges.ts # Charge calculation
+│ └── level.ts # Level calculation
+├── routes/
+│ ├── +layout.svelte # Root layout
+│ ├── +page.svelte # Main canvas
+│ ├── admin/
+│ │ └── +page.svelte # Admin dashboard
+│ ├── moderation/
+│ │ └── +page.svelte # Moderation panel
+│ └── join/
+│ └── +page.svelte # Login/onboarding
+└── app.html # HTML template
+```
+
+## Notes
+- Original frontend uses Svelte 5 runes syntax (`$state`, `$derived`)
+- We can use Svelte 4 with traditional stores for simplicity
+- No additional HTTP library needed - uses native `fetch`
+- Leaflet needs `import 'leaflet/dist/leaflet.css'` in layout
diff --git a/frontend-src/QUICKSTART.md b/frontend-src/QUICKSTART.md
new file mode 100644
index 0000000..250e6af
--- /dev/null
+++ b/frontend-src/QUICKSTART.md
@@ -0,0 +1,231 @@
+# Quick Start Guide
+
+## Setup (5 minutes)
+
+### 1. Install Dependencies
+```bash
+cd frontend-src
+pnpm install
+```
+
+### 2. Configure Environment
+```bash
+cp .env.example .env
+```
+
+Edit `.env`:
+```env
+PUBLIC_API_URL=http://localhost:3000
+PUBLIC_SEASON=s1
+PUBLIC_ENABLE_TURNSTILE=false
+PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAABpqJe8FO0N84q0F
+```
+
+**Important**: Set `PUBLIC_ENABLE_TURNSTILE=false` for local development to skip captcha.
+
+### 3. Run Development Server
+```bash
+pnpm dev
+```
+
+Open http://localhost:5173
+
+The dev server proxies API calls to `http://localhost:3000` (your backend).
+
+---
+
+## Building for Production
+
+### Build and Deploy to Parent Backend
+
+```bash
+# Builds to ../frontend/ directory
+pnpm build
+
+# Start parent backend
+cd ..
+pnpm start
+
+# Open http://localhost:3000
+```
+
+The build automatically outputs to the parent backend's static folder.
+
+---
+
+## Turnstile Configuration
+
+### Development (No Captcha)
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+```
+- Login works immediately, no captcha
+- Perfect for local development
+
+### Production (With Captcha)
+```env
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
+```
+- Cloudflare Turnstile loads on login
+- Protects against bots
+
+---
+
+## What You'll See
+
+### Home Page (`/`)
+- Redirects to `/join` if not logged in
+- Shows canvas placeholder if authenticated
+
+### Login Page (`/join`)
+- Google OAuth button
+- Twitch OAuth button
+- Cloudflare Turnstile captcha (if enabled)
+
+### After Login
+- Header with user avatar
+- Dropdown menu (Profile, Alliance, Leaderboard, Store, Logout)
+- Toast notifications for actions
+
+---
+
+## Testing Authentication
+
+**With Turnstile disabled** (recommended for dev):
+1. Click "Login with Google" or "Login with Twitch"
+2. Authorize with OAuth provider
+3. Backend sets JWT cookie (`j`)
+4. Redirected back to home page
+5. User data loaded automatically
+
+**With Turnstile enabled**:
+1. Complete Turnstile captcha first
+2. Then click OAuth button
+3. ... rest same as above
+
+---
+
+## Common Tasks
+
+### Make an API Call
+```typescript
+import { api } from '$lib/api/client';
+
+const user = await api.getMe();
+const alliance = await api.getAlliance();
+await api.paintPixels('s1', 0, 0, {
+ colors: [7],
+ coords: [[100, 100]]
+});
+```
+
+### Use a Store
+```svelte
+
+
+{#if $currentUser}
+ Hello, {$currentUser.name}!
+
+{/if}
+```
+
+### Translate Text
+```svelte
+
+
+{$t('login')}
+{$t('loginWith', { provider: 'Google' })}
+```
+
+### Create a New Page
+```svelte
+
+
+
+
+ My Page
+
+
+
+
+ My Page
+
+```
+
+---
+
+## Available Scripts
+
+```bash
+pnpm dev # Start dev server (hot reload)
+pnpm build # Build for production → ../frontend/
+pnpm preview # Preview production build
+pnpm check # Type check
+```
+
+---
+
+## Troubleshooting
+
+### "Module not found" errors
+```bash
+pnpm install
+```
+
+### TypeScript errors
+```bash
+pnpm check
+```
+
+### Vite not starting
+```bash
+rm -rf node_modules .svelte-kit
+pnpm install
+pnpm dev
+```
+
+### API calls fail
+- Check `PUBLIC_API_URL` in `.env`
+- Ensure backend is running on `http://localhost:3000`
+- Check browser console for CORS errors
+
+### OAuth redirect fails
+- Backend must handle `/auth/google` and `/auth/twitch`
+- If Turnstile is enabled, check token is being sent
+- Verify backend sets `j` cookie correctly
+
+### Turnstile not showing
+- Check `PUBLIC_ENABLE_TURNSTILE=true` (string, not boolean)
+- Verify `PUBLIC_TURNSTILE_SITE_KEY` is correct
+- Look for script errors in browser console
+
+---
+
+## Resources
+
+- **Full Build Guide**: [BUILD.md](BUILD.md)
+- **SvelteKit Docs**: https://kit.svelte.dev/docs
+- **DaisyUI Components**: https://daisyui.com/components/
+- **TailwindCSS**: https://tailwindcss.com/docs
+- **Leaflet**: https://leafletjs.com/
+
+---
+
+## Questions?
+
+See full documentation:
+- [QUICKSTART.md](QUICKSTART.md) - This file
+- [BUILD.md](BUILD.md) - Build configuration & deployment
+- [README.md](README.md) - Overview
+- [IMPLEMENTATION_NOTES.md](IMPLEMENTATION_NOTES.md) - Technical details
+- [BUILD_STATUS.md](BUILD_STATUS.md) - What's done and what's next
diff --git a/frontend-src/README.md b/frontend-src/README.md
new file mode 100644
index 0000000..d08ee9b
--- /dev/null
+++ b/frontend-src/README.md
@@ -0,0 +1,132 @@
+# Openplace Frontend Source
+
+Reconstructed SvelteKit frontend for Openplace, based on analysis of compiled production build.
+
+## Quick Start
+
+```bash
+# Install dependencies
+pnpm install
+
+# Set up environment
+cp .env.example .env
+
+# Run development server
+pnpm dev
+
+# Build for production
+pnpm build
+```
+
+## Project Structure
+
+```
+src/
+├── lib/
+│ ├── api/
+│ │ └── client.ts # API client with all backend endpoints
+│ ├── constants/
+│ │ ├── config.ts # App configuration
+│ │ └── colors.ts # 64-color palette
+│ ├── stores/
+│ │ ├── global.ts # Global state (turnstile, language, time)
+│ │ ├── user.ts # Current user + auth
+│ │ ├── canvas.ts # Canvas/map state
+│ │ ├── alliance.ts # Alliance state
+│ │ └── toast.ts # Toast notifications
+│ ├── utils/
+│ │ ├── bitmap.ts # Bitmap for unlocked colors/flags
+│ │ ├── charges.ts # Charge regeneration calculations
+│ │ └── level.ts # Level/XP calculations
+│ ├── components/ # To be created
+│ └── i18n/ # To be created
+├── routes/ # SvelteKit routes (to be created)
+└── app.css # TailwindCSS + DaisyUI styles
+```
+
+## Features Implemented
+
+### ✅ Core Infrastructure
+- API client with all backend endpoints
+- Svelte stores for state management
+- Utility functions (bitmap, charges, levels)
+- Constants (colors, config, store items)
+
+### 🚧 In Progress
+- i18n translation system (EN/PT)
+- UI components
+- Auth pages
+- Canvas/map implementation
+- Admin/moderation panels
+
+## Tech Stack
+
+- **SvelteKit** 2.x - Framework
+- **TypeScript** - Type safety
+- **TailwindCSS** - Utility-first CSS
+- **DaisyUI** 4.x - Component library
+- **Leaflet** - Map/canvas
+- **Cloudflare Turnstile** - Bot protection
+
+## Key Patterns
+
+### API Calls
+```typescript
+import { api } from '$lib/api/client';
+
+// Fetch user profile
+const user = await api.getMe();
+
+// Paint pixels
+await api.paintPixels('s1', tileX, tileY, {
+ colors: [7, 7, 7],
+ coords: [[100, 100], [101, 100], [102, 100]]
+});
+```
+
+### Stores
+```typescript
+import { currentUser, isAuthenticated } from '$lib/stores/user';
+import { toast } from '$lib/stores/toast';
+
+// Subscribe to user
+$: user = $currentUser;
+
+// Show notification
+toast.success('Welcome!');
+```
+
+### Colors
+```typescript
+import { COLOR_PALETTE, getColorHex, isColorUnlocked } from '$lib/constants/colors';
+
+const hex = getColorHex(7); // '#ed1c24'
+const unlocked = isColorUnlocked(32, user.extraColorsBitmap);
+```
+
+## Environment Variables
+
+```env
+PUBLIC_API_URL=http://localhost:3000
+PUBLIC_SEASON=s1
+```
+
+## Next Steps
+
+1. Create i18n translation system
+2. Build common components (Button, Modal, Toast, etc.)
+3. Create Turnstile component
+4. Build auth/login page
+5. Implement main canvas with Leaflet
+6. Create alliance, leaderboard, store pages
+7. Build admin and moderation panels
+
+## Development Notes
+
+- Uses DaisyUI's light theme only
+- Pixelated rendering for tile images
+- Charge regeneration updates every 500ms
+- JWT auth via `j` HttpOnly cookie
+- OAuth providers: Google, Twitch
+
+See [IMPLEMENTATION_NOTES.md](IMPLEMENTATION_NOTES.md) for detailed findings from compiled frontend analysis.
diff --git a/frontend-src/README_TURNSTILE.md b/frontend-src/README_TURNSTILE.md
new file mode 100644
index 0000000..886b260
--- /dev/null
+++ b/frontend-src/README_TURNSTILE.md
@@ -0,0 +1,277 @@
+# Turnstile Configuration Guide
+
+Cloudflare Turnstile is **optional** and can be completely disabled for development or self-hosted deployments.
+
+## Quick Config
+
+### Disable Turnstile (Recommended for Development)
+
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+```
+
+**What happens:**
+- ✅ No Turnstile widget loads
+- ✅ No captcha required to log in
+- ✅ Login buttons work immediately
+- ✅ OAuth URLs don't include `token` parameter
+- ✅ No external script loaded from Cloudflare
+- ✅ Completely works offline
+
+**Perfect for:**
+- Local development
+- Self-hosted deployments
+- Testing without internet
+
+---
+
+### Enable Turnstile (Production)
+
+```env
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAABpqJe8FO0N84q0F
+```
+
+**What happens:**
+- 🔒 Turnstile widget loads on login page
+- 🔒 User must complete captcha before login
+- 🔒 OAuth URLs include `?token=...` parameter
+- 🔒 Script loaded from `challenges.cloudflare.com`
+
+**Perfect for:**
+- Production deployments
+- Bot protection
+- Public-facing instances
+
+---
+
+## How It Works
+
+### Frontend Behavior
+
+**When `ENABLE_TURNSTILE=false`:**
+```typescript
+// Turnstile.svelte
+if (!ENABLE_TURNSTILE) {
+ captcha.set('turnstile-disabled'); // Dummy token
+}
+
+// LoginForm.svelte
+function getOAuthUrl(provider) {
+ let url = `${API_URL}/auth/${provider}`;
+ // No token parameter added
+ return url;
+}
+```
+
+**When `ENABLE_TURNSTILE=true`:**
+```typescript
+// Turnstile.svelte loads script
+
+
+// LoginForm.svelte
+function getOAuthUrl(provider) {
+ let url = `${API_URL}/auth/${provider}?token=${captchaToken}`;
+ return url;
+}
+```
+
+### Backend Requirements
+
+Your backend should handle both cases:
+
+```typescript
+// Example: src/routes/auth.ts
+app.post("/auth/google", async (req, res) => {
+ const turnstileToken = req.query.token;
+
+ // If Turnstile is enabled in your backend, validate token
+ if (process.env.ENABLE_TURNSTILE === 'true') {
+ if (!turnstileToken) {
+ return res.status(400).json({ error: "Turnstile token required" });
+ }
+
+ // Validate with Cloudflare API
+ const isValid = await validateTurnstileToken(turnstileToken);
+ if (!isValid) {
+ return res.status(403).json({ error: "Invalid captcha" });
+ }
+ }
+
+ // Continue with OAuth flow...
+});
+```
+
+---
+
+## Getting a Turnstile Site Key
+
+If you want to enable Turnstile:
+
+1. Go to https://dash.cloudflare.com
+2. Select "Turnstile" from the sidebar
+3. Create a new site
+4. Copy the **Site Key** (starts with `0x4`)
+5. Add to `.env`:
+ ```env
+ PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAXXXXXXXXXXXXXXXX
+ ```
+
+**Note:** The example key `0x4AAAAAABpqJe8FO0N84q0F` is from the original compiled frontend and may not work for you. Get your own key.
+
+---
+
+## Build-Time vs Runtime
+
+**Important:** Turnstile config is **build-time only**. It's embedded into the JavaScript bundle.
+
+To change Turnstile settings:
+1. Update `.env`
+2. Rebuild: `pnpm build`
+3. Restart backend
+
+You **cannot** change it at runtime without rebuilding.
+
+---
+
+## Security Considerations
+
+### With Turnstile Disabled
+
+**Pros:**
+- Simpler development
+- Works offline
+- No external dependencies
+- No privacy concerns
+
+**Cons:**
+- No bot protection on login
+- Vulnerable to automated account creation
+- Anyone can spam OAuth endpoints
+
+**Recommendation:** Fine for development and private deployments. Not recommended for public production.
+
+### With Turnstile Enabled
+
+**Pros:**
+- Bot protection
+- Rate limiting
+- Industry standard (Cloudflare)
+
+**Cons:**
+- Requires internet connection
+- External dependency
+- Cloudflare can track users (privacy concern)
+- Adds friction to login flow
+
+**Recommendation:** Good for public production deployments with high traffic.
+
+---
+
+## Environment Variable Reference
+
+| Variable | Type | Default | Description |
+|----------|------|---------|-------------|
+| `PUBLIC_ENABLE_TURNSTILE` | `'true'` \| `'false'` | `'false'` | Enable/disable Turnstile |
+| `PUBLIC_TURNSTILE_SITE_KEY` | string | `'0x4AAAAAABpqJe8FO0N84q0F'` | Cloudflare site key |
+
+**Note:** Must be strings `'true'` or `'false'`, not booleans.
+
+---
+
+## Testing
+
+### Test with Turnstile Disabled
+```bash
+# .env
+PUBLIC_ENABLE_TURNSTILE=false
+
+# Build and test
+pnpm build
+cd .. && pnpm start
+# Open http://localhost:3000/join
+# Should see login buttons immediately clickable
+```
+
+### Test with Turnstile Enabled
+```bash
+# .env
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=your_key_here
+
+# Build and test
+pnpm build
+cd .. && pnpm start
+# Open http://localhost:3000/join
+# Should see Turnstile widget before buttons work
+```
+
+---
+
+## Troubleshooting
+
+### "Turnstile failed to load"
+- Check `PUBLIC_TURNSTILE_SITE_KEY` is valid
+- Ensure internet connection
+- Check browser console for errors
+- Verify CSP allows `challenges.cloudflare.com`
+
+### "Login buttons don't work"
+- If Turnstile is enabled, complete captcha first
+- If disabled, check that `captcha` store has value
+- Check browser console for errors
+
+### "Token parameter missing"
+- Set `PUBLIC_ENABLE_TURNSTILE=false` if you don't want Turnstile
+- Rebuild after changing `.env`
+
+---
+
+## CSP Configuration
+
+If you enable Turnstile, your CSP must allow:
+
+```html
+
+```
+
+This is already configured in `src/app.html`.
+
+---
+
+## Alternative: reCAPTCHA
+
+Want to use Google reCAPTCHA instead? You'll need to:
+
+1. Create a new `Recaptcha.svelte` component
+2. Update `LoginForm.svelte` to use it
+3. Load reCAPTCHA script instead of Turnstile
+4. Update backend to validate reCAPTCHA tokens
+
+The architecture supports swapping captcha providers easily.
+
+---
+
+## Summary
+
+**For Development:**
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+```
+→ No captcha, login works immediately
+
+**For Production (with bot protection):**
+```env
+PUBLIC_ENABLE_TURNSTILE=true
+PUBLIC_TURNSTILE_SITE_KEY=your_cloudflare_key
+```
+→ Captcha required before login
+
+**For Production (without bot protection):**
+```env
+PUBLIC_ENABLE_TURNSTILE=false
+```
+→ Same as development, but publicly accessible
+
+Choose based on your needs!
diff --git a/frontend-src/package-lock.json b/frontend-src/package-lock.json
new file mode 100644
index 0000000..dfd28de
--- /dev/null
+++ b/frontend-src/package-lock.json
@@ -0,0 +1,3369 @@
+{
+ "name": "openplace-frontend",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "openplace-frontend",
+ "version": "0.0.1",
+ "dependencies": {
+ "leaflet": "^1.9.4"
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-static": "^3.0.1",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "@types/leaflet": "^1.9.8",
+ "autoprefixer": "^10.4.16",
+ "daisyui": "^4.4.24",
+ "postcss": "^8.4.32",
+ "svelte": "^4.2.7",
+ "svelte-check": "^3.6.0",
+ "tailwindcss": "^3.3.6",
+ "typescript": "^5.0.0",
+ "vite": "^5.0.3"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-win32-x64-msvc": "^4.52.3"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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/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/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
+ "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
+ "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
+ "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
+ "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
+ "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
+ "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
+ "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
+ "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
+ "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
+ "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
+ "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
+ "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
+ "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
+ "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
+ "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
+ "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
+ "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
+ "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
+ "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
+ "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
+ "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
+ "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sveltejs/acorn-typescript": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
+ "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^8.9.0"
+ }
+ },
+ "node_modules/@sveltejs/adapter-static": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.9.tgz",
+ "integrity": "sha512-aytHXcMi7lb9ljsWUzXYQ0p5X1z9oWud2olu/EpmH7aCu4m84h7QLvb5Wp+CFirKcwoNnYvYWhyP/L8Vh1ztdw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@sveltejs/kit": "^2.0.0"
+ }
+ },
+ "node_modules/@sveltejs/kit": {
+ "version": "2.43.7",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.43.7.tgz",
+ "integrity": "sha512-6trpyltB9XZNkM8cfVHG9U2urAH4NPD7UeO0wiBvZjD8gHj6w9bVeWnBQgnO8LPNpzOhSlwnZDk355OOAa/9Zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/cookie": "^0.6.0",
+ "acorn": "^8.14.1",
+ "cookie": "^0.6.0",
+ "devalue": "^5.3.2",
+ "esm-env": "^1.2.2",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.5",
+ "mrmime": "^2.0.0",
+ "sade": "^1.8.1",
+ "set-cookie-parser": "^2.6.0",
+ "sirv": "^3.0.0"
+ },
+ "bin": {
+ "svelte-kit": "svelte-kit.js"
+ },
+ "engines": {
+ "node": ">=18.13"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0",
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
+ "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
+ "debug": "^4.3.4",
+ "deepmerge": "^4.3.1",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.10",
+ "svelte-hmr": "^0.16.0",
+ "vitefu": "^0.2.5"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20"
+ },
+ "peerDependencies": {
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
+ "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/leaflet": {
+ "version": "1.9.20",
+ "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
+ "integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/pug": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
+ "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/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/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.10",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz",
+ "integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "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/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.26.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
+ "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "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.9",
+ "caniuse-lite": "^1.0.30001746",
+ "electron-to-chromium": "^1.5.227",
+ "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-crc32": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
+ "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001746",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz",
+ "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==",
+ "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/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/code-red": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
+ "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.10.0",
+ "estree-walker": "^3.0.3",
+ "periscopic": "^3.1.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-selector-tokenizer": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
+ "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "fastparse": "^1.1.2"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/culori": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
+ "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/daisyui": {
+ "version": "4.12.24",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.24.tgz",
+ "integrity": "sha512-JYg9fhQHOfXyLadrBrEqCDM6D5dWCSSiM6eTNCRrBRzx/VlOCrLS8eDfIw9RVvs64v2mJdLooKXY8EwQzoszAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-selector-tokenizer": "^0.8",
+ "culori": "^3",
+ "picocolors": "^1",
+ "postcss-js": "^4"
+ },
+ "engines": {
+ "node": ">=16.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/daisyui"
+ }
+ },
+ "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/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-indent": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+ "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/devalue": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz",
+ "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.228",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz",
+ "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es6-promise": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "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/esm-env": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
+ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+ "dev": true,
+ "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/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastparse": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "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/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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"
+ },
+ "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/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/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/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/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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-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-reference": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.6"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/leaflet": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
+ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "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-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.19",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.21",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
+ "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/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-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/periscopic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+ "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^3.0.0",
+ "is-reference": "^3.0.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
+ "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1",
+ "yaml": "^2.4.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "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/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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/rollup": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
+ "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.52.3",
+ "@rollup/rollup-android-arm64": "4.52.3",
+ "@rollup/rollup-darwin-arm64": "4.52.3",
+ "@rollup/rollup-darwin-x64": "4.52.3",
+ "@rollup/rollup-freebsd-arm64": "4.52.3",
+ "@rollup/rollup-freebsd-x64": "4.52.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.3",
+ "@rollup/rollup-linux-arm64-musl": "4.52.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.3",
+ "@rollup/rollup-linux-x64-gnu": "4.52.3",
+ "@rollup/rollup-linux-x64-musl": "4.52.3",
+ "@rollup/rollup-openharmony-arm64": "4.52.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.3",
+ "@rollup/rollup-win32-x64-gnu": "4.52.3",
+ "@rollup/rollup-win32-x64-msvc": "4.52.3",
+ "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",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/sander": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
+ "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es6-promise": "^3.1.2",
+ "graceful-fs": "^4.1.3",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.2"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sirv": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/sorcery": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.1.tgz",
+ "integrity": "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.14",
+ "buffer-crc32": "^1.0.0",
+ "minimist": "^1.2.0",
+ "sander": "^0.5.0"
+ },
+ "bin": {
+ "sorcery": "bin/sorcery"
+ }
+ },
+ "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/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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/svelte": {
+ "version": "4.2.20",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.20.tgz",
+ "integrity": "sha512-eeEgGc2DtiUil5ANdtd8vPwt9AgaMdnuUFnPft9F5oMvU/FHu5IHFic+p1dR/UOB7XU2mX2yHW+NcTch4DCh5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.9.0",
+ "aria-query": "^5.3.0",
+ "axobject-query": "^4.0.0",
+ "code-red": "^1.0.3",
+ "css-tree": "^2.3.1",
+ "estree-walker": "^3.0.3",
+ "is-reference": "^3.0.1",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.4",
+ "periscopic": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/svelte-check": {
+ "version": "3.8.6",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.6.tgz",
+ "integrity": "sha512-ij0u4Lw/sOTREP13BdWZjiXD/BlHE6/e2e34XzmVmsp5IN4kVa3PWP65NM32JAgwjZlwBg/+JtiNV1MM8khu0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "chokidar": "^3.4.1",
+ "picocolors": "^1.0.0",
+ "sade": "^1.7.4",
+ "svelte-preprocess": "^5.1.3",
+ "typescript": "^5.0.3"
+ },
+ "bin": {
+ "svelte-check": "bin/svelte-check"
+ },
+ "peerDependencies": {
+ "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
+ }
+ },
+ "node_modules/svelte-hmr": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
+ "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.20 || ^14.13.1 || >= 16"
+ },
+ "peerDependencies": {
+ "svelte": "^3.19.0 || ^4.0.0"
+ }
+ },
+ "node_modules/svelte-preprocess": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",
+ "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/pug": "^2.0.6",
+ "detect-indent": "^6.1.0",
+ "magic-string": "^0.30.5",
+ "sorcery": "^0.11.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.10.2",
+ "coffeescript": "^2.5.1",
+ "less": "^3.11.3 || ^4.0.0",
+ "postcss": "^7 || ^8",
+ "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
+ "pug": "^3.0.0",
+ "sass": "^1.26.8",
+ "stylus": "^0.55.0",
+ "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
+ "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
+ "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "coffeescript": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "postcss-load-config": {
+ "optional": true
+ },
+ "pug": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
+ "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/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/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.20",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
+ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
+ "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/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"
+ },
+ "node_modules/yaml": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
+ "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ }
+ }
+ }
+}
diff --git a/frontend-src/package.json b/frontend-src/package.json
new file mode 100644
index 0000000..634dbad
--- /dev/null
+++ b/frontend-src/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "openplace-frontend",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-static": "^3.0.1",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "@types/leaflet": "^1.9.8",
+ "autoprefixer": "^10.4.16",
+ "daisyui": "^4.4.24",
+ "postcss": "^8.4.32",
+ "svelte": "^4.2.7",
+ "svelte-check": "^3.6.0",
+ "tailwindcss": "^3.3.6",
+ "typescript": "^5.0.0",
+ "vite": "^5.0.3"
+ },
+ "dependencies": {
+ "leaflet": "^1.9.4"
+ },
+ "type": "module",
+ "optionalDependencies": {
+ "@rollup/rollup-win32-x64-msvc": "^4.52.3"
+ }
+}
diff --git a/frontend-src/postcss.config.js b/frontend-src/postcss.config.js
new file mode 100644
index 0000000..0f77216
--- /dev/null
+++ b/frontend-src/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {}
+ }
+};
diff --git a/frontend-src/src/app.css b/frontend-src/src/app.css
new file mode 100644
index 0000000..2651266
--- /dev/null
+++ b/frontend-src/src/app.css
@@ -0,0 +1,27 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@font-face {
+ font-family: 'PixelifySans';
+ src: url('/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2') format('woff2');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+
+.pixelated {
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: crisp-edges;
+}
+
+/* Leaflet overrides */
+.leaflet-container {
+ background: #f8f4f0;
+ font-family: inherit;
+}
+
+.leaflet-popup-content-wrapper {
+ border-radius: 0.5rem;
+}
diff --git a/frontend-src/src/app.html b/frontend-src/src/app.html
new file mode 100644
index 0000000..4e3ceb9
--- /dev/null
+++ b/frontend-src/src/app.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+ openplace - Paint the world
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
+
+
+
+
+
diff --git a/frontend-src/src/lib/api/client.ts b/frontend-src/src/lib/api/client.ts
new file mode 100644
index 0000000..95e915d
--- /dev/null
+++ b/frontend-src/src/lib/api/client.ts
@@ -0,0 +1,275 @@
+import { API_URL } from '$lib/constants/config';
+
+export class ApiError extends Error {
+ constructor(
+ public status: number,
+ message: string
+ ) {
+ super(message);
+ this.name = 'ApiError';
+ }
+}
+
+export class ApiClient {
+ private baseUrl: string;
+ public online: boolean = true;
+
+ constructor(baseUrl: string = API_URL) {
+ this.baseUrl = baseUrl;
+ }
+
+ async request(endpoint: string, options: RequestInit = {}): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
+ credentials: 'include',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+ });
+
+ this.online = true;
+
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
+ throw new ApiError(response.status, error.error || 'Request failed');
+ }
+
+ // Handle empty responses
+ const text = await response.text();
+ return text ? JSON.parse(text) : null;
+ } catch (error) {
+ if (error instanceof ApiError) throw error;
+ this.online = false;
+ throw new ApiError(0, error instanceof Error ? error.message : 'Network error');
+ }
+ }
+
+ // Auth endpoints
+ async logout() {
+ return this.request('/auth/logout', { method: 'POST' });
+ }
+
+ // User endpoints
+ async getMe() {
+ return this.request('/me');
+ }
+
+ async updateMe(data: { name?: string; showLastPixel?: boolean; discord?: string }) {
+ return this.request('/me/update', {
+ method: 'POST',
+ body: JSON.stringify(data)
+ });
+ }
+
+ async getProfilePictures() {
+ return this.request('/me/profile-pictures');
+ }
+
+ // Pixel endpoints
+ async getRandomTile(season: string) {
+ return this.request(`/${season}/tile/random`);
+ }
+
+ async getPixelInfo(season: string, tileX: number, tileY: number, x: number, y: number) {
+ return this.request(`/${season}/pixel/${tileX}/${tileY}?x=${x}&y=${y}`);
+ }
+
+ async paintPixels(
+ season: string,
+ tileX: number,
+ tileY: number,
+ data: { colors: number[]; coords: number[] }
+ ) {
+ return this.request(`/${season}/pixel/${tileX}/${tileY}`, {
+ method: 'POST',
+ body: JSON.stringify(data)
+ });
+ }
+
+ getTileImageUrl(season: string, tileX: number, tileY: number): string {
+ return `${this.baseUrl}/files/${season}/tiles/${tileX}/${tileY}.png`;
+ }
+
+ // Alliance endpoints
+ async getAlliance() {
+ return this.request('/alliance');
+ }
+
+ async createAlliance(name: string) {
+ return this.request('/alliance', {
+ method: 'POST',
+ body: JSON.stringify({ name })
+ });
+ }
+
+ async updateAllianceDescription(description: string) {
+ return this.request('/alliance/update-description', {
+ method: 'POST',
+ body: JSON.stringify({ description })
+ });
+ }
+
+ async updateAllianceHeadquarters(latitude: number, longitude: number) {
+ return this.request('/alliance/update-headquarters', {
+ method: 'POST',
+ body: JSON.stringify({ latitude, longitude })
+ });
+ }
+
+ async getAllianceInvites() {
+ return this.request('/alliance/invites');
+ }
+
+ async joinAlliance(inviteCode: string) {
+ return this.request(`/alliance/join/${inviteCode}`);
+ }
+
+ async getAllianceMembers(page: number) {
+ return this.request(`/alliance/members/${page}`);
+ }
+
+ async getBannedMembers(page: number) {
+ return this.request(`/alliance/members/banned/${page}`);
+ }
+
+ async promoteUser(promotedUserId: number) {
+ return this.request('/alliance/give-admin', {
+ method: 'POST',
+ body: JSON.stringify({ promotedUserId })
+ });
+ }
+
+ async banUser(bannedUserId: number) {
+ return this.request('/alliance/ban', {
+ method: 'POST',
+ body: JSON.stringify({ bannedUserId })
+ });
+ }
+
+ async unbanUser(unbannedUserId: number) {
+ return this.request('/alliance/unban', {
+ method: 'POST',
+ body: JSON.stringify({ unbannedUserId })
+ });
+ }
+
+ async getAllianceLeaderboard(mode: string) {
+ return this.request(`/alliance/leaderboard/${mode}`);
+ }
+
+ // Leaderboard endpoints
+ async getPlayerLeaderboard(mode: string) {
+ return this.request(`/leaderboard/player/${mode}`);
+ }
+
+ async getAllianceLeaderboardGlobal(mode: string) {
+ return this.request(`/leaderboard/alliance/${mode}`);
+ }
+
+ async getCountryLeaderboard(mode: string) {
+ return this.request(`/leaderboard/country/${mode}`);
+ }
+
+ async getRegionLeaderboard(mode: string, country: string) {
+ return this.request(`/leaderboard/region/${mode}/${country}`);
+ }
+
+ // Store endpoints
+ async purchase(product: { id: number; amount?: number; variant?: number }) {
+ return this.request('/purchase', {
+ method: 'POST',
+ body: JSON.stringify({ product })
+ });
+ }
+
+ async equipFlag(flagId: number) {
+ return this.request(`/flag/equip/${flagId}`, { method: 'POST' });
+ }
+
+ // Favorite locations
+ async addFavoriteLocation(data: { name: string; latitude: number; longitude: number }) {
+ return this.request('/favorite-location', {
+ method: 'POST',
+ body: JSON.stringify(data)
+ });
+ }
+
+ async updateFavoriteLocation(
+ id: number,
+ data: { name: string; latitude: number; longitude: number }
+ ) {
+ return this.request('/favorite-location/update', {
+ method: 'POST',
+ body: JSON.stringify({ id, ...data })
+ });
+ }
+
+ // Moderation endpoints
+ async getModeratorTickets() {
+ return this.request('/moderator/tickets');
+ }
+
+ async getSevereTicketsCount() {
+ return this.request('/moderator/severe-open-tickets-count', { method: 'POST' });
+ }
+
+ async getOpenTicketsCount() {
+ return this.request('/moderator/open-tickets-count');
+ }
+
+ // Admin endpoints
+ async getUser(id: number) {
+ return this.request(`/admin/users?id=${id}`);
+ }
+
+ async getUserNotes(userId: number) {
+ return this.request(`/admin/users/notes?userId=${userId}`);
+ }
+
+ async addUserNote(userId: number, note: string) {
+ return this.request('/admin/users/notes', {
+ method: 'POST',
+ body: JSON.stringify({ userId, note })
+ });
+ }
+
+ async setUserDroplets(userId: number, droplets: number) {
+ return this.request('/admin/users/set-user-droplets', {
+ method: 'POST',
+ body: JSON.stringify({ userId, droplets })
+ });
+ }
+
+ async getAdminTickets() {
+ return this.request('/admin/tickets');
+ }
+
+ async getClosedTickets() {
+ return this.request('/admin/closed-tickets');
+ }
+
+ async getOpenTicketsCountAdmin() {
+ return this.request('/admin/open-tickets-count');
+ }
+
+ async countAllTickets() {
+ return this.request('/admin/count-all-tickets');
+ }
+
+ async searchAlliances(query: string) {
+ return this.request(`/admin/alliances/search?q=${encodeURIComponent(query)}`);
+ }
+
+ async getAllianceById(id: number) {
+ return this.request(`/admin/alliances/${id}`);
+ }
+
+ async getAllianceByIdFull(id: number) {
+ return this.request(`/admin/alliances/${id}/full`);
+ }
+}
+
+// Export singleton instance
+export const api = new ApiClient();
diff --git a/frontend-src/src/lib/components/auth/LoginForm.svelte b/frontend-src/src/lib/components/auth/LoginForm.svelte
new file mode 100644
index 0000000..dd44179
--- /dev/null
+++ b/frontend-src/src/lib/components/auth/LoginForm.svelte
@@ -0,0 +1,106 @@
+
+
+
diff --git a/frontend-src/src/lib/components/auth/Turnstile.svelte b/frontend-src/src/lib/components/auth/Turnstile.svelte
new file mode 100644
index 0000000..616feea
--- /dev/null
+++ b/frontend-src/src/lib/components/auth/Turnstile.svelte
@@ -0,0 +1,96 @@
+
+
+
+
+{#if ENABLE_TURNSTILE}
+
+{:else}
+
+{/if}
diff --git a/frontend-src/src/lib/components/canvas/CanvasMap.svelte b/frontend-src/src/lib/components/canvas/CanvasMap.svelte
new file mode 100644
index 0000000..ddd9527
--- /dev/null
+++ b/frontend-src/src/lib/components/canvas/CanvasMap.svelte
@@ -0,0 +1,152 @@
+
+
+
+
+
diff --git a/frontend-src/src/lib/components/canvas/ColorPalette.svelte b/frontend-src/src/lib/components/canvas/ColorPalette.svelte
new file mode 100644
index 0000000..74c2715
--- /dev/null
+++ b/frontend-src/src/lib/components/canvas/ColorPalette.svelte
@@ -0,0 +1,61 @@
+
+
+
+
+ Free Colors
+
+ {#each freeColors as color}
+ handleSelect(event.detail)} />
+ {/each}
+
+
+
+
+ Paid Colors
+ 2000 droplets
+
+
+ {#each paidColors as color}
+ handleSelect(event.detail)} />
+ {/each}
+
+
+
diff --git a/frontend-src/src/lib/components/canvas/ColorSwatch.svelte b/frontend-src/src/lib/components/canvas/ColorSwatch.svelte
new file mode 100644
index 0000000..aea6bab
--- /dev/null
+++ b/frontend-src/src/lib/components/canvas/ColorSwatch.svelte
@@ -0,0 +1,40 @@
+
+
+
diff --git a/frontend-src/src/lib/components/common/Button.svelte b/frontend-src/src/lib/components/common/Button.svelte
new file mode 100644
index 0000000..a796be5
--- /dev/null
+++ b/frontend-src/src/lib/components/common/Button.svelte
@@ -0,0 +1,39 @@
+
+
+{#if href}
+
+ {#if loading}
+
+ {/if}
+
+
+{:else}
+
+{/if}
diff --git a/frontend-src/src/lib/components/common/Modal.svelte b/frontend-src/src/lib/components/common/Modal.svelte
new file mode 100644
index 0000000..7f3acec
--- /dev/null
+++ b/frontend-src/src/lib/components/common/Modal.svelte
@@ -0,0 +1,60 @@
+
+
+
diff --git a/frontend-src/src/lib/components/common/Toast.svelte b/frontend-src/src/lib/components/common/Toast.svelte
new file mode 100644
index 0000000..b206290
--- /dev/null
+++ b/frontend-src/src/lib/components/common/Toast.svelte
@@ -0,0 +1,37 @@
+
+
+
+ {#each $toast as item (item.id)}
+
+ {item.message}
+
+
+ {/each}
+
diff --git a/frontend-src/src/lib/components/layout/Header.svelte b/frontend-src/src/lib/components/layout/Header.svelte
new file mode 100644
index 0000000..f92bd01
--- /dev/null
+++ b/frontend-src/src/lib/components/layout/Header.svelte
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+ {#if $currentUser}
+
+
{$currentUser.name}
+
+
+
+
+
+ {:else}
+
{$t('login')}
+ {/if}
+
+
+
diff --git a/frontend-src/src/lib/components/layout/Logo.svelte b/frontend-src/src/lib/components/layout/Logo.svelte
new file mode 100644
index 0000000..fc4d725
--- /dev/null
+++ b/frontend-src/src/lib/components/layout/Logo.svelte
@@ -0,0 +1,26 @@
+
+
+
+

+ {#if hasText}
+
+ wplace
+
+ {/if}
+
diff --git a/frontend-src/src/lib/constants/colors.ts b/frontend-src/src/lib/constants/colors.ts
new file mode 100644
index 0000000..20e36d5
--- /dev/null
+++ b/frontend-src/src/lib/constants/colors.ts
@@ -0,0 +1,74 @@
+export interface WplaceColor {
+ id: number;
+ name: string;
+ hex: string;
+ rgb: [number, number, number] | null;
+ paid: boolean;
+}
+
+export const COLORS: WplaceColor[] = [
+ { id: 0, name: 'Transparent', hex: 'transparent', rgb: null, paid: false },
+ { id: 1, name: 'Color 1', hex: '#000000', rgb: [0, 0, 0], paid: false },
+ { id: 2, name: 'Color 2', hex: '#3c3c3c', rgb: [60, 60, 60], paid: false },
+ { id: 3, name: 'Color 3', hex: '#787878', rgb: [120, 120, 120], paid: false },
+ { id: 4, name: 'Color 4', hex: '#d2d2d2', rgb: [210, 210, 210], paid: false },
+ { id: 5, name: 'Color 5', hex: '#ffffff', rgb: [255, 255, 255], paid: false },
+ { id: 6, name: 'Color 6', hex: '#600018', rgb: [96, 0, 24], paid: false },
+ { id: 7, name: 'Color 7', hex: '#ed1c24', rgb: [237, 28, 36], paid: false },
+ { id: 8, name: 'Color 8', hex: '#ff7f27', rgb: [255, 127, 39], paid: false },
+ { id: 9, name: 'Color 9', hex: '#f6aa09', rgb: [246, 170, 9], paid: false },
+ { id: 10, name: 'Color 10', hex: '#f9dd3b', rgb: [249, 221, 59], paid: false },
+ { id: 11, name: 'Color 11', hex: '#fffabc', rgb: [255, 250, 188], paid: false },
+ { id: 12, name: 'Color 12', hex: '#0eb968', rgb: [14, 185, 104], paid: false },
+ { id: 13, name: 'Color 13', hex: '#13e67b', rgb: [19, 230, 123], paid: false },
+ { id: 14, name: 'Color 14', hex: '#87ff5e', rgb: [135, 255, 94], paid: false },
+ { id: 15, name: 'Color 15', hex: '#0c816e', rgb: [12, 129, 110], paid: false },
+ { id: 16, name: 'Color 16', hex: '#10aea6', rgb: [16, 174, 166], paid: false },
+ { id: 17, name: 'Color 17', hex: '#13e1be', rgb: [19, 225, 190], paid: false },
+ { id: 18, name: 'Color 18', hex: '#28509e', rgb: [40, 80, 158], paid: false },
+ { id: 19, name: 'Color 19', hex: '#4093e4', rgb: [64, 147, 228], paid: false },
+ { id: 20, name: 'Color 20', hex: '#60f7f2', rgb: [96, 247, 242], paid: false },
+ { id: 21, name: 'Color 21', hex: '#6b50f6', rgb: [107, 80, 246], paid: false },
+ { id: 22, name: 'Color 22', hex: '#99b1fb', rgb: [153, 177, 251], paid: false },
+ { id: 23, name: 'Color 23', hex: '#780c99', rgb: [120, 12, 153], paid: false },
+ { id: 24, name: 'Color 24', hex: '#aa38b9', rgb: [170, 56, 185], paid: false },
+ { id: 25, name: 'Color 25', hex: '#e09ff9', rgb: [224, 159, 249], paid: false },
+ { id: 26, name: 'Color 26', hex: '#cb007a', rgb: [203, 0, 122], paid: false },
+ { id: 27, name: 'Color 27', hex: '#ec1f80', rgb: [236, 31, 128], paid: false },
+ { id: 28, name: 'Color 28', hex: '#f38da9', rgb: [243, 141, 169], paid: false },
+ { id: 29, name: 'Color 29', hex: '#684634', rgb: [104, 70, 52], paid: false },
+ { id: 30, name: 'Color 30', hex: '#95682a', rgb: [149, 104, 42], paid: false },
+ { id: 31, name: 'Color 31', hex: '#f8b277', rgb: [248, 178, 119], paid: false },
+ { id: 32, name: 'Color 32', hex: '#aaaaaa', rgb: [170, 170, 170], paid: true },
+ { id: 33, name: 'Color 33', hex: '#a50e1e', rgb: [165, 14, 30], paid: true },
+ { id: 34, name: 'Color 34', hex: '#fa8072', rgb: [250, 128, 114], paid: true },
+ { id: 35, name: 'Color 35', hex: '#e45c1a', rgb: [228, 92, 26], paid: true },
+ { id: 36, name: 'Color 36', hex: '#d6b594', rgb: [214, 181, 148], paid: true },
+ { id: 37, name: 'Color 37', hex: '#9c8431', rgb: [156, 132, 49], paid: true },
+ { id: 38, name: 'Color 38', hex: '#c5ad31', rgb: [197, 173, 49], paid: true },
+ { id: 39, name: 'Color 39', hex: '#e8d45f', rgb: [232, 212, 95], paid: true },
+ { id: 40, name: 'Color 40', hex: '#4a6b3a', rgb: [74, 107, 58], paid: true },
+ { id: 41, name: 'Color 41', hex: '#5a944a', rgb: [90, 148, 74], paid: true },
+ { id: 42, name: 'Color 42', hex: '#84c573', rgb: [132, 197, 115], paid: true },
+ { id: 43, name: 'Color 43', hex: '#0f799f', rgb: [15, 121, 159], paid: true },
+ { id: 44, name: 'Color 44', hex: '#bbfaf2', rgb: [187, 250, 242], paid: true },
+ { id: 45, name: 'Color 45', hex: '#7dc7ff', rgb: [125, 199, 255], paid: true },
+ { id: 46, name: 'Color 46', hex: '#4d31b8', rgb: [77, 49, 184], paid: true },
+ { id: 47, name: 'Color 47', hex: '#4a4284', rgb: [74, 66, 132], paid: true },
+ { id: 48, name: 'Color 48', hex: '#7a71c4', rgb: [122, 113, 196], paid: true },
+ { id: 49, name: 'Color 49', hex: '#b5aef1', rgb: [181, 174, 241], paid: true },
+ { id: 50, name: 'Color 50', hex: '#dba463', rgb: [219, 164, 99], paid: true },
+ { id: 51, name: 'Color 51', hex: '#d18051', rgb: [209, 128, 81], paid: true },
+ { id: 52, name: 'Color 52', hex: '#ffc5a5', rgb: [255, 197, 165], paid: true },
+ { id: 53, name: 'Color 53', hex: '#9b5249', rgb: [155, 82, 73], paid: true },
+ { id: 54, name: 'Color 54', hex: '#d18078', rgb: [209, 128, 120], paid: true },
+ { id: 55, name: 'Color 55', hex: '#fab6a4', rgb: [250, 182, 164], paid: true },
+ { id: 56, name: 'Color 56', hex: '#7b6352', rgb: [123, 99, 82], paid: true },
+ { id: 57, name: 'Color 57', hex: '#9c846b', rgb: [156, 132, 107], paid: true },
+ { id: 58, name: 'Color 58', hex: '#333941', rgb: [51, 57, 65], paid: true },
+ { id: 59, name: 'Color 59', hex: '#6d758d', rgb: [109, 117, 141], paid: true },
+ { id: 60, name: 'Color 60', hex: '#b3b9d1', rgb: [179, 185, 209], paid: true },
+ { id: 61, name: 'Color 61', hex: '#6d643f', rgb: [109, 100, 63], paid: true },
+ { id: 62, name: 'Color 62', hex: '#948c6b', rgb: [148, 140, 107], paid: true },
+ { id: 63, name: 'Color 63', hex: '#cdc59e', rgb: [205, 197, 158], paid: true },
+];
diff --git a/frontend-src/src/lib/constants/config.ts b/frontend-src/src/lib/constants/config.ts
new file mode 100644
index 0000000..02b49f9
--- /dev/null
+++ b/frontend-src/src/lib/constants/config.ts
@@ -0,0 +1,58 @@
+// API Configuration
+export const BACKEND_URL = import.meta.env.PUBLIC_BACKEND_URL || 'http://localhost:3000';
+export const FILES_URL = import.meta.env.PUBLIC_FILES_URL || `${BACKEND_URL}/files`;
+export const MAP_URL = import.meta.env.PUBLIC_MAP_URL || 'https://maps.wplace.live/styles/liberty';
+
+// Environment
+export const ENV = import.meta.env.PUBLIC_ENV || 'dev';
+export const MAINTENANCE = import.meta.env.PUBLIC_MAINTENANCE === 'true';
+
+// Cloudflare Turnstile (optional)
+export const TURNSTILE_ENABLED = import.meta.env.PUBLIC_TURNSTILE_ENABLED === 'true';
+export const TURNSTILE_SITE_KEY_LOGIN = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY_LOGIN || '0x4AAAAAABpHqZ-6i7uL0nmG';
+export const TURNSTILE_SITE_KEY_PAINT = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY_PAINT || '0x4AAAAAABpqJe8FO0N84q0F';
+
+// Legacy aliases for backward compatibility
+export const API_URL = BACKEND_URL;
+export const ENABLE_TURNSTILE = TURNSTILE_ENABLED;
+export const TURNSTILE_SITE_KEY = TURNSTILE_SITE_KEY_PAINT;
+
+// OAuth providers
+export const OAUTH_PROVIDERS = {
+ google: {
+ name: 'Google',
+ icon: 'google'
+ },
+ twitch: {
+ name: 'Twitch',
+ icon: 'twitch'
+ }
+} as const;
+
+// Files/Assets
+export const FILES_BASE = 'files';
+
+// Tile configuration
+export const TILE_SIZE = 1000;
+
+// Store items
+export const STORE_ITEMS = {
+ 70: { name: '+5 Max. Charges', price: 500, type: 'charges' },
+ 80: { name: '+30 Paint Charges', price: 500, type: 'paint' },
+ 100: { name: 'Unlock Paid Colors', price: 2000, type: 'color' },
+ 110: { name: 'Unlock Flag', price: 20_000, type: 'flag' }
+} as const;
+
+// Report reasons
+export const REPORT_REASONS = [
+ { key: 'inappropriate-content', label: 'Inappropriate Content' },
+ { key: 'hate-speech', label: 'Hate Speech' },
+ { key: 'doxxing', label: 'Doxxing' },
+ { key: 'bot', label: 'Bot' },
+ { key: 'griefing', label: 'Griefing' },
+ { key: 'other', label: 'Other' }
+] as const;
+
+// Leaderboard modes
+export const LEADERBOARD_MODES = ['today', 'week', 'month', 'all-time'] as const;
+export type LeaderboardMode = (typeof LEADERBOARD_MODES)[number];
diff --git a/frontend-src/src/lib/i18n/en.ts b/frontend-src/src/lib/i18n/en.ts
new file mode 100644
index 0000000..cba4939
--- /dev/null
+++ b/frontend-src/src/lib/i18n/en.ts
@@ -0,0 +1,124 @@
+export const en = {
+ // Common
+ close: 'Close',
+ cancel: 'Cancel',
+ save: 'Save',
+ delete: 'Delete',
+ edit: 'Edit',
+ loading: 'Loading...',
+ error: 'Error',
+ success: 'Success',
+ copy: 'Copy',
+ copied: 'Copied!',
+
+ // Auth
+ login: 'Login',
+ loginWith: 'Login with {provider}',
+ logout: 'Logout',
+ loginRequired: 'You must be logged in',
+ termsAgreement: 'By continuing, you agree to our',
+ termsOfService: 'Terms of Service',
+ privacyPolicy: 'Privacy Policy',
+ and: 'and',
+
+ // User
+ profile: 'Profile',
+ username: 'Username',
+ discord: 'Discord',
+ level: 'Level',
+ pixelsPainted: 'Pixels Painted',
+ droplets: 'Droplets',
+ charges: 'Charges',
+ showLastPixel: 'Show Last Pixel',
+
+ // Canvas
+ paint: 'Paint',
+ eyedropper: 'Eyedropper',
+ colorPicker: 'Color Picker',
+ freeColors: 'Free Colors',
+ paidColors: 'Paid Colors (2000 droplets each)',
+ selectColor: 'Select a color',
+ notEnoughCharges: 'Not enough charges',
+ colorLocked: 'Color is locked. Purchase in the store.',
+
+ // Alliance
+ alliance: 'Alliance',
+ createAlliance: 'Create Alliance',
+ allianceName: 'Alliance Name',
+ allianceDescription: 'Alliance Description',
+ members: 'Members',
+ invites: 'Invites',
+ headquarters: 'Headquarters',
+ joinAlliance: 'Join Alliance',
+ leaveAlliance: 'Leave Alliance',
+ allianceLeaderboard: 'Alliance Leaderboard',
+ promoteToAdmin: 'Promote to Admin',
+ banMember: 'Ban Member',
+ unbanMember: 'Unban Member',
+
+ // Leaderboard
+ leaderboard: 'Leaderboard',
+ playerLeaderboard: 'Player Leaderboard',
+ globalLeaderboard: 'Global Leaderboard',
+ today: 'Today',
+ week: 'Week',
+ month: 'Month',
+ allTime: 'All Time',
+ rank: 'Rank',
+ player: 'Player',
+
+ // Store
+ store: 'Store',
+ purchase: 'Purchase',
+ maxCharges: '+5 Max. Charges',
+ paintCharges: '+30 Paint Charges',
+ unlockColor: 'Unlock Paid Color',
+ unlockFlag: 'Unlock Flag',
+ insufficientDroplets: 'Insufficient droplets',
+ purchaseSuccess: 'Purchase successful!',
+ purchaseFailed: 'Purchase failed',
+
+ // Report
+ reportUser: 'Report User',
+ timeoutUser: 'Timeout User',
+ banUser: 'Ban User',
+ reportReason: 'Report Reason',
+ reportNotes: 'Notes',
+ reportSuccess: 'Report sent successfully',
+ reportFailed: 'Report failed. Please try again later',
+ inappropriateContent: 'Inappropriate Content',
+ inappropriateContentDesc: '+18, inappropriate link, highly suggestive content, ...',
+ hateSpeech: 'Hate Speech',
+ hateSpeechDesc: 'Racism, homophobia, hate groups, ...',
+ doxxing: 'Doxxing',
+ doxxingDesc: "Released other's personal information without their consent",
+ bot: 'Bot',
+ botDesc: 'Use of software to completely automate painting',
+ griefing: 'Griefing',
+ griefingDesc: 'Messed up artworks for no reason',
+ other: 'Other',
+ otherDesc: 'Other reason not listed',
+
+ // Admin
+ admin: 'Admin',
+ dashboard: 'Dashboard',
+ users: 'Users',
+ alliances: 'Alliances',
+ tickets: 'Tickets',
+ openTickets: 'Open Tickets',
+ closedTickets: 'Closed Tickets',
+ moderator: 'Moderator',
+ moderation: 'Moderation',
+
+ // Errors
+ unexpectedError: 'Unexpected server error. Try again later.',
+ networkError: 'Network error. Check your connection.',
+ unauthorized: 'Unauthorized',
+ forbidden: 'Forbidden',
+ notFound: 'Not found',
+
+ // PWA
+ installApp: 'Install App',
+ offline: 'You are offline',
+ online: 'You are back online'
+};
diff --git a/frontend-src/src/lib/i18n/index.ts b/frontend-src/src/lib/i18n/index.ts
new file mode 100644
index 0000000..e76b7c2
--- /dev/null
+++ b/frontend-src/src/lib/i18n/index.ts
@@ -0,0 +1,27 @@
+import { derived } from 'svelte/store';
+import { language } from '$lib/stores/global';
+import { en } from './en';
+import { pt } from './pt';
+
+export type Language = 'en' | 'pt';
+export type TranslationKey = keyof typeof en;
+
+const translations = { en, pt };
+
+export const t = derived(language, ($lang) => {
+ return (key: TranslationKey, params?: Record): string => {
+ let text = translations[$lang][key] || translations.en[key] || key;
+
+ if (params) {
+ Object.entries(params).forEach(([k, v]) => {
+ text = text.replace(`{${k}}`, String(v));
+ });
+ }
+
+ return text;
+ };
+});
+
+export function setLanguage(lang: Language) {
+ language.set(lang);
+}
diff --git a/frontend-src/src/lib/i18n/pt.ts b/frontend-src/src/lib/i18n/pt.ts
new file mode 100644
index 0000000..d63bc2a
--- /dev/null
+++ b/frontend-src/src/lib/i18n/pt.ts
@@ -0,0 +1,124 @@
+export const pt = {
+ // Common
+ close: 'Fechar',
+ cancel: 'Cancelar',
+ save: 'Salvar',
+ delete: 'Deletar',
+ edit: 'Editar',
+ loading: 'Carregando...',
+ error: 'Erro',
+ success: 'Sucesso',
+ copy: 'Copiar',
+ copied: 'Copiado!',
+
+ // Auth
+ login: 'Entrar',
+ loginWith: 'Entrar com {provider}',
+ logout: 'Sair',
+ loginRequired: 'Você precisa estar logado',
+ termsAgreement: 'Ao continuar, você concorda com nossos',
+ termsOfService: 'Termos de Serviço',
+ privacyPolicy: 'Política de privacidade',
+ and: 'e',
+
+ // User
+ profile: 'Perfil',
+ username: 'Nome de usuário',
+ discord: 'Discord',
+ level: 'Nível',
+ pixelsPainted: 'Pixels Pintados',
+ droplets: 'Gotas',
+ charges: 'Cargas',
+ showLastPixel: 'Mostrar Último Pixel',
+
+ // Canvas
+ paint: 'Pintar',
+ eyedropper: 'Conta-gotas',
+ colorPicker: 'Seletor de Cores',
+ freeColors: 'Cores Gratuitas',
+ paidColors: 'Cores Pagas (2000 gotas cada)',
+ selectColor: 'Selecione uma cor',
+ notEnoughCharges: 'Cargas insuficientes',
+ colorLocked: 'Cor bloqueada. Compre na loja.',
+
+ // Alliance
+ alliance: 'Aliança',
+ createAlliance: 'Criar Aliança',
+ allianceName: 'Nome da Aliança',
+ allianceDescription: 'Descrição da Aliança',
+ members: 'Membros',
+ invites: 'Convites',
+ headquarters: 'Quartel General',
+ joinAlliance: 'Entrar na Aliança',
+ leaveAlliance: 'Sair da Aliança',
+ allianceLeaderboard: 'Ranking da Aliança',
+ promoteToAdmin: 'Promover para Admin',
+ banMember: 'Banir Membro',
+ unbanMember: 'Desbanir Membro',
+
+ // Leaderboard
+ leaderboard: 'Ranking',
+ playerLeaderboard: 'Ranking de Jogadores',
+ globalLeaderboard: 'Ranking Global',
+ today: 'Hoje',
+ week: 'Semana',
+ month: 'Mês',
+ allTime: 'Todos os Tempos',
+ rank: 'Posição',
+ player: 'Jogador',
+
+ // Store
+ store: 'Loja',
+ purchase: 'Comprar',
+ maxCharges: '+5 Cargas Máx.',
+ paintCharges: '+30 Cargas de Pintura',
+ unlockColor: 'Desbloquear Cor Paga',
+ unlockFlag: 'Desbloquear Bandeira',
+ insufficientDroplets: 'Gotas insuficientes',
+ purchaseSuccess: 'Compra realizada com sucesso!',
+ purchaseFailed: 'Falha na compra',
+
+ // Report
+ reportUser: 'Reportar usuário',
+ timeoutUser: 'Suspender usuário',
+ banUser: 'Banir usuário',
+ reportReason: 'Motivo do Relatório',
+ reportNotes: 'Notas',
+ reportSuccess: 'Denúncia enviada com sucesso',
+ reportFailed: 'Denúncia falhou. Por favor, tente novamente mais tarde',
+ inappropriateContent: 'Conteúdo Inadequado',
+ inappropriateContentDesc: '+18, links inapropriados, conteúdo altamente sugestivo, ...',
+ hateSpeech: 'Discurso de Ódio',
+ hateSpeechDesc: 'Racismo, homofobia, grupos de ódio, ...',
+ doxxing: 'Doxxing',
+ doxxingDesc: 'Vazar informações pessoais de terceiros sem consentimento',
+ bot: 'Bot',
+ botDesc: 'Uso de software para pintar de forma completamente automatizada',
+ griefing: 'Griefing',
+ griefingDesc: 'Estragar desenho dos outros sem motivo',
+ other: 'Outro',
+ otherDesc: 'Outro motivo não listado',
+
+ // Admin
+ admin: 'Admin',
+ dashboard: 'Painel',
+ users: 'Usuários',
+ alliances: 'Alianças',
+ tickets: 'Tickets',
+ openTickets: 'Tickets Abertos',
+ closedTickets: 'Tickets Fechados',
+ moderator: 'Moderador',
+ moderation: 'Moderação',
+
+ // Errors
+ unexpectedError: 'Erro inesperado do servidor. Tente novamente mais tarde.',
+ networkError: 'Erro de rede. Verifique sua conexão.',
+ unauthorized: 'Não autorizado',
+ forbidden: 'Proibido',
+ notFound: 'Não encontrado',
+
+ // PWA
+ installApp: 'Instalar App',
+ offline: 'Você está offline',
+ online: 'Você está online novamente'
+};
diff --git a/frontend-src/src/lib/stores/alliance.ts b/frontend-src/src/lib/stores/alliance.ts
new file mode 100644
index 0000000..ebe4a1c
--- /dev/null
+++ b/frontend-src/src/lib/stores/alliance.ts
@@ -0,0 +1,67 @@
+import { writable } from 'svelte/store';
+import { api } from '$lib/api/client';
+
+export interface Alliance {
+ id: number;
+ name: string;
+ description: string | null;
+ hqLatitude: number | null;
+ hqLongitude: number | null;
+ pixelsPainted: number;
+}
+
+function createAllianceStore() {
+ const { subscribe, set, update } = writable(null);
+
+ return {
+ subscribe,
+ set,
+ update,
+ async fetch() {
+ try {
+ const alliance = await api.getAlliance();
+ set(alliance);
+ return alliance;
+ } catch (error) {
+ console.error('Failed to fetch alliance:', error);
+ set(null);
+ return null;
+ }
+ },
+ async create(name: string) {
+ try {
+ const alliance = await api.createAlliance(name);
+ set(alliance);
+ return alliance;
+ } catch (error) {
+ console.error('Failed to create alliance:', error);
+ throw error;
+ }
+ },
+ async updateDescription(description: string) {
+ try {
+ const alliance = await api.updateAllianceDescription(description);
+ set(alliance);
+ return alliance;
+ } catch (error) {
+ console.error('Failed to update description:', error);
+ throw error;
+ }
+ },
+ async updateHeadquarters(lat: number, lng: number) {
+ try {
+ const alliance = await api.updateAllianceHeadquarters(lat, lng);
+ set(alliance);
+ return alliance;
+ } catch (error) {
+ console.error('Failed to update headquarters:', error);
+ throw error;
+ }
+ },
+ clear() {
+ set(null);
+ }
+ };
+}
+
+export const currentAlliance = createAllianceStore();
diff --git a/frontend-src/src/lib/stores/canvas.ts b/frontend-src/src/lib/stores/canvas.ts
new file mode 100644
index 0000000..e4b58be
--- /dev/null
+++ b/frontend-src/src/lib/stores/canvas.ts
@@ -0,0 +1,45 @@
+import { writable, derived } from 'svelte/store';
+
+export interface CanvasPosition {
+ lat: number;
+ lng: number;
+ zoom: number;
+}
+
+export interface SelectedPixel {
+ tileX: number;
+ tileY: number;
+ x: number;
+ y: number;
+}
+
+// Canvas state
+export const selectedColor = writable(1);
+export const canvasPosition = writable({
+ lat: 0,
+ lng: 0,
+ zoom: 3
+});
+
+export const selectedPixel = writable(null);
+export const visibleTiles = writable>(new Set());
+
+// Brush/tool state
+export const brushSize = writable(1);
+export const toolMode = writable<'paint' | 'eyedropper'>('paint');
+
+// Pixel info modal
+export const pixelInfoOpen = writable(false);
+
+// Helper to add visible tile
+export function addVisibleTile(tileX: number, tileY: number) {
+ visibleTiles.update((tiles) => {
+ tiles.add(`${tileX},${tileY}`);
+ return tiles;
+ });
+}
+
+// Helper to clear visible tiles
+export function clearVisibleTiles() {
+ visibleTiles.set(new Set());
+}
diff --git a/frontend-src/src/lib/stores/global.ts b/frontend-src/src/lib/stores/global.ts
new file mode 100644
index 0000000..7d7cf49
--- /dev/null
+++ b/frontend-src/src/lib/stores/global.ts
@@ -0,0 +1,36 @@
+import { writable, derived } from 'svelte/store';
+
+// Detect browser language
+function detectLanguage(): 'en' | 'pt' {
+ if (typeof navigator === 'undefined') return 'en';
+
+ if (navigator.languages && navigator.languages.length > 0) {
+ const twoLetterLang = navigator.languages.find((lang) => lang.length === 2);
+ if (twoLetterLang) {
+ return twoLetterLang === 'pt' ? 'pt' : 'en';
+ }
+ }
+
+ const lang =
+ (navigator.language || (navigator as any).userLanguage || (navigator as any).browserLanguage || 'en')
+ .substring(0, 2);
+ return lang === 'pt' ? 'pt' : 'en';
+}
+
+// Global state (matching compiled frontend)
+export const dropletsDialogOpen = writable(false);
+export const muted = writable(false);
+export const language = writable<'en' | 'pt'>(detectLanguage());
+export const captcha = writable(undefined);
+export const now = writable(Date.now());
+export const turnstileLoaded = writable(false);
+
+// Update time every 500ms (for charge regeneration)
+if (typeof window !== 'undefined') {
+ setInterval(() => {
+ now.set(Date.now());
+ }, 500);
+}
+
+// Derived online status
+export const online = writable(true);
diff --git a/frontend-src/src/lib/stores/toast.ts b/frontend-src/src/lib/stores/toast.ts
new file mode 100644
index 0000000..9b43745
--- /dev/null
+++ b/frontend-src/src/lib/stores/toast.ts
@@ -0,0 +1,48 @@
+import { writable } from 'svelte/store';
+
+export interface Toast {
+ id: string;
+ message: string;
+ type: 'info' | 'success' | 'warning' | 'error';
+ duration?: number;
+}
+
+function createToastStore() {
+ const { subscribe, update } = writable([]);
+
+ function addToast(message: string, type: Toast['type'], duration = 5000) {
+ const id = Math.random().toString(36).substring(2, 9);
+ const toast: Toast = { id, message, type, duration };
+
+ update((toasts) => [...toasts, toast]);
+
+ if (duration > 0) {
+ setTimeout(() => {
+ removeToast(id);
+ }, duration);
+ }
+
+ return id;
+ }
+
+ function removeToast(id: string) {
+ update((toasts) => toasts.filter((t) => t.id !== id));
+ }
+
+ return {
+ subscribe,
+ info: (message: string, duration?: number) => addToast(message, 'info', duration),
+ success: (message: string, duration?: number) => addToast(message, 'success', duration),
+ warning: (message: string, duration?: number) => addToast(message, 'warning', duration),
+ error: (message: string, duration?: number) => addToast(message, 'error', duration),
+ remove: removeToast,
+ clear: () => update(() => [])
+ };
+}
+
+export const toast = createToastStore();
+
+// Global toast instance (W in compiled code)
+if (typeof window !== 'undefined') {
+ (window as any).W = toast;
+}
diff --git a/frontend-src/src/lib/stores/user.ts b/frontend-src/src/lib/stores/user.ts
new file mode 100644
index 0000000..0299d88
--- /dev/null
+++ b/frontend-src/src/lib/stores/user.ts
@@ -0,0 +1,98 @@
+import { writable, derived } from 'svelte/store';
+import { api } from '$lib/api/client';
+import { calculateCurrentCharges } from '$lib/utils/charges';
+import { calculateLevel } from '$lib/utils/level';
+import { now } from './global';
+
+export interface User {
+ id: number;
+ name: string;
+ discord: string | null;
+ country: string;
+ droplets: number;
+ currentCharges: number;
+ maxCharges: number;
+ chargesCooldownMs: number;
+ chargesLastUpdatedAt: string;
+ pixelsPainted: number;
+ level: number;
+ equippedFlag: number;
+ extraColorsBitmap: number;
+ flagsBitmap: string | null;
+ showLastPixel: boolean;
+ picture: string | null;
+ allianceId: number | null;
+ allianceRole: string;
+ alliance: {
+ id: number;
+ name: string;
+ description: string;
+ pixelsPainted: number;
+ } | null;
+}
+
+function createUserStore() {
+ const { subscribe, set, update } = writable(null);
+
+ return {
+ subscribe,
+ set,
+ update,
+ async fetch() {
+ try {
+ const user = await api.getMe();
+ set(user);
+ return user;
+ } catch (error) {
+ console.error('Failed to fetch user:', error);
+ set(null);
+ return null;
+ }
+ },
+ async updateProfile(data: { name?: string; showLastPixel?: boolean; discord?: string }) {
+ try {
+ const updated = await api.updateMe(data);
+ set(updated);
+ return updated;
+ } catch (error) {
+ console.error('Failed to update profile:', error);
+ throw error;
+ }
+ },
+ async logout() {
+ try {
+ await api.logout();
+ set(null);
+ } catch (error) {
+ console.error('Failed to logout:', error);
+ throw error;
+ }
+ },
+ clear() {
+ set(null);
+ }
+ };
+}
+
+export const currentUser = createUserStore();
+
+// Derived store for current charges (recalculated based on time)
+export const currentCharges = derived([currentUser, now], ([$user, $now]) => {
+ if (!$user) return 0;
+
+ return calculateCurrentCharges(
+ $user.currentCharges,
+ $user.maxCharges,
+ new Date($user.chargesLastUpdatedAt),
+ $user.chargesCooldownMs
+ );
+});
+
+// Derived store for authentication status
+export const isAuthenticated = derived(currentUser, ($user) => $user !== null);
+
+// Derived store for user level
+export const userLevel = derived(currentUser, ($user) => {
+ if (!$user) return 1;
+ return calculateLevel($user.pixelsPainted);
+});
diff --git a/frontend-src/src/lib/utils/bitmap.ts b/frontend-src/src/lib/utils/bitmap.ts
new file mode 100644
index 0000000..44399fc
--- /dev/null
+++ b/frontend-src/src/lib/utils/bitmap.ts
@@ -0,0 +1,49 @@
+export class WplaceBitmap {
+ private bytes: Uint8Array;
+
+ constructor(base64?: string) {
+ if (base64) {
+ this.bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
+ } else {
+ this.bytes = new Uint8Array(0);
+ }
+ }
+
+ get(index: number): boolean {
+ const byteIndex = Math.floor(index / 8);
+ const bitIndex = index % 8;
+ if (byteIndex >= this.bytes.length) return false;
+ const realIndex = this.bytes.length - 1 - byteIndex;
+ return (this.bytes[realIndex] & (1 << bitIndex)) !== 0;
+ }
+
+ set(index: number, value: boolean): void {
+ const byteIndex = Math.floor(index / 8);
+ const bitIndex = index % 8;
+
+ if (byteIndex >= this.bytes.length) {
+ const newBytes = new Uint8Array(byteIndex + 1);
+ const offset = newBytes.length - this.bytes.length;
+ for (let i = 0; i < this.bytes.length; i++) {
+ newBytes[i + offset] = this.bytes[i];
+ }
+ this.bytes = newBytes;
+ }
+
+ const realIndex = this.bytes.length - 1 - byteIndex;
+
+ if (value) {
+ this.bytes[realIndex] |= 1 << bitIndex;
+ } else {
+ this.bytes[realIndex] &= ~(1 << bitIndex);
+ }
+ }
+
+ toBase64(): string {
+ return btoa(String.fromCharCode(...this.bytes));
+ }
+
+ static fromBase64(base64: string): WplaceBitmap {
+ return new WplaceBitmap(base64);
+ }
+}
diff --git a/frontend-src/src/lib/utils/charges.ts b/frontend-src/src/lib/utils/charges.ts
new file mode 100644
index 0000000..7cacc50
--- /dev/null
+++ b/frontend-src/src/lib/utils/charges.ts
@@ -0,0 +1,41 @@
+export function calculateCurrentCharges(
+ currentCharges: number,
+ maxCharges: number,
+ lastUpdate: Date,
+ cooldownMs: number
+): number {
+ if (currentCharges >= maxCharges) return currentCharges;
+
+ const timeSinceLastUpdate = Date.now() - lastUpdate.getTime();
+ const chargesGenerated = Math.floor(timeSinceLastUpdate / cooldownMs);
+
+ return Math.min(maxCharges, currentCharges + chargesGenerated);
+}
+
+export function getNextChargeTime(
+ currentCharges: number,
+ maxCharges: number,
+ lastUpdate: Date,
+ cooldownMs: number
+): Date | null {
+ if (currentCharges >= maxCharges) return null;
+
+ const timeSinceLastUpdate = Date.now() - lastUpdate.getTime();
+ const timeUntilNextCharge = cooldownMs - (timeSinceLastUpdate % cooldownMs);
+
+ return new Date(Date.now() + timeUntilNextCharge);
+}
+
+export function getChargesRegenPercentage(
+ currentCharges: number,
+ maxCharges: number,
+ lastUpdate: Date,
+ cooldownMs: number
+): number {
+ if (currentCharges >= maxCharges) return 0;
+
+ const timeSinceLastUpdate = Date.now() - lastUpdate.getTime();
+ const progress = (timeSinceLastUpdate % cooldownMs) / cooldownMs;
+
+ return progress * 100;
+}
diff --git a/frontend-src/src/lib/utils/level.ts b/frontend-src/src/lib/utils/level.ts
new file mode 100644
index 0000000..3b3ff5d
--- /dev/null
+++ b/frontend-src/src/lib/utils/level.ts
@@ -0,0 +1,22 @@
+export function calculateLevel(pixelsPainted: number): number {
+ return Math.floor(Math.sqrt(pixelsPainted / 100)) + 1;
+}
+
+export function getPixelsForLevel(level: number): number {
+ return ((level - 1) ** 2) * 100;
+}
+
+export function getPixelsForNextLevel(currentLevel: number): number {
+ return (currentLevel ** 2) * 100;
+}
+
+export function getLevelProgress(pixelsPainted: number): number {
+ const currentLevel = calculateLevel(pixelsPainted);
+ const pixelsForCurrentLevel = getPixelsForLevel(currentLevel);
+ const pixelsForNextLevel = getPixelsForNextLevel(currentLevel);
+ const pixelsInCurrentLevel = pixelsPainted - pixelsForCurrentLevel;
+ const pixelsNeededForLevel = pixelsForNextLevel - pixelsForCurrentLevel;
+
+ if (pixelsNeededForLevel === 0) return 0;
+ return (pixelsInCurrentLevel / pixelsNeededForLevel) * 100;
+}
diff --git a/frontend-src/src/routes/+layout.js b/frontend-src/src/routes/+layout.js
new file mode 100644
index 0000000..335055d
--- /dev/null
+++ b/frontend-src/src/routes/+layout.js
@@ -0,0 +1,2 @@
+export const prerender = true;
+export const ssr = false; // Disable SSR for SPA mode
diff --git a/frontend-src/src/routes/+layout.svelte b/frontend-src/src/routes/+layout.svelte
new file mode 100644
index 0000000..f87b35e
--- /dev/null
+++ b/frontend-src/src/routes/+layout.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/frontend-src/src/routes/+page.svelte b/frontend-src/src/routes/+page.svelte
new file mode 100644
index 0000000..a89670f
--- /dev/null
+++ b/frontend-src/src/routes/+page.svelte
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
diff --git a/frontend-src/src/routes/404/+page.svelte b/frontend-src/src/routes/404/+page.svelte
new file mode 100644
index 0000000..f75bc49
--- /dev/null
+++ b/frontend-src/src/routes/404/+page.svelte
@@ -0,0 +1,18 @@
+
+
+
+ 404 - Page Not Found
+
+
+
+
+
+
404
+
+ Oops! This page doesn't exist.
+
+
Go Home
+
+
diff --git a/frontend-src/src/routes/admin/+page.svelte b/frontend-src/src/routes/admin/+page.svelte
new file mode 100644
index 0000000..29e8ca9
--- /dev/null
+++ b/frontend-src/src/routes/admin/+page.svelte
@@ -0,0 +1,45 @@
+
+
+
+ Admin Dashboard - openplace
+
+
+
+
+
+
+
+
+ Dashboard
+ Admin dashboard content coming soon...
+
+
+
+
diff --git a/frontend-src/src/routes/join/+page.svelte b/frontend-src/src/routes/join/+page.svelte
new file mode 100644
index 0000000..8616a50
--- /dev/null
+++ b/frontend-src/src/routes/join/+page.svelte
@@ -0,0 +1,17 @@
+
+
+
+ Login - openplace
+
+
+
diff --git a/frontend-src/src/routes/moderation/+page.svelte b/frontend-src/src/routes/moderation/+page.svelte
new file mode 100644
index 0000000..2cb0281
--- /dev/null
+++ b/frontend-src/src/routes/moderation/+page.svelte
@@ -0,0 +1,43 @@
+
+
+
+ Moderation - openplace
+
+
+
+
+
+
+
+
+
+
+
+ Reported users
+ Open tickets: 0
+
+
+
+
+
+
+
+
+ Select a ticket to view details
+
+
+
diff --git a/frontend-src/static/PixelifySans-latin.vdc2vUDH.woff2 b/frontend-src/static/PixelifySans-latin.vdc2vUDH.woff2
new file mode 100644
index 0000000..ea75ad5
Binary files /dev/null and b/frontend-src/static/PixelifySans-latin.vdc2vUDH.woff2 differ
diff --git a/frontend-src/static/css2.css b/frontend-src/static/css2.css
new file mode 100644
index 0000000..9bbf474
--- /dev/null
+++ b/frontend-src/static/css2.css
@@ -0,0 +1,108 @@
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3CWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3mWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm36WWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3KWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3OWWpCBC10HFw.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: italic;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm32WWpCBC10.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto Mono';
+ font-style: normal;
+ font-weight: 100 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
diff --git a/frontend-src/static/download.png b/frontend-src/static/download.png
new file mode 100644
index 0000000..83d23f6
Binary files /dev/null and b/frontend-src/static/download.png differ
diff --git a/frontend-src/static/download.svg b/frontend-src/static/download.svg
new file mode 100644
index 0000000..751b034
--- /dev/null
+++ b/frontend-src/static/download.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/frontend-src/static/favicon.ico b/frontend-src/static/favicon.ico
new file mode 100644
index 0000000..be576b1
Binary files /dev/null and b/frontend-src/static/favicon.ico differ
diff --git a/frontend-src/static/img/og-image.png b/frontend-src/static/img/og-image.png
new file mode 100644
index 0000000..02eb56e
Binary files /dev/null and b/frontend-src/static/img/og-image.png differ
diff --git a/frontend-src/static/img/web-app-manifest-192x192.png b/frontend-src/static/img/web-app-manifest-192x192.png
new file mode 100644
index 0000000..a6fddf3
Binary files /dev/null and b/frontend-src/static/img/web-app-manifest-192x192.png differ
diff --git a/frontend-src/static/maps/styles/fiord b/frontend-src/static/maps/styles/fiord
new file mode 100644
index 0000000..1b1762c
--- /dev/null
+++ b/frontend-src/static/maps/styles/fiord
@@ -0,0 +1,2871 @@
+{
+ "version": 8,
+ "sources": {
+ "ne2_shaded": {
+ "maxzoom": 6,
+ "tileSize": 256,
+ "tiles": [
+ "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"
+ ],
+ "type": "raster"
+ },
+ "openmaptiles": {
+ "type": "vector",
+ "url": "https://tiles.openfreemap.org/planet"
+ }
+ },
+ "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
+ "glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#45516E"
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "#38435C"
+ }
+ },
+ {
+ "id": "landcover_ice_shelf",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "maxzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "subclass"
+ ],
+ "ice_shelf"
+ ]
+ ],
+ "paint": {
+ "fill-color": "hsl(232,33%,34%)",
+ "fill-opacity": 0.4
+ }
+ },
+ {
+ "id": "landuse_residential",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "maxzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "subclass"
+ ],
+ "residential"
+ ]
+ ],
+ "paint": {
+ "fill-color": "rgb(234, 234, 230)",
+ "fill-opacity": [
+ "interpolate",
+ [
+ "exponential",
+ 0.6
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 0.8,
+ 9,
+ 0.6
+ ]
+ }
+ },
+ {
+ "id": "landcover_wood",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "minzoom": 10,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "wood"
+ ]
+ ],
+ "paint": {
+ "fill-color": "hsla(232,18%,30%,0.57)",
+ "fill-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 9,
+ 0,
+ 12,
+ 1
+ ]
+ }
+ },
+ {
+ "id": "park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": "hsl(204,17%,35%)",
+ "fill-opacity": 0.3
+ }
+ },
+ {
+ "id": "park_outline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "line-color": "hsl(205,49%,31%)",
+ "line-dasharray": [
+ 2,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "line-color": "hsl(232,23%,28%)",
+ "line-opacity": 0.6
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "minzoom": 12,
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "hsla(232,47%,18%,0.65)",
+ "fill-opacity": 0.25
+ }
+ },
+ {
+ "id": "tunnel_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#3C4357",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 5.8,
+ 0,
+ 6,
+ 3,
+ 20,
+ 40
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_inner",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,18%,21%)",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 2,
+ 6,
+ 1.3,
+ 20,
+ 30
+ ]
+ }
+ },
+ {
+ "id": "aeroway-taxiway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 12,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "taxiway"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.55
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 1.8,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "aeroway-runway-casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "runway"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.5
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 6,
+ 17,
+ 55
+ ]
+ }
+ },
+ {
+ "id": "aeroway-area",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 4,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "runway",
+ "taxiway"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "fill-color": "hsl(224,20%,29%)",
+ "fill-opacity": 1
+ }
+ },
+ {
+ "id": "aeroway-runway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "maxzoom": 24,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "runway"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,20%,29%)",
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.5
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 4,
+ 17,
+ 50
+ ]
+ }
+ },
+ {
+ "id": "road_area_pier",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "pier"
+ ]
+ ],
+ "paint": {
+ "fill-antialias": true,
+ "fill-color": "#45516E"
+ }
+ },
+ {
+ "id": "road_pier",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "pier"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#45516E",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 17,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway_path",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "path"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(211,29%,38%)",
+ "line-dasharray": [
+ 2,
+ 2
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 20,
+ 4
+ ]
+ }
+ },
+ {
+ "id": "highway_minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor",
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-opacity": 0.9,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.55
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 1.8,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "highway_major_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-dasharray": [
+ 12,
+ 0
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 10,
+ 3,
+ 20,
+ 23
+ ]
+ }
+ },
+ {
+ "id": "highway_major_inner",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#3C4357",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 10,
+ 2,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "highway_major_subtle",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "maxzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#3D4355",
+ "line-opacity": 0.6,
+ "line-width": 2
+ }
+ },
+ {
+ "id": "highway_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "hsl(224,22%,45%)",
+ "line-dasharray": [
+ 2,
+ 0
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 5.8,
+ 0,
+ 6,
+ 3,
+ 20,
+ 40
+ ]
+ }
+ },
+ {
+ "id": "highway_motorway_inner",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 5.8,
+ "hsla(0,0%,85%,0.53)",
+ 6,
+ "hsl(224,20%,29%)"
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 2,
+ 6,
+ 1.3,
+ 20,
+ 30
+ ]
+ }
+ },
+ {
+ "id": "highway_motorway_subtle",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "maxzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsla(239,45%,69%,0.2)",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 2,
+ 6,
+ 1.3
+ ]
+ }
+ },
+ {
+ "id": "railway_transit",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "tunnel"
+ ],
+ false,
+ true
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(200,65%,11%)",
+ "line-width": 3
+ }
+ },
+ {
+ "id": "railway_transit_dashline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "tunnel"
+ ],
+ false,
+ true
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(193,63%,26%)",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-width": 2
+ }
+ },
+ {
+ "id": "railway_service",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "has",
+ "service"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(200,65%,11%)",
+ "line-width": 3
+ }
+ },
+ {
+ "id": "railway_service_dashline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "has",
+ "service"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(193,63%,26%)",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-width": 2
+ }
+ },
+ {
+ "id": "railway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "!",
+ [
+ "has",
+ "service"
+ ]
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(200,10%,18%)",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 16,
+ 3,
+ 20,
+ 7
+ ]
+ }
+ },
+ {
+ "id": "railway_dashline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "!",
+ [
+ "has",
+ "service"
+ ]
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(224,20%,41%)",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 16,
+ 1.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "water_name",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 500,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "hsl(223,21%,52%)",
+ "text-halo-blur": 0,
+ "text-halo-color": "hsl(232,5%,19%)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway_name_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "filter": [
+ "all",
+ [
+ "!=",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-angle": 30,
+ "text-pitch-alignment": "viewport",
+ "text-rotation-alignment": "map",
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(223,31%,61%)",
+ "text-halo-blur": 0,
+ "text-halo-color": "hsl(232,9%,23%)",
+ "text-halo-width": 2,
+ "text-opacity": 1,
+ "text-translate": [
+ 0,
+ 0
+ ]
+ }
+ },
+ {
+ "id": "highway_ref",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-pitch-alignment": "viewport",
+ "text-rotation-alignment": "viewport",
+ "text-size": 10,
+ "visibility": "none"
+ },
+ "paint": {
+ "text-color": "hsl(215,57%,77%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsl(209,64%,19%)",
+ "text-halo-width": 1,
+ "text-opacity": 1,
+ "text-translate": [
+ 0,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "boundary_state",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 4
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-blur": 0.4,
+ "line-color": "hsla(195,47%,62%,0.26)",
+ "line-dasharray": [
+ 2,
+ 2
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 22,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "boundary_country_z0-4",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "maxzoom": 5,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 2
+ ],
+ [
+ "!",
+ [
+ "has",
+ "claimed_by"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-blur": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.4,
+ 22,
+ 4
+ ],
+ "line-color": "hsl(214,63%,76%)",
+ "line-opacity": 0.56,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.1
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 22,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "boundary_country_z5-",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "minzoom": 5,
+ "filter": [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 2
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-blur": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.4,
+ 22,
+ 4
+ ],
+ "line-color": "hsl(214,63%,76%)",
+ "line-opacity": 0.56,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.1
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 22,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "place_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "hamlet",
+ "isolated_dwelling",
+ "neighbourhood"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "center",
+ "text-offset": [
+ 0.5,
+ 0
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(195,37%,73%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1,
+ "text-opacity": 0.6
+ }
+ },
+ {
+ "id": "place_suburb",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "suburb"
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "center",
+ "text-offset": [
+ 0.5,
+ 0
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(195,41%,49%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_village",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "village"
+ ]
+ ],
+ "layout": {
+ "icon-size": 0.4,
+ "text-anchor": "left",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,41%,49%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_town",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "town"
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle-11",
+ 9,
+ ""
+ ],
+ "icon-size": 0.4,
+ "text-anchor": [
+ "step",
+ [
+ "zoom"
+ ],
+ "left",
+ 8,
+ "center"
+ ],
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,25%,76%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_city",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ],
+ [
+ "\u003E",
+ [
+ "get",
+ "rank"
+ ],
+ 3
+ ]
+ ]
+ ],
+ "layout": {
+ "icon-size": 0.4,
+ "text-anchor": [
+ "step",
+ [
+ "zoom"
+ ],
+ "left",
+ 8,
+ "center"
+ ],
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,25%,76%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_city_large",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "all",
+ [
+ "\u003C=",
+ [
+ "get",
+ "rank"
+ ],
+ 3
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ]
+ ]
+ ],
+ "layout": {
+ "icon-size": 0.4,
+ "text-anchor": [
+ "step",
+ [
+ "zoom"
+ ],
+ "left",
+ 8,
+ "center"
+ ],
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-justify": "left",
+ "text-offset": [
+ 0.5,
+ 0.2
+ ],
+ "text-size": 14,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "icon-opacity": 0.7,
+ "text-color": "hsl(195,25%,76%)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_state",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "state"
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": 10,
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "rgb(113, 129, 144)",
+ "text-halo-blur": 1,
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "place_country_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "!",
+ [
+ "has",
+ "iso_a2"
+ ]
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 9,
+ 6,
+ 11
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ "rgb(157,169,177)",
+ 4,
+ "rgb(153, 153, 153)"
+ ],
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": 1
+ }
+ },
+ {
+ "id": "place_country_minor",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 8,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "\u003E=",
+ [
+ "get",
+ "rank"
+ ],
+ 2
+ ],
+ [
+ "has",
+ "iso_a2"
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 6,
+ 12
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ "rgb(157,169,177)",
+ 4,
+ "rgb(153, 153, 153)"
+ ],
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": 1
+ }
+ },
+ {
+ "id": "place_country_major",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "\u003C=",
+ [
+ "get",
+ "rank"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "has",
+ "iso_a2"
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 3,
+ 12,
+ 4,
+ 14
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ "rgb(157,169,177)",
+ 4,
+ "rgb(153, 153, 153)"
+ ],
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": 1
+ }
+ },
+ {
+ "id": "place_continent",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 6,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "continent"
+ ]
+ ],
+ "layout": {
+ "text-anchor": "center",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 3,
+ 12,
+ 4,
+ 14
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "hsl(0,0%,100%)",
+ "text-halo-color": "hsla(228,60%,21%,0.7)",
+ "text-halo-width": 1.4,
+ "text-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.6,
+ 3,
+ 0
+ ]
+ }
+ }
+ ]
+}
diff --git a/frontend-src/static/maps/styles/liberty b/frontend-src/static/maps/styles/liberty
new file mode 100644
index 0000000..a1922f6
--- /dev/null
+++ b/frontend-src/static/maps/styles/liberty
@@ -0,0 +1,6034 @@
+{
+ "version": 8,
+ "sources": {
+ "ne2_shaded": {
+ "maxzoom": 6,
+ "tileSize": 256,
+ "tiles": [
+ "https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"
+ ],
+ "type": "raster"
+ },
+ "openmaptiles": {
+ "type": "vector",
+ "url": "https://tiles.openfreemap.org/planet"
+ }
+ },
+ "sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
+ "glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#f8f4f0"
+ }
+ },
+ {
+ "id": "natural_earth",
+ "type": "raster",
+ "source": "ne2_shaded",
+ "maxzoom": 7,
+ "paint": {
+ "raster-opacity": [
+ "interpolate",
+ [
+ "exponential",
+ 1.5
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.6,
+ 6,
+ 0.1
+ ]
+ }
+ },
+ {
+ "id": "park",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "paint": {
+ "fill-color": "#d8e8c8",
+ "fill-opacity": 0.7,
+ "fill-outline-color": "rgba(95, 208, 100, 1)"
+ }
+ },
+ {
+ "id": "park_outline",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "park",
+ "paint": {
+ "line-color": "rgba(228, 241, 215, 1)",
+ "line-dasharray": [
+ 1,
+ 1.5
+ ]
+ }
+ },
+ {
+ "id": "landuse_residential",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "maxzoom": 12,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "residential"
+ ],
+ "paint": {
+ "fill-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 9,
+ "hsla(0,3%,85%,0.84)",
+ 12,
+ "hsla(35,57%,88%,0.49)"
+ ]
+ }
+ },
+ {
+ "id": "landcover_wood",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "wood"
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "hsla(98,61%,72%,0.7)",
+ "fill-opacity": 0.4
+ }
+ },
+ {
+ "id": "landcover_grass",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "grass"
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "rgba(176, 213, 154, 1)",
+ "fill-opacity": 0.3
+ }
+ },
+ {
+ "id": "landcover_ice",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "ice"
+ ],
+ "paint": {
+ "fill-antialias": false,
+ "fill-color": "rgba(224, 236, 236, 1)",
+ "fill-opacity": 0.8
+ }
+ },
+ {
+ "id": "landcover_wetland",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "minzoom": 12,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "wetland"
+ ],
+ "paint": {
+ "fill-antialias": true,
+ "fill-opacity": 0.8,
+ "fill-pattern": "wetland_bg_11",
+ "fill-translate-anchor": "map"
+ }
+ },
+ {
+ "id": "landuse_pitch",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "pitch"
+ ],
+ "paint": {
+ "fill-color": "#DEE3CD"
+ }
+ },
+ {
+ "id": "landuse_track",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "track"
+ ],
+ "paint": {
+ "fill-color": "#DEE3CD"
+ }
+ },
+ {
+ "id": "landuse_cemetery",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "cemetery"
+ ],
+ "paint": {
+ "fill-color": "hsl(75,37%,81%)"
+ }
+ },
+ {
+ "id": "landuse_hospital",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "hospital"
+ ],
+ "paint": {
+ "fill-color": "#fde"
+ }
+ },
+ {
+ "id": "landuse_school",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "school"
+ ],
+ "paint": {
+ "fill-color": "rgb(236,238,204)"
+ }
+ },
+ {
+ "id": "waterway_tunnel",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-dasharray": [
+ 3,
+ 3
+ ],
+ "line-gap-width": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 20,
+ 6
+ ],
+ "line-opacity": 1,
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "waterway_river",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "river"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round"
+ },
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "waterway_other",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ [
+ "!=",
+ [
+ "get",
+ "class"
+ ],
+ "river"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-cap": "round"
+ },
+ "paint": {
+ "line-color": "#a0c8f0",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.3
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "water",
+ "filter": [
+ "!=",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ "paint": {
+ "fill-color": "rgb(158,189,255)"
+ }
+ },
+ {
+ "id": "landcover_sand",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "landcover",
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "sand"
+ ],
+ "paint": {
+ "fill-color": "rgba(247, 239, 195, 1)"
+ }
+ },
+ {
+ "id": "aeroway_fill",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-color": "rgba(229, 228, 224, 1)",
+ "fill-opacity": 0.7
+ }
+ },
+ {
+ "id": "aeroway_runway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "runway"
+ ]
+ ],
+ "paint": {
+ "line-color": "#f0ede9",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 3,
+ 20,
+ 16
+ ]
+ }
+ },
+ {
+ "id": "aeroway_taxiway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "aeroway",
+ "minzoom": 11,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "taxiway"
+ ]
+ ],
+ "paint": {
+ "line-color": "#f0ede9",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 11,
+ 0.5,
+ 20,
+ 6
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [
+ 0.5,
+ 0.25
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel_service_track_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-dasharray": [
+ 0.5,
+ 0.25
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "tunnel_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel_street_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "street",
+ "street_limited"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 12.5,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "tunnel_secondary_tertiary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "tunnel_trunk_primary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-dasharray": [
+ 0.5,
+ 0.25
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "tunnel_path_pedestrian",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,100%)",
+ "line-dasharray": [
+ 1,
+ 0.75
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_service_track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "tunnel_secondary_tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "tunnel_trunk_primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff4c6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel_motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#ffdaa6",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "tunnel_major_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "rail"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "tunnel_major_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "tunnel_transit_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "transit"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "tunnel_transit_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "tunnel"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "road_area_pattern",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPolygon",
+ "Polygon"
+ ],
+ true,
+ false
+ ],
+ "paint": {
+ "fill-pattern": "pedestrian_polygon"
+ }
+ },
+ {
+ "id": "road_motorway_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "road_service_track_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "road_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "motorway",
+ "path",
+ "pedestrian",
+ "service",
+ "track"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "road_minor_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 12.5,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 20
+ ]
+ }
+ },
+ {
+ "id": "road_secondary_tertiary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "road_trunk_primary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "road_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "road_path_pedestrian",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 14,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(0,0%,100%)",
+ "line-dasharray": [
+ 1,
+ 0.7
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 1,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "road_motorway_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 12,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "road_service_track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "road_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 13,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "motorway",
+ "path",
+ "pedestrian",
+ "service",
+ "track"
+ ],
+ false,
+ true
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "road_minor",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "road_secondary_tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 6.5,
+ 0,
+ 8,
+ 0.5,
+ 20,
+ 13
+ ]
+ }
+ },
+ {
+ "id": "road_trunk_primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "road_motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ "hsl(26,87%,62%)",
+ 6,
+ "#fc8"
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "road_major_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "road_major_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "road_transit_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "road_transit_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "get",
+ "brunnel"
+ ],
+ [
+ "bridge",
+ "tunnel"
+ ],
+ false,
+ true
+ ],
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "road_one_way_arrow",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "==",
+ [
+ "get",
+ "oneway"
+ ],
+ 1
+ ],
+ "layout": {
+ "icon-image": "arrow",
+ "symbol-placement": "line"
+ }
+ },
+ {
+ "id": "road_one_way_arrow_opposite",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "minzoom": 16,
+ "filter": [
+ "==",
+ [
+ "get",
+ "oneway"
+ ],
+ -1
+ ],
+ "layout": {
+ "icon-image": "arrow",
+ "icon-rotate": 180,
+ "symbol-placement": "line"
+ }
+ },
+ {
+ "id": "bridge_motorway_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "bridge_service_track_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#cfcdca",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15,
+ 1,
+ 16,
+ 4,
+ 20,
+ 11
+ ]
+ }
+ },
+ {
+ "id": "bridge_link_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "link"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 1,
+ 13,
+ 3,
+ 14,
+ 4,
+ 20,
+ 15
+ ]
+ }
+ },
+ {
+ "id": "bridge_street_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "street",
+ "street_limited"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(36,6%,74%)",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0,
+ 12.5,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12,
+ 0.5,
+ 13,
+ 1,
+ 14,
+ 4,
+ 20,
+ 25
+ ]
+ }
+ },
+ {
+ "id": "bridge_path_pedestrian_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(35,6%,80%)",
+ "line-dasharray": [
+ 1,
+ 0
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 1.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_secondary_tertiary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 1.5,
+ 20,
+ 17
+ ]
+ }
+ },
+ {
+ "id": "bridge_trunk_primary_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "bridge_motorway_casing",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#e9ac77",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0.4,
+ 6,
+ 0.7,
+ 7,
+ 1.75,
+ 20,
+ 22
+ ]
+ }
+ },
+ {
+ "id": "bridge_path_pedestrian",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "path",
+ "pedestrian"
+ ],
+ true,
+ false
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,100%)",
+ "line-dasharray": [
+ 1,
+ 0.3
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "bridge_motorway_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge_service_track",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 15.5,
+ 0,
+ 16,
+ 2,
+ 20,
+ 7.5
+ ]
+ }
+ },
+ {
+ "id": "bridge_link",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "link"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 12.5,
+ 0,
+ 13,
+ 1.5,
+ 14,
+ 2.5,
+ 20,
+ 11.5
+ ]
+ }
+ },
+ {
+ "id": "bridge_street",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 13.5,
+ 0,
+ 14,
+ 2.5,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_secondary_tertiary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "secondary",
+ "tertiary"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 6.5,
+ 0,
+ 7,
+ 0.5,
+ 20,
+ 10
+ ]
+ }
+ },
+ {
+ "id": "bridge_trunk_primary",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "trunk"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fea",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_motorway",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "motorway"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "ramp"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "layout": {
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fc8",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 0,
+ 7,
+ 1,
+ 20,
+ 18
+ ]
+ }
+ },
+ {
+ "id": "bridge_major_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "bridge_major_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "rail"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "bridge_transit_rail",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14,
+ 0.4,
+ 15,
+ 0.75,
+ 20,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "bridge_transit_rail_hatching",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "transportation",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "transit"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "brunnel"
+ ],
+ "bridge"
+ ]
+ ],
+ "paint": {
+ "line-color": "#bbb",
+ "line-dasharray": [
+ 0.2,
+ 8
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "exponential",
+ 1.4
+ ],
+ [
+ "zoom"
+ ],
+ 14.5,
+ 0,
+ 15,
+ 3,
+ 20,
+ 8
+ ]
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "minzoom": 13,
+ "maxzoom": 14,
+ "paint": {
+ "fill-color": "hsl(35,8%,85%)",
+ "fill-outline-color": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ "hsla(35,6%,79%,0.32)",
+ 14,
+ "hsl(35,6%,79%)"
+ ]
+ }
+ },
+ {
+ "id": "building-3d",
+ "type": "fill-extrusion",
+ "source": "openmaptiles",
+ "source-layer": "building",
+ "minzoom": 14,
+ "paint": {
+ "fill-extrusion-base": [
+ "get",
+ "render_min_height"
+ ],
+ "fill-extrusion-color": "hsl(35,8%,85%)",
+ "fill-extrusion-height": [
+ "get",
+ "render_height"
+ ],
+ "fill-extrusion-opacity": 0.8
+ }
+ },
+ {
+ "id": "boundary_3",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "minzoom": 5,
+ "filter": [
+ "all",
+ [
+ ">=",
+ [
+ "get",
+ "admin_level"
+ ],
+ 3
+ ],
+ [
+ "<=",
+ [
+ "get",
+ "admin_level"
+ ],
+ 6
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "maritime"
+ ],
+ 1
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "disputed"
+ ],
+ 1
+ ],
+ [
+ "!",
+ [
+ "has",
+ "claimed_by"
+ ]
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(0,0%,70%)",
+ "line-dasharray": [
+ 1,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "linear",
+ 1
+ ],
+ [
+ "zoom"
+ ],
+ 7,
+ 1,
+ 11,
+ 2
+ ]
+ }
+ },
+ {
+ "id": "boundary_2",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "admin_level"
+ ],
+ 2
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "maritime"
+ ],
+ 1
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "disputed"
+ ],
+ 1
+ ],
+ [
+ "!",
+ [
+ "has",
+ "claimed_by"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "hsl(248,1%,41%)",
+ "line-opacity": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0.4,
+ 4,
+ 1
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 5,
+ 1.2,
+ 12,
+ 3
+ ]
+ }
+ },
+ {
+ "id": "boundary_disputed",
+ "type": "line",
+ "source": "openmaptiles",
+ "source-layer": "boundary",
+ "filter": [
+ "all",
+ [
+ "!=",
+ [
+ "get",
+ "maritime"
+ ],
+ 1
+ ],
+ [
+ "==",
+ [
+ "get",
+ "disputed"
+ ],
+ 1
+ ]
+ ],
+ "paint": {
+ "line-color": "hsl(248,1%,41%)",
+ "line-dasharray": [
+ 1,
+ 2
+ ],
+ "line-width": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 1,
+ 5,
+ 1.2,
+ 12,
+ 3
+ ]
+ }
+ },
+ {
+ "id": "waterway_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "waterway",
+ "minzoom": 10,
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#74aee9",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_point_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 10,
+ 8,
+ 14
+ ]
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "water_name_line_label",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "water_name",
+ "filter": [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "symbol-spacing": 350,
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 5,
+ "text-size": 14
+ },
+ "paint": {
+ "text-color": "#495e91",
+ "text-halo-color": "rgba(255,255,255,0.7)",
+ "text-halo-width": 1.5
+ }
+ },
+ {
+ "id": "poi_r20",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 17,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 20
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "florist",
+ "furniture"
+ ],
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_r7",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 16,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 7
+ ],
+ [
+ "<",
+ [
+ "get",
+ "rank"
+ ],
+ 20
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "florist",
+ "furniture"
+ ],
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_r1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "MultiPoint",
+ "Point"
+ ],
+ true,
+ false
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 1
+ ],
+ [
+ "<",
+ [
+ "get",
+ "rank"
+ ],
+ 7
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "match",
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "florist",
+ "furniture"
+ ],
+ [
+ "get",
+ "subclass"
+ ],
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "poi_transit",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "poi",
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "airport",
+ "bus",
+ "rail"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "icon-image": [
+ "to-string",
+ [
+ "get",
+ "class"
+ ]
+ ],
+ "icon-size": 0.7,
+ "text-anchor": "left",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0.9,
+ 0
+ ],
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#2e5a80",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-path",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15.5,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "path"
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 12,
+ 14,
+ 13
+ ]
+ },
+ "paint": {
+ "text-color": "hsl(30,23%,62%)",
+ "text-halo-color": "#f8f4f0",
+ "text-halo-width": 0.5
+ }
+ },
+ {
+ "id": "highway-name-minor",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 15,
+ "filter": [
+ "all",
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "minor",
+ "service",
+ "track"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 12,
+ 14,
+ 13
+ ]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-name-major",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 12.2,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ true,
+ false
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ " ",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "map",
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 13,
+ 12,
+ 14,
+ 13
+ ]
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "highway-shield-non-us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ [
+ "<=",
+ [
+ "get",
+ "ref_length"
+ ],
+ 6
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "network"
+ ],
+ [
+ "us-highway",
+ "us-interstate",
+ "us-state"
+ ],
+ false,
+ true
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ "road_",
+ [
+ "get",
+ "ref_length"
+ ]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": [
+ "step",
+ [
+ "zoom"
+ ],
+ "point",
+ 11,
+ "line"
+ ],
+ "symbol-spacing": 200,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "highway-shield-us-interstate",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 7,
+ "filter": [
+ "all",
+ [
+ "<=",
+ [
+ "get",
+ "ref_length"
+ ],
+ 6
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "network"
+ ],
+ [
+ "us-interstate"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ [
+ "get",
+ "network"
+ ],
+ "_",
+ [
+ "get",
+ "ref_length"
+ ]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": [
+ "step",
+ [
+ "zoom"
+ ],
+ "point",
+ 7,
+ "line",
+ 8,
+ "line"
+ ],
+ "symbol-spacing": 200,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "road_shield_us",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "transportation_name",
+ "minzoom": 9,
+ "filter": [
+ "all",
+ [
+ "<=",
+ [
+ "get",
+ "ref_length"
+ ],
+ 6
+ ],
+ [
+ "match",
+ [
+ "geometry-type"
+ ],
+ [
+ "LineString",
+ "MultiLineString"
+ ],
+ true,
+ false
+ ],
+ [
+ "match",
+ [
+ "get",
+ "network"
+ ],
+ [
+ "us-highway",
+ "us-state"
+ ],
+ true,
+ false
+ ]
+ ],
+ "layout": {
+ "icon-image": [
+ "concat",
+ [
+ "get",
+ "network"
+ ],
+ "_",
+ [
+ "get",
+ "ref_length"
+ ]
+ ],
+ "icon-rotation-alignment": "viewport",
+ "icon-size": 1,
+ "symbol-placement": [
+ "step",
+ [
+ "zoom"
+ ],
+ "point",
+ 11,
+ "line"
+ ],
+ "symbol-spacing": 200,
+ "text-field": [
+ "to-string",
+ [
+ "get",
+ "ref"
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-rotation-alignment": "viewport",
+ "text-size": 10
+ }
+ },
+ {
+ "id": "airport",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "aerodrome_label",
+ "minzoom": 10,
+ "filter": [
+ "all",
+ [
+ "has",
+ "iata"
+ ]
+ ],
+ "layout": {
+ "icon-image": "airport_11",
+ "icon-size": 1,
+ "text-anchor": "top",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 9,
+ "text-offset": [
+ 0,
+ 0.6
+ ],
+ "text-optional": true,
+ "text-padding": 2,
+ "text-size": 12
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-blur": 0.5,
+ "text-halo-color": "#ffffff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_other",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 8,
+ "filter": [
+ "match",
+ [
+ "get",
+ "class"
+ ],
+ [
+ "city",
+ "continent",
+ "country",
+ "state",
+ "town",
+ "village"
+ ],
+ false,
+ true
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.1,
+ "text-max-width": 9,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 8,
+ 9,
+ 12,
+ 10
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_village",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 9,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "village"
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 10,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 7,
+ 10,
+ 11,
+ 12
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_town",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 6,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "town"
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 10,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.2,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 8,
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 7,
+ 12,
+ 11,
+ 14
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_state",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 5,
+ "maxzoom": 8,
+ "filter": [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "state"
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Italic"
+ ],
+ "text-letter-spacing": 0.2,
+ "text-max-width": 9,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 5,
+ 10,
+ 8,
+ 14
+ ],
+ "text-transform": "uppercase"
+ },
+ "paint": {
+ "text-color": "#333",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ],
+ [
+ "!=",
+ [
+ "get",
+ "capital"
+ ],
+ 2
+ ]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 9,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.4,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Regular"
+ ],
+ "text-max-width": 8,
+ "text-offset": [
+ 0,
+ -0.1
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 11,
+ 7,
+ 13,
+ 11,
+ 18
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_city_capital",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 3,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "city"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "capital"
+ ],
+ 2
+ ]
+ ],
+ "layout": {
+ "icon-allow-overlap": true,
+ "icon-image": [
+ "step",
+ [
+ "zoom"
+ ],
+ "circle_11_black",
+ 9,
+ ""
+ ],
+ "icon-optional": false,
+ "icon-size": 0.5,
+ "text-anchor": "bottom",
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 8,
+ "text-offset": [
+ 0,
+ -0.2
+ ],
+ "text-size": [
+ "interpolate",
+ [
+ "exponential",
+ 1.2
+ ],
+ [
+ "zoom"
+ ],
+ 4,
+ 12,
+ 7,
+ 14,
+ 11,
+ 20
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_3",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "minzoom": 2,
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ ">=",
+ [
+ "get",
+ "rank"
+ ],
+ 3
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 6.25,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 3,
+ 9,
+ 7,
+ 17
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_2",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "rank"
+ ],
+ 2
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 6.25,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 2,
+ 9,
+ 5,
+ 17
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ },
+ {
+ "id": "label_country_1",
+ "type": "symbol",
+ "source": "openmaptiles",
+ "source-layer": "place",
+ "maxzoom": 9,
+ "filter": [
+ "all",
+ [
+ "==",
+ [
+ "get",
+ "class"
+ ],
+ "country"
+ ],
+ [
+ "==",
+ [
+ "get",
+ "rank"
+ ],
+ 1
+ ]
+ ],
+ "layout": {
+ "text-field": [
+ "case",
+ [
+ "has",
+ "name:nonlatin"
+ ],
+ [
+ "concat",
+ [
+ "get",
+ "name:latin"
+ ],
+ "\n",
+ [
+ "get",
+ "name:nonlatin"
+ ]
+ ],
+ [
+ "coalesce",
+ [
+ "get",
+ "name_en"
+ ],
+ [
+ "get",
+ "name"
+ ]
+ ]
+ ],
+ "text-font": [
+ "Noto Sans Bold"
+ ],
+ "text-max-width": 6.25,
+ "text-size": [
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 1,
+ 9,
+ 4,
+ 17
+ ]
+ },
+ "paint": {
+ "text-color": "#000",
+ "text-halo-blur": 1,
+ "text-halo-color": "#fff",
+ "text-halo-width": 1
+ }
+ }
+ ]
+}
diff --git a/frontend-src/static/service-worker.js b/frontend-src/static/service-worker.js
new file mode 100644
index 0000000..21e1622
--- /dev/null
+++ b/frontend-src/static/service-worker.js
@@ -0,0 +1,705 @@
+const ae = "files",
+ a = location.pathname.split("/").slice(0, -1).join("/"),
+ ne = [a + "/_app/immutable/entry/app.iDaujbEI.js", a + "/_app/immutable/nodes/0.CnnlsrhC.js", a + "/_app/immutable/assets/0.CmqRY0au.css", a + "/_app/immutable/assets/Geist-cyrillic.CHSlOQsW.woff2", a + "/_app/immutable/assets/Geist-latin-ext.DMtmJ5ZE.woff2", a + "/_app/immutable/assets/Geist-latin.Dg_dQHbK.woff2", a + "/_app/immutable/assets/GeistMono-cyrillic.BZdD_g9V.woff2", a + "/_app/immutable/assets/GeistMono-latin-ext.b6lpi8_2.woff2", a + "/_app/immutable/assets/GeistMono-latin.Cjtb1TV-.woff2", a + "/_app/immutable/assets/PixelifySans-cyrillic.CPPz0Qvd.woff2", a + "/_app/immutable/assets/PixelifySans-latin.vdc2vUDH.woff2", a + "/_app/immutable/assets/NotoColorEmoji-flags.ClvgErYz.woff2", a + "/_app/immutable/assets/flags.a2kmUSbF.webp", a + "/_app/immutable/assets/flags@2x.gR6KPp3x.webp", a + "/_app/immutable/nodes/1.DpC5h7KA.js", a + "/_app/immutable/nodes/10.C07JyVXo.js", a + "/_app/immutable/nodes/11.BVmrEev1.js", a + "/_app/immutable/assets/9.BD1hRFPA.css", a + "/_app/immutable/nodes/2.BY7SdjrD.js", a + "/_app/immutable/assets/2.BtKF873c.css", a + "/_app/immutable/nodes/3.DVSEiJTt.js", a + "/_app/immutable/nodes/4.CeYpVeIo.js", a + "/_app/immutable/nodes/5.CXeQMqhf.js", a + "/_app/immutable/nodes/6.DD7Zmm97.js", a + "/_app/immutable/nodes/7.DDuBPi09.js", a + "/_app/immutable/nodes/8.B8sOtsfv.js", a + "/_app/immutable/nodes/9.BQE9fbrM.js", a + "/_app/immutable/chunks/07L1R_bo.js", a + "/_app/immutable/chunks/1lh-LSvX.js", a + "/_app/immutable/chunks/1mTheT_N.js", a + "/_app/immutable/chunks/2CRhGZHc.js", a + "/_app/immutable/chunks/5NasrULQ.js", a + "/_app/immutable/chunks/B1GmkH4o.js", a + "/_app/immutable/chunks/BMKgGW48.js", a + "/_app/immutable/chunks/BtP6pfnb.js", a + "/_app/immutable/chunks/ByKBPM-D.js", a + "/_app/immutable/chunks/Bzak7iHL.js", a + "/_app/immutable/chunks/C5GsJ62f.js", a + "/_app/immutable/chunks/CBqzI9hL.js", a + "/_app/immutable/assets/ProfileAvatarWithLevel.6dmPRSfx.css", a + "/_app/immutable/chunks/CMs8vKjq.js", a + "/_app/immutable/chunks/CQklNc9N.js", a + "/_app/immutable/assets/LoginForm.CxMG0irz.css", a + "/_app/immutable/chunks/CeLr1p76.js", a + "/_app/immutable/chunks/Cp3o644A.js", a + "/_app/immutable/chunks/D1ivTjwA.js", a + "/_app/immutable/chunks/D2m5UD3G.js", a + "/_app/immutable/assets/notification.CPyrWqU1.mp3", a + "/_app/immutable/chunks/D35KiPL1.js", a + "/_app/immutable/chunks/DUoKDNpf.js", a + "/_app/immutable/chunks/DkBFL3pa.js", a + "/_app/immutable/chunks/Dp1pzeXC.js", a + "/_app/immutable/chunks/DsJqb9ei.js", a + "/_app/immutable/chunks/F0pgzfyy.js", a + "/_app/immutable/chunks/KvV259my.js", a + "/_app/immutable/chunks/U908S-6f.js", a + "/_app/immutable/chunks/Y9es74tr.js", a + "/_app/immutable/chunks/g8c1BvYP.js", a + "/_app/immutable/entry/start.CJ_UwIBa.js", a + "/_app/immutable/chunks/1FgtjJRR.js"],
+ ie = [a + "/.well-known/security.txt", a + "/26/2025/08/12/horse.png", a + "/favicon.ico", a + "/img/apple-touch-icon.png", a + "/img/favicon-96x96.png", a + "/img/logo-512x512.png", a + "/img/logo.png", a + "/img/og-image-mobile.png", a + "/img/og-image.png", a + "/img/pwa-country-leaderboard-mobile.png", a + "/img/pwa-kiev-mobile.png", a + "/img/pwa-paint-heart-mobile.png", a + "/img/pwa-void-mobile.png", a + "/img/web-app-manifest-192x192.png", a + "/img/web-app-manifest-512x512.png", a + "/site.webmanifest"],
+ oe = "1756230503892";
+let r;
+const J = typeof TextDecoder < "u" ? new TextDecoder("utf-8", {
+ ignoreBOM: !0,
+ fatal: !0
+}) : {
+ decode: () => {
+ throw Error("TextDecoder not available")
+ }
+};
+typeof TextDecoder < "u" && J.decode();
+let S = null;
+
+function K() {
+ return (S === null || S.byteLength === 0) && (S = new Uint8Array(r.memory.buffer)), S
+}
+
+function te(e, n) {
+ return e = e >>> 0, J.decode(K().subarray(e, e + n))
+}
+let C = null;
+
+function de() {
+ return (C === null || C.byteLength === 0) && (C = new Uint8ClampedArray(r.memory.buffer)), C
+}
+
+function le(e, n) {
+ return e = e >>> 0, de().subarray(e / 1, e / 1 + n)
+}
+const b = new Array(128).fill(void 0);
+b.push(void 0, null, !0, !1);
+let D = b.length;
+
+function se(e) {
+ D === b.length && b.push(b.length + 1);
+ const n = D;
+ return D = b[n], b[n] = e, n
+}
+let U = 0;
+
+function q(e, n) {
+ const i = n(e.length * 1, 1) >>> 0;
+ return K().set(e, i / 1), U = e.length, i
+}
+let M = null;
+
+function H() {
+ return (M === null || M.byteLength === 0) && (M = new Int32Array(r.memory.buffer)), M
+}
+
+function ce(e, n) {
+ return e = e >>> 0, K().subarray(e / 1, e / 1 + n)
+}
+
+function re(e, n, i) {
+ try {
+ const m = r.__wbindgen_add_to_stack_pointer(-16),
+ y = q(e, r.__wbindgen_malloc),
+ t = U;
+ r.encode(m, y, t, n, i);
+ var l = H()[m / 4 + 0],
+ s = H()[m / 4 + 1],
+ u = ce(l, s).slice();
+ return r.__wbindgen_free(l, s * 1, 1), u
+ } finally {
+ r.__wbindgen_add_to_stack_pointer(16)
+ }
+}
+
+function me(e) {
+ return b[e]
+}
+
+function ge(e) {
+ e < 132 || (b[e] = D, D = e)
+}
+
+function fe(e) {
+ const n = me(e);
+ return ge(e), n
+}
+
+function ue(e) {
+ const n = q(e, r.__wbindgen_malloc),
+ i = U,
+ l = r.decode(n, i);
+ return fe(l)
+}
+async function pe(e, n) {
+ if (typeof Response == "function" && e instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming == "function") try {
+ return await WebAssembly.instantiateStreaming(e, n)
+ } catch (l) {
+ if (e.headers.get("Content-Type") != "application/wasm") console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", l);
+ else throw l
+ }
+ const i = await e.arrayBuffer();
+ return await WebAssembly.instantiate(i, n)
+ } else {
+ const i = await WebAssembly.instantiate(e, n);
+ return i instanceof WebAssembly.Instance ? {
+ instance: i,
+ module: e
+ } : i
+ }
+}
+
+function be() {
+ const e = {};
+ return e.wbg = {}, e.wbg.__wbg_newwithownedu8clampedarrayandsh_91db5987993a08fb = function(n, i, l, s) {
+ var u = le(n, i).slice();
+ r.__wbindgen_free(n, i * 1, 1);
+ const m = new ImageData(u, l >>> 0, s >>> 0);
+ return se(m)
+ }, e.wbg.__wbindgen_throw = function(n, i) {
+ throw new Error(te(n, i))
+ }, e
+}
+
+function he(e, n) {
+ return r = e.exports, F.__wbindgen_wasm_module = n, M = null, S = null, C = null, r
+}
+async function F(e) {
+ if (r !== void 0) return r;
+ const n = be();
+ (typeof e == "string" || typeof Request == "function" && e instanceof Request || typeof URL == "function" && e instanceof URL) && (e = fetch(e));
+ const {
+ instance: i,
+ module: l
+ } = await pe(await e, n);
+ return he(i, l)
+}
+const we = globalThis.ServiceWorkerGlobalScope !== void 0,
+ ye = we && typeof self < "u" && globalThis.caches && globalThis.caches.default !== void 0,
+ _e = typeof process == "object" && process.release && process.release.name === "node";
+(ye || _e) && (globalThis.ImageData || (globalThis.ImageData = class {
+ constructor(n, i, l) {
+ this.data = n, this.width = i, this.height = l
+ }
+}), typeof self < "u" && self.location === void 0 && (self.location = {
+ href: ""
+}));
+let j;
+async function Se(e) {
+ return j || (j = F(e)), j
+}
+async function Ce(e) {
+ await Se();
+ const n = await ue(new Uint8Array(e));
+ if (!n) throw new Error("Encoding error.");
+ return n
+}
+let E;
+async function Y(e) {
+ return E || (E = F(e)), E
+}
+async function V(e) {
+ await Y();
+ const n = await re(e.data, e.width, e.height);
+ if (!n) throw new Error("Encoding error.");
+ return n.buffer
+}
+const Me = "" + new URL("_app/immutable/assets/squoosh_png_bg.BsfxGNEB.wasm", location.href).pathname;
+
+function z({
+ pixel: e,
+ season: n,
+ tile: i
+}) {
+ return `t=(${i[0]},${i[1]});p=(${e[0]},${e[1]});s=${n}`
+}
+const De = [{
+ tileSize: 1e3,
+ zoom: 11
+ }],
+ ke = 4,
+ Te = 6e3,
+ Be = [{
+ name: "Transparent",
+ rgb: [0, 0, 0]
+ }, {
+ name: "Black",
+ rgb: [0, 0, 0]
+ }, {
+ name: "Dark Gray",
+ rgb: [60, 60, 60]
+ }, {
+ name: "Gray",
+ rgb: [120, 120, 120]
+ }, {
+ name: "Light Gray",
+ rgb: [210, 210, 210]
+ }, {
+ name: "White",
+ rgb: [255, 255, 255]
+ }, {
+ name: "Deep Red",
+ rgb: [96, 0, 24]
+ }, {
+ name: "Red",
+ rgb: [237, 28, 36]
+ }, {
+ name: "Orange",
+ rgb: [255, 127, 39]
+ }, {
+ name: "Gold",
+ rgb: [246, 170, 9]
+ }, {
+ name: "Yellow",
+ rgb: [249, 221, 59]
+ }, {
+ name: "Light Yellow",
+ rgb: [255, 250, 188]
+ }, {
+ name: "Dark Green",
+ rgb: [14, 185, 104]
+ }, {
+ name: "Green",
+ rgb: [19, 230, 123]
+ }, {
+ name: "Light Green",
+ rgb: [135, 255, 94]
+ }, {
+ name: "Dark Teal",
+ rgb: [12, 129, 110]
+ }, {
+ name: "Teal",
+ rgb: [16, 174, 166]
+ }, {
+ name: "Light Teal",
+ rgb: [19, 225, 190]
+ }, {
+ name: "Dark Blue",
+ rgb: [40, 80, 158]
+ }, {
+ name: "Blue",
+ rgb: [64, 147, 228]
+ }, {
+ name: "Cyan",
+ rgb: [96, 247, 242]
+ }, {
+ name: "Indigo",
+ rgb: [107, 80, 246]
+ }, {
+ name: "Light Indigo",
+ rgb: [153, 177, 251]
+ }, {
+ name: "Dark Purple",
+ rgb: [120, 12, 153]
+ }, {
+ name: "Purple",
+ rgb: [170, 56, 185]
+ }, {
+ name: "Light Purple",
+ rgb: [224, 159, 249]
+ }, {
+ name: "Dark Pink",
+ rgb: [203, 0, 122]
+ }, {
+ name: "Pink",
+ rgb: [236, 31, 128]
+ }, {
+ name: "Light Pink",
+ rgb: [243, 141, 169]
+ }, {
+ name: "Dark Brown",
+ rgb: [104, 70, 52]
+ }, {
+ name: "Brown",
+ rgb: [149, 104, 42]
+ }, {
+ name: "Beige",
+ rgb: [248, 178, 119]
+ }, {
+ name: "Medium Gray",
+ rgb: [170, 170, 170]
+ }, {
+ name: "Dark Red",
+ rgb: [165, 14, 30]
+ }, {
+ name: "Light Red",
+ rgb: [250, 128, 114]
+ }, {
+ name: "Dark Orange",
+ rgb: [228, 92, 26]
+ }, {
+ name: "Light Tan",
+ rgb: [214, 181, 148]
+ }, {
+ name: "Dark Goldenrod",
+ rgb: [156, 132, 49]
+ }, {
+ name: "Goldenrod",
+ rgb: [197, 173, 49]
+ }, {
+ name: "Light Goldenrod",
+ rgb: [232, 212, 95]
+ }, {
+ name: "Dark Olive",
+ rgb: [74, 107, 58]
+ }, {
+ name: "Olive",
+ rgb: [90, 148, 74]
+ }, {
+ name: "Light Olive",
+ rgb: [132, 197, 115]
+ }, {
+ name: "Dark Cyan",
+ rgb: [15, 121, 159]
+ }, {
+ name: "Light Cyan",
+ rgb: [187, 250, 242]
+ }, {
+ name: "Light Blue",
+ rgb: [125, 199, 255]
+ }, {
+ name: "Dark Indigo",
+ rgb: [77, 49, 184]
+ }, {
+ name: "Dark Slate Blue",
+ rgb: [74, 66, 132]
+ }, {
+ name: "Slate Blue",
+ rgb: [122, 113, 196]
+ }, {
+ name: "Light Slate Blue",
+ rgb: [181, 174, 241]
+ }, {
+ name: "Light Brown",
+ rgb: [219, 164, 99]
+ }, {
+ name: "Dark Beige",
+ rgb: [209, 128, 81]
+ }, {
+ name: "Light Beige",
+ rgb: [255, 197, 165]
+ }, {
+ name: "Dark Peach",
+ rgb: [155, 82, 73]
+ }, {
+ name: "Peach",
+ rgb: [209, 128, 120]
+ }, {
+ name: "Light Peach",
+ rgb: [250, 182, 164]
+ }, {
+ name: "Dark Tan",
+ rgb: [123, 99, 82]
+ }, {
+ name: "Tan",
+ rgb: [156, 132, 107]
+ }, {
+ name: "Dark Slate",
+ rgb: [51, 57, 65]
+ }, {
+ name: "Slate",
+ rgb: [109, 117, 141]
+ }, {
+ name: "Light Slate",
+ rgb: [179, 185, 209]
+ }, {
+ name: "Dark Stone",
+ rgb: [109, 100, 63]
+ }, {
+ name: "Stone",
+ rgb: [148, 140, 107]
+ }, {
+ name: "Light Stone",
+ rgb: [205, 197, 158]
+ }],
+ Pe = {
+ needsPhoneVerification: "needs_phone_verification"
+ },
+ Ie = {
+ Droplet: {},
+ "Max. Charge": {},
+ "Paint Charge": {},
+ Color: {},
+ Flag: {},
+ "Profile Picture": {}
+ },
+ Ge = {
+ 10: {
+ name: "25,000 Droplets",
+ price: 500,
+ isDollar: !0,
+ lookupKey: "droplets_5",
+ items: [{
+ name: "Droplet",
+ amount: 25e3
+ }]
+ },
+ 20: {
+ name: "78,750 Droplets",
+ price: 1500,
+ isDollar: !0,
+ lookupKey: "droplets_15",
+ items: [{
+ name: "Droplet",
+ amount: 76750
+ }]
+ },
+ 30: {
+ name: "165,000 Droplets",
+ price: 3e3,
+ isDollar: !0,
+ lookupKey: "droplets_30",
+ items: [{
+ name: "Droplet",
+ amount: 165e3
+ }]
+ },
+ 40: {
+ name: "287,500 Droplets",
+ price: 5e3,
+ isDollar: !0,
+ lookupKey: "droplets_50",
+ items: [{
+ name: "Droplet",
+ amount: 287500
+ }]
+ },
+ 50: {
+ name: "450,000 Droplets",
+ price: 7500,
+ isDollar: !0,
+ lookupKey: "droplets_75",
+ items: [{
+ name: "Droplet",
+ amount: 45e4
+ }]
+ },
+ 60: {
+ name: "625,000 Droplets",
+ price: 1e4,
+ isDollar: !0,
+ lookupKey: "droplets_100",
+ items: [{
+ name: "Droplet",
+ amount: 625e3
+ }]
+ },
+ 70: {
+ name: "+5 Max. Charges",
+ price: 500,
+ isDollar: !1,
+ items: [{
+ name: "Max. Charge",
+ amount: 5
+ }]
+ },
+ 80: {
+ name: "+30 Paint Charges",
+ price: 500,
+ isDollar: !1,
+ items: [{
+ name: "Paint Charge",
+ amount: 30
+ }]
+ },
+ 100: {
+ name: "Unlock Color",
+ price: 2e3,
+ isDollar: !1,
+ items: [{
+ name: "Color",
+ amount: 1
+ }]
+ },
+ 110: {
+ name: "Flag",
+ price: 2e4,
+ isDollar: !1,
+ items: [{
+ name: "Flag",
+ amount: 1
+ }]
+ },
+ 120: {
+ name: "Profile Picture",
+ price: 2e4,
+ isDollar: !1,
+ items: [{
+ name: "Profile Picture",
+ amount: 1
+ }]
+ }
+ },
+ Le = JSON.parse(`[{"id":1,"name":"Afghanistan","code":"AF","flag":"🇦🇫"},{"id":2,"name":"Albania","code":"AL","flag":"🇦🇱"},{"id":3,"name":"Algeria","code":"DZ","flag":"🇩🇿"},{"id":4,"name":"American Samoa","code":"AS","flag":"🇦🇸"},{"id":5,"name":"Andorra","code":"AD","flag":"🇦🇩"},{"id":6,"name":"Angola","code":"AO","flag":"🇦🇴"},{"id":7,"name":"Anguilla","code":"AI","flag":"🇦🇮"},{"id":8,"name":"Antarctica","code":"AQ","flag":"🇦🇶"},{"id":9,"name":"Antigua and Barbuda","code":"AG","flag":"🇦🇬"},{"id":10,"name":"Argentina","code":"AR","flag":"🇦🇷"},{"id":11,"name":"Armenia","code":"AM","flag":"🇦🇲"},{"id":12,"name":"Aruba","code":"AW","flag":"🇦🇼"},{"id":13,"name":"Australia","code":"AU","flag":"🇦🇺"},{"id":14,"name":"Austria","code":"AT","flag":"🇦🇹"},{"id":15,"name":"Azerbaijan","code":"AZ","flag":"🇦🇿"},{"id":16,"name":"Bahamas","code":"BS","flag":"🇧🇸"},{"id":17,"name":"Bahrain","code":"BH","flag":"🇧🇭"},{"id":18,"name":"Bangladesh","code":"BD","flag":"🇧🇩"},{"id":19,"name":"Barbados","code":"BB","flag":"🇧🇧"},{"id":20,"name":"Belarus","code":"BY","flag":"🇧🇾"},{"id":21,"name":"Belgium","code":"BE","flag":"🇧🇪"},{"id":22,"name":"Belize","code":"BZ","flag":"🇧🇿"},{"id":23,"name":"Benin","code":"BJ","flag":"🇧🇯"},{"id":24,"name":"Bermuda","code":"BM","flag":"🇧🇲"},{"id":25,"name":"Bhutan","code":"BT","flag":"🇧🇹"},{"id":26,"name":"Bolivia","code":"BO","flag":"🇧🇴"},{"id":27,"name":"Bonaire","code":"BQ","flag":"🇧🇶"},{"id":28,"name":"Bosnia and Herzegovina","code":"BA","flag":"🇧🇦"},{"id":29,"name":"Botswana","code":"BW","flag":"🇧🇼"},{"id":30,"name":"Bouvet Island","code":"BV","flag":"🇧🇻"},{"id":31,"name":"Brazil","code":"BR","flag":"🇧🇷"},{"id":32,"name":"British Indian Ocean Territory","code":"IO","flag":"🇮🇴"},{"id":33,"name":"Brunei Darussalam","code":"BN","flag":"🇧🇳"},{"id":34,"name":"Bulgaria","code":"BG","flag":"🇧🇬"},{"id":35,"name":"Burkina Faso","code":"BF","flag":"🇧🇫"},{"id":36,"name":"Burundi","code":"BI","flag":"🇧🇮"},{"id":37,"name":"Cabo Verde","code":"CV","flag":"🇨🇻"},{"id":38,"name":"Cambodia","code":"KH","flag":"🇰🇭"},{"id":39,"name":"Cameroon","code":"CM","flag":"🇨🇲"},{"id":40,"name":"Canada","code":"CA","flag":"🇨🇦"},{"id":41,"name":"Cayman Islands","code":"KY","flag":"🇰🇾"},{"id":42,"name":"Central African Republic","code":"CF","flag":"🇨🇫"},{"id":43,"name":"Chad","code":"TD","flag":"🇹🇩"},{"id":44,"name":"Chile","code":"CL","flag":"🇨🇱"},{"id":45,"name":"China","code":"CN","flag":"🇨🇳"},{"id":46,"name":"Christmas Island","code":"CX","flag":"🇨🇽"},{"id":47,"name":"Cocos (Keeling) Islands","code":"CC","flag":"🇨🇨"},{"id":48,"name":"Colombia","code":"CO","flag":"🇨🇴"},{"id":49,"name":"Comoros","code":"KM","flag":"🇰🇲"},{"id":50,"name":"Congo","code":"CG","flag":"🇨🇬"},{"id":51,"name":"Cook Islands","code":"CK","flag":"🇨🇰"},{"id":52,"name":"Costa Rica","code":"CR","flag":"🇨🇷"},{"id":53,"name":"Croatia","code":"HR","flag":"🇭🇷"},{"id":54,"name":"Cuba","code":"CU","flag":"🇨🇺"},{"id":55,"name":"Curaçao","code":"CW","flag":"🇨🇼"},{"id":56,"name":"Cyprus","code":"CY","flag":"🇨🇾"},{"id":57,"name":"Czechia","code":"CZ","flag":"🇨🇿"},{"id":58,"name":"Côte d'Ivoire","code":"CI","flag":"🇨🇮"},{"id":59,"name":"Denmark","code":"DK","flag":"🇩🇰"},{"id":60,"name":"Djibouti","code":"DJ","flag":"🇩🇯"},{"id":61,"name":"Dominica","code":"DM","flag":"🇩🇲"},{"id":62,"name":"Dominican Republic","code":"DO","flag":"🇩🇴"},{"id":63,"name":"Ecuador","code":"EC","flag":"🇪🇨"},{"id":64,"name":"Egypt","code":"EG","flag":"🇪🇬"},{"id":65,"name":"El Salvador","code":"SV","flag":"🇸🇻"},{"id":66,"name":"Equatorial Guinea","code":"GQ","flag":"🇬🇶"},{"id":67,"name":"Eritrea","code":"ER","flag":"🇪🇷"},{"id":68,"name":"Estonia","code":"EE","flag":"🇪🇪"},{"id":69,"name":"Eswatini","code":"SZ","flag":"🇸🇿"},{"id":70,"name":"Ethiopia","code":"ET","flag":"🇪🇹"},{"id":71,"name":"Falkland Islands (Malvinas)","code":"FK","flag":"🇫🇰"},{"id":72,"name":"Faroe Islands","code":"FO","flag":"🇫🇴"},{"id":73,"name":"Fiji","code":"FJ","flag":"🇫🇯"},{"id":74,"name":"Finland","code":"FI","flag":"🇫🇮"},{"id":75,"name":"France","code":"FR","flag":"🇫🇷"},{"id":76,"name":"French Guiana","code":"GF","flag":"🇬🇫"},{"id":77,"name":"French Polynesia","code":"PF","flag":"🇵🇫"},{"id":78,"name":"French Southern Territories","code":"TF","flag":"🇹🇫"},{"id":79,"name":"Gabon","code":"GA","flag":"🇬🇦"},{"id":80,"name":"Gambia","code":"GM","flag":"🇬🇲"},{"id":81,"name":"Georgia","code":"GE","flag":"🇬🇪"},{"id":82,"name":"Germany","code":"DE","flag":"🇩🇪"},{"id":83,"name":"Ghana","code":"GH","flag":"🇬🇭"},{"id":84,"name":"Gibraltar","code":"GI","flag":"🇬🇮"},{"id":85,"name":"Greece","code":"GR","flag":"🇬🇷"},{"id":86,"name":"Greenland","code":"GL","flag":"🇬🇱"},{"id":87,"name":"Grenada","code":"GD","flag":"🇬🇩"},{"id":88,"name":"Guadeloupe","code":"GP","flag":"🇬🇵"},{"id":89,"name":"Guam","code":"GU","flag":"🇬🇺"},{"id":90,"name":"Guatemala","code":"GT","flag":"🇬🇹"},{"id":91,"name":"Guernsey","code":"GG","flag":"🇬🇬"},{"id":92,"name":"Guinea","code":"GN","flag":"🇬🇳"},{"id":93,"name":"Guinea-Bissau","code":"GW","flag":"🇬🇼"},{"id":94,"name":"Guyana","code":"GY","flag":"🇬🇾"},{"id":95,"name":"Haiti","code":"HT","flag":"🇭🇹"},{"id":96,"name":"Heard Island and McDonald Islands","code":"HM","flag":"🇭🇲"},{"id":97,"name":"Honduras","code":"HN","flag":"🇭🇳"},{"id":98,"name":"Hong Kong","code":"HK","flag":"🇭🇰"},{"id":99,"name":"Hungary","code":"HU","flag":"🇭🇺"},{"id":100,"name":"Iceland","code":"IS","flag":"🇮🇸"},{"id":101,"name":"India","code":"IN","flag":"🇮🇳"},{"id":102,"name":"Indonesia","code":"ID","flag":"🇮🇩"},{"id":103,"name":"Iran","code":"IR","flag":"🇮🇷"},{"id":104,"name":"Iraq","code":"IQ","flag":"🇮🇶"},{"id":105,"name":"Ireland","code":"IE","flag":"🇮🇪"},{"id":106,"name":"Isle of Man","code":"IM","flag":"🇮🇲"},{"id":107,"name":"Israel","code":"IL","flag":"🇮🇱"},{"id":108,"name":"Italy","code":"IT","flag":"🇮🇹"},{"id":109,"name":"Jamaica","code":"JM","flag":"🇯🇲"},{"id":110,"name":"Japan","code":"JP","flag":"🇯🇵"},{"id":111,"name":"Jersey","code":"JE","flag":"🇯🇪"},{"id":112,"name":"Jordan","code":"JO","flag":"🇯🇴"},{"id":113,"name":"Kazakhstan","code":"KZ","flag":"🇰🇿"},{"id":114,"name":"Kenya","code":"KE","flag":"🇰🇪"},{"id":115,"name":"Kiribati","code":"KI","flag":"🇰🇮"},{"id":116,"name":"Kosovo","code":"XK","flag":"🇽🇰"},{"id":117,"name":"Kuwait","code":"KW","flag":"🇰🇼"},{"id":118,"name":"Kyrgyzstan","code":"KG","flag":"🇰🇬"},{"id":119,"name":"Laos","code":"LA","flag":"🇱🇦"},{"id":120,"name":"Latvia","code":"LV","flag":"🇱🇻"},{"id":121,"name":"Lebanon","code":"LB","flag":"🇱🇧"},{"id":122,"name":"Lesotho","code":"LS","flag":"🇱🇸"},{"id":123,"name":"Liberia","code":"LR","flag":"🇱🇷"},{"id":124,"name":"Libya","code":"LY","flag":"🇱🇾"},{"id":125,"name":"Liechtenstein","code":"LI","flag":"🇱🇮"},{"id":126,"name":"Lithuania","code":"LT","flag":"🇱🇹"},{"id":127,"name":"Luxembourg","code":"LU","flag":"🇱🇺"},{"id":128,"name":"Macao","code":"MO","flag":"🇲🇴"},{"id":129,"name":"Madagascar","code":"MG","flag":"🇲🇬"},{"id":130,"name":"Malawi","code":"MW","flag":"🇲🇼"},{"id":131,"name":"Malaysia","code":"MY","flag":"🇲🇾"},{"id":132,"name":"Maldives","code":"MV","flag":"🇲🇻"},{"id":133,"name":"Mali","code":"ML","flag":"🇲🇱"},{"id":134,"name":"Malta","code":"MT","flag":"🇲🇹"},{"id":135,"name":"Marshall Islands","code":"MH","flag":"🇲🇭"},{"id":136,"name":"Martinique","code":"MQ","flag":"🇲🇶"},{"id":137,"name":"Mauritania","code":"MR","flag":"🇲🇷"},{"id":138,"name":"Mauritius","code":"MU","flag":"🇲🇺"},{"id":139,"name":"Mayotte","code":"YT","flag":"🇾🇹"},{"id":140,"name":"Mexico","code":"MX","flag":"🇲🇽"},{"id":141,"name":"Micronesia","code":"FM","flag":"🇫🇲"},{"id":142,"name":"Moldova","code":"MD","flag":"🇲🇩"},{"id":143,"name":"Monaco","code":"MC","flag":"🇲🇨"},{"id":144,"name":"Mongolia","code":"MN","flag":"🇲🇳"},{"id":145,"name":"Montenegro","code":"ME","flag":"🇲🇪"},{"id":146,"name":"Montserrat","code":"MS","flag":"🇲🇸"},{"id":147,"name":"Morocco","code":"MA","flag":"🇲🇦"},{"id":148,"name":"Mozambique","code":"MZ","flag":"🇲🇿"},{"id":149,"name":"Myanmar","code":"MM","flag":"🇲🇲"},{"id":150,"name":"Namibia","code":"NA","flag":"🇳🇦"},{"id":151,"name":"Nauru","code":"NR","flag":"🇳🇷"},{"id":152,"name":"Nepal","code":"NP","flag":"🇳🇵"},{"id":153,"name":"Netherlands","code":"NL","flag":"🇳🇱"},{"id":154,"name":"New Caledonia","code":"NC","flag":"🇳🇨"},{"id":155,"name":"New Zealand","code":"NZ","flag":"🇳🇿"},{"id":156,"name":"Nicaragua","code":"NI","flag":"🇳🇮"},{"id":157,"name":"Niger","code":"NE","flag":"🇳🇪"},{"id":158,"name":"Nigeria","code":"NG","flag":"🇳🇬"},{"id":159,"name":"Niue","code":"NU","flag":"🇳🇺"},{"id":160,"name":"Norfolk Island","code":"NF","flag":"🇳🇫"},{"id":161,"name":"North Korea","code":"KP","flag":"🇰🇵"},{"id":162,"name":"North Macedonia","code":"MK","flag":"🇲🇰"},{"id":163,"name":"Northern Mariana Islands","code":"MP","flag":"🇲🇵"},{"id":164,"name":"Norway","code":"NO","flag":"🇳🇴"},{"id":165,"name":"Oman","code":"OM","flag":"🇴🇲"},{"id":166,"name":"Pakistan","code":"PK","flag":"🇵🇰"},{"id":167,"name":"Palau","code":"PW","flag":"🇵🇼"},{"id":168,"name":"Palestine","code":"PS","flag":"🇵🇸"},{"id":169,"name":"Panama","code":"PA","flag":"🇵🇦"},{"id":170,"name":"Papua New Guinea","code":"PG","flag":"🇵🇬"},{"id":171,"name":"Paraguay","code":"PY","flag":"🇵🇾"},{"id":172,"name":"Peru","code":"PE","flag":"🇵🇪"},{"id":173,"name":"Philippines","code":"PH","flag":"🇵🇭"},{"id":174,"name":"Pitcairn","code":"PN","flag":"🇵🇳"},{"id":175,"name":"Poland","code":"PL","flag":"🇵🇱"},{"id":176,"name":"Portugal","code":"PT","flag":"🇵🇹"},{"id":177,"name":"Puerto Rico","code":"PR","flag":"🇵🇷"},{"id":178,"name":"Qatar","code":"QA","flag":"🇶🇦"},{"id":179,"name":"Republic of the Congo","code":"CD","flag":"🇨🇩"},{"id":180,"name":"Romania","code":"RO","flag":"🇷🇴"},{"id":181,"name":"Russia","code":"RU","flag":"🇷🇺"},{"id":182,"name":"Rwanda","code":"RW","flag":"🇷🇼"},{"id":183,"name":"Réunion","code":"RE","flag":"🇷🇪"},{"id":184,"name":"Saint Barthélemy","code":"BL","flag":"🇧🇱"},{"id":185,"name":"Saint Helena","code":"SH","flag":"🇸🇭"},{"id":186,"name":"Saint Kitts and Nevis","code":"KN","flag":"🇰🇳"},{"id":187,"name":"Saint Lucia","code":"LC","flag":"🇱🇨"},{"id":188,"name":"Saint Martin (French part)","code":"MF","flag":"🇲🇫"},{"id":189,"name":"Saint Pierre and Miquelon","code":"PM","flag":"🇵🇲"},{"id":190,"name":"Saint Vincent and the Grenadines","code":"VC","flag":"🇻🇨"},{"id":191,"name":"Samoa","code":"WS","flag":"🇼🇸"},{"id":192,"name":"San Marino","code":"SM","flag":"🇸🇲"},{"id":193,"name":"Sao Tome and Principe","code":"ST","flag":"🇸🇹"},{"id":194,"name":"Saudi Arabia","code":"SA","flag":"🇸🇦"},{"id":195,"name":"Senegal","code":"SN","flag":"🇸🇳"},{"id":196,"name":"Serbia","code":"RS","flag":"🇷🇸"},{"id":197,"name":"Seychelles","code":"SC","flag":"🇸🇨"},{"id":198,"name":"Sierra Leone","code":"SL","flag":"🇸🇱"},{"id":199,"name":"Singapore","code":"SG","flag":"🇸🇬"},{"id":200,"name":"Sint Maarten (Dutch part)","code":"SX","flag":"🇸🇽"},{"id":201,"name":"Slovakia","code":"SK","flag":"🇸🇰"},{"id":202,"name":"Slovenia","code":"SI","flag":"🇸🇮"},{"id":203,"name":"Solomon Islands","code":"SB","flag":"🇸🇧"},{"id":204,"name":"Somalia","code":"SO","flag":"🇸🇴"},{"id":205,"name":"South Africa","code":"ZA","flag":"🇿🇦"},{"id":206,"name":"South Georgia and the South Sandwich Islands","code":"GS","flag":"🇬🇸"},{"id":207,"name":"South Korea","code":"KR","flag":"🇰🇷"},{"id":208,"name":"South Sudan","code":"SS","flag":"🇸🇸"},{"id":209,"name":"Spain","code":"ES","flag":"🇪🇸"},{"id":210,"name":"Sri Lanka","code":"LK","flag":"🇱🇰"},{"id":211,"name":"Sudan","code":"SD","flag":"🇸🇩"},{"id":212,"name":"Suriname","code":"SR","flag":"🇸🇷"},{"id":213,"name":"Svalbard and Jan Mayen","code":"SJ","flag":"🇸🇯"},{"id":214,"name":"Sweden","code":"SE","flag":"🇸🇪"},{"id":215,"name":"Switzerland","code":"CH","flag":"🇨🇭"},{"id":216,"name":"Syrian Arab Republic","code":"SY","flag":"🇸🇾"},{"id":217,"name":"Taiwan Province of China","code":"TW","flag":"🇨🇳"},{"id":218,"name":"Tajikistan","code":"TJ","flag":"🇹🇯"},{"id":219,"name":"Tanzania","code":"TZ","flag":"🇹🇿"},{"id":220,"name":"Thailand","code":"TH","flag":"🇹🇭"},{"id":221,"name":"Timor-Leste","code":"TL","flag":"🇹🇱"},{"id":222,"name":"Togo","code":"TG","flag":"🇹🇬"},{"id":223,"name":"Tokelau","code":"TK","flag":"🇹🇰"},{"id":224,"name":"Tonga","code":"TO","flag":"🇹🇴"},{"id":225,"name":"Trinidad and Tobago","code":"TT","flag":"🇹🇹"},{"id":226,"name":"Tunisia","code":"TN","flag":"🇹🇳"},{"id":227,"name":"Turkmenistan","code":"TM","flag":"🇹🇲"},{"id":228,"name":"Turks and Caicos Islands","code":"TC","flag":"🇹🇨"},{"id":229,"name":"Tuvalu","code":"TV","flag":"🇹🇻"},{"id":230,"name":"Türkiye","code":"TR","flag":"🇹🇷"},{"id":231,"name":"Uganda","code":"UG","flag":"🇺🇬"},{"id":232,"name":"Ukraine","code":"UA","flag":"🇺🇦"},{"id":233,"name":"United Arab Emirates","code":"AE","flag":"🇦🇪"},{"id":234,"name":"United Kingdom","code":"GB","flag":"🇬🇧"},{"id":235,"name":"United States","code":"US","flag":"🇺🇸"},{"id":236,"name":"United States Minor Outlying Islands","code":"UM","flag":"🇺🇲"},{"id":237,"name":"Uruguay","code":"UY","flag":"🇺🇾"},{"id":238,"name":"Uzbekistan","code":"UZ","flag":"🇺🇿"},{"id":239,"name":"Vanuatu","code":"VU","flag":"🇻🇺"},{"id":240,"name":"Vatican City","code":"VA","flag":"🇻🇦"},{"id":241,"name":"Venezuela","code":"VE","flag":"🇻🇪"},{"id":242,"name":"Viet Nam","code":"VN","flag":"🇻🇳"},{"id":243,"name":"Virgin Islands","code":"VG","flag":"🇻🇬"},{"id":244,"name":"Virgin Islands","code":"VI","flag":"🇻🇮"},{"id":245,"name":"Wallis and Futuna","code":"WF","flag":"🇼🇫"},{"id":246,"name":"Western Sahara","code":"EH","flag":"🇪🇭"},{"id":247,"name":"Yemen","code":"YE","flag":"🇾🇪"},{"id":248,"name":"Zambia","code":"ZM","flag":"🇿🇲"},{"id":249,"name":"Zimbabwe","code":"ZW","flag":"🇿🇼"},{"id":250,"name":"Åland Islands","code":"AX","flag":"🇦🇽"},{"id":251,"name":"Canary Islands","code":"IC","flag":"🇮🇨"}]`),
+ I = {
+ seasons: De,
+ regionSize: ke,
+ refreshIntervalMs: Te,
+ colors: Be,
+ errors: Pe,
+ items: Ie,
+ products: Ge,
+ countries: Le
+ },
+ B = I,
+ Z = I.seasons.length - 1;
+I.seasons[Z].zoom;
+I.seasons[Z].tileSize;
+const Ae = Y(Me),
+ v = `cache-${oe}`,
+ Re = new Set([...ne, ...ie]),
+ k = self,
+ P = new Map;
+let w = [];
+self.addEventListener("install", event => {
+ event.waitUntil(Promise.resolve());
+});
+k.addEventListener("activate", e => {
+ async function n() {
+ for (const i of await caches.keys()) i !== v && await caches.delete(i)
+ }
+ e.waitUntil(n())
+});
+k.addEventListener("fetch", e => {
+ if (e.request.method !== "GET") return;
+ async function n() {
+ const l = new URL(e.request.url);
+ try {
+ return await i(l)
+ } catch (s) {
+ const m = await (await caches.open(v)).match(e.request);
+ if (m) return m;
+ throw s
+ }
+ }
+ async function i(l) {
+ var m, y;
+ const s = e.request.url.startsWith(ae) && l.pathname.match(/^.*\/s(\d+).*\/tiles\/(\d+)\/(\d+).png$/);
+ if (s) {
+ const t = P.get(e.clientId);
+ if (t || w.length) {
+ const _ = parseInt(s[1]),
+ G = parseInt(s[2]),
+ L = parseInt(s[3]),
+ W = Date.now(),
+ Q = 1.9 * B.refreshIntervalMs;
+ w = w.filter(o => W - o.time.getTime() < Q);
+ const $ = w.filter(({
+ data: o
+ }) => G === o.tile[0] && L === o.tile[1] && o.season === _).map(({
+ data: o
+ }) => ({
+ ...o
+ })),
+ X = ((m = t == null ? void 0 : t.data) == null ? void 0 : m.filter(o => G === o.tile[0] && L === o.tile[1] && o.season === _)) ?? [],
+ x = $.concat(X);
+ if (x.length || t) {
+ await Ae;
+ let o, A;
+ const T = je(G, L, _),
+ f = await ((y = t == null ? void 0 : t.cachedTiles) == null ? void 0 : y.get(T)),
+ O = f && W - f.time.getTime() < B.refreshIntervalMs;
+ if (O) o = structuredClone(f.png), A = f.init;
+ else {
+ let g = f;
+ if (t)
+ if (f === void 0) {
+ t.cachedTiles.set(T, p());
+ const c = await t.cachedTiles.get(T);
+ c && (g = c)
+ } else !O && !f.refreshing && (f.refreshing = !0, setTimeout(async () => {
+ try {
+ const c = await p();
+ t.cachedTiles.set(T, new Promise(h => h(c)));
+ const d = await k.clients.get(e == null ? void 0 : e.clientId);
+ d == null || d.postMessage({
+ type: "refreshPixelArt"
+ })
+ } catch {
+ f.refreshing = !1
+ }
+ }));
+ g || (g = await p()), o = structuredClone(g.png), A = g.init;
+ async function p() {
+ try {
+ const c = await fetch(e == null ? void 0 : e.request);
+ if (c && c.status !== 404) {
+ const d = await c.blob();
+ return {
+ png: await Ce(await d.arrayBuffer()),
+ init: {
+ headers: c.headers,
+ status: c.status,
+ statusText: c.statusText
+ },
+ time: new Date,
+ refreshing: !1
+ }
+ } else {
+ console.warn("painting 404 tile");
+ const d = B.seasons[_].tileSize;
+ return {
+ png: N(d, d),
+ init: {
+ headers: {
+ "Content-Type": "image/png"
+ },
+ status: 200
+ },
+ time: new Date,
+ refreshing: !1
+ }
+ }
+ } catch (c) {
+ if (console.error("Error while fetching in servicer worker: ", c), f) return f;
+ {
+ const d = B.seasons[_].tileSize;
+ return {
+ png: N(d, d),
+ init: {
+ headers: {
+ "Content-Type": "image/png"
+ },
+ status: 200
+ },
+ time: new Date,
+ refreshing: !1
+ }
+ }
+ }
+ }
+ }
+ const R = new Map;
+ for (const g of x) {
+ const [p, c] = g.pixel, d = p + c * o.width << 2, h = g.color;
+ R.get(d) || R.set(d, [o.data[d], o.data[d + 1], o.data[d + 2], o.data[d + 3]]), o.data[d] = h.r, o.data[d + 1] = h.g, o.data[d + 2] = h.b, o.data[d + 3] = h.a
+ }
+ const ee = await V(o);
+ for (const [g, p] of R.entries()) o.data[g] = p[0], o.data[g + 1] = p[1], o.data[g + 2] = p[2], o.data[g + 3] = p[3];
+ return new Response(ee, A)
+ }
+ }
+ }
+ const u = await fetch(e == null ? void 0 : e.request);
+ if (s && u.status === 404) {
+ const t = await V(N(1, 1));
+ return new Response(t, {
+ headers: {
+ "Content-Type": "image/png"
+ },
+ status: 200
+ })
+ }
+ return u
+ }
+ e.respondWith(n())
+});
+k.addEventListener("message", e => {
+ var i, l;
+ const n = e.data;
+ try {
+ const s = ((i = e.source) == null ? void 0 : i.id) ?? "none";
+ switch (n == null ? void 0 : n.type) {
+ case "previewPixels":
+ const u = n.data,
+ m = P.get(s);
+ m ? m.data = u : P.set(s, {
+ data: u,
+ cachedTiles: new Map
+ });
+ break;
+ case "clearPixelPreview":
+ P.delete(s);
+ break;
+ case "paintPixels":
+ w.push(...n.data.map(t => ({
+ data: t,
+ time: new Date
+ })));
+ break;
+ case "unpaintPixels":
+ const y = new Set(n.data.map(t => z(t)));
+ w = w.filter(({
+ data: t
+ }) => !y.has(z(t)));
+ break
+ }
+ } finally {
+ (l = e.source) == null || l.postMessage({
+ id: n.id
+ })
+ }
+});
+
+function je(e, n, i) {
+ return `t=(${e},${n});s=${i}`
+}
+
+function N(e, n) {
+ return {
+ data: new Uint8ClampedArray(e * n * 4),
+ width: e,
+ height: n,
+ colorSpace: "srgb"
+ }
+}
\ No newline at end of file
diff --git a/frontend-src/static/site.webmanifest b/frontend-src/static/site.webmanifest
new file mode 100644
index 0000000..dad99be
--- /dev/null
+++ b/frontend-src/static/site.webmanifest
@@ -0,0 +1,53 @@
+{
+ "name": "Wplace",
+ "short_name": "Wplace",
+ "description": "Wplace is a collaborative, real-time pixel canvas layered over the world map, where anyone can paint and create art together.",
+ "start_url": "/",
+ "theme_color": "#f8f4f0",
+ "background_color": "#ffffff",
+ "display": "standalone",
+ "icons": [
+ {
+ "src": "/img/web-app-manifest-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/img/web-app-manifest-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "screenshots": [
+ {
+ "src": "/img/pwa-void-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/pwa-kiev-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/pwa-paint-heart-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/pwa-country-leaderboard-mobile.png",
+ "type": "image/png",
+ "sizes": "1080x2170",
+ "form_factor": "narrow"
+ },
+ {
+ "src": "/img/og-image.png",
+ "type": "image/png",
+ "sizes": "1200x630",
+ "form_factor": "wide"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend-src/svelte.config.js b/frontend-src/svelte.config.js
new file mode 100644
index 0000000..9db3e5f
--- /dev/null
+++ b/frontend-src/svelte.config.js
@@ -0,0 +1,24 @@
+import adapter from '@sveltejs/adapter-static';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ preprocess: vitePreprocess(),
+
+ kit: {
+ adapter: adapter({
+ // Build output goes to ../frontend (parent backend's static folder)
+ pages: '../frontend',
+ assets: '../frontend',
+ fallback: '404.html',
+ precompress: false,
+ strict: true
+ }),
+ paths: {
+ // No base path needed - served from root
+ base: ''
+ }
+ }
+};
+
+export default config;
diff --git a/frontend-src/tailwind.config.js b/frontend-src/tailwind.config.js
new file mode 100644
index 0000000..362134f
--- /dev/null
+++ b/frontend-src/tailwind.config.js
@@ -0,0 +1,19 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./src/**/*.{html,js,svelte,ts}'],
+ theme: {
+ extend: {
+ fontFamily: {
+ pixel: ['PixelifySans', 'monospace']
+ }
+ }
+ },
+ plugins: [require('daisyui')],
+ daisyui: {
+ themes: ['light'],
+ darkTheme: false,
+ base: true,
+ styled: true,
+ utils: true
+ }
+};
diff --git a/frontend-src/tsconfig.json b/frontend-src/tsconfig.json
new file mode 100644
index 0000000..a8f10c8
--- /dev/null
+++ b/frontend-src/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "moduleResolution": "bundler"
+ }
+}
diff --git a/frontend-src/vite.config.ts b/frontend-src/vite.config.ts
new file mode 100644
index 0000000..e440791
--- /dev/null
+++ b/frontend-src/vite.config.ts
@@ -0,0 +1,15 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: [sveltekit()],
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:3000',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, '')
+ }
+ }
+ }
+});
diff --git a/frontend/404.html b/frontend/404.html
index 8a2637f..17e5659 100644
--- a/frontend/404.html
+++ b/frontend/404.html
@@ -4,124 +4,89 @@
+ openplace - Paint the world
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
-
+
+
-
+
+
-
+
+
+
+
+
+
+
+
+
+
-
- Version: 1759175263375
-
+
-
-
+
+
+
+
+