aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-11-15 18:00:59 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2025-11-15 18:00:59 +0100
commit4b9c57dc6547d0c9d105ac3767dcc90da758a25d (patch)
tree5150d5494e7591df2fe6d83d42fc4642e9b0d1b1 /src
parentf349c491284c0cb007a97c9a11220cc00adbb64f (diff)
Refactor code structure for improved readability and maintainability
Diffstat (limited to 'src')
-rw-r--r--src/frontend/app/components/StopMapSheet.css66
-rw-r--r--src/frontend/app/components/StopMapSheet.tsx353
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.css31
-rw-r--r--src/frontend/app/maps/styleloader.ts7
-rw-r--r--src/frontend/app/root.css1
-rw-r--r--src/frontend/app/routes/stops-$id.css7
-rw-r--r--src/frontend/app/routes/stops-$id.tsx2
-rw-r--r--src/frontend/public/maps/styles/openfreemap-dark.json1
-rw-r--r--src/frontend/public/maps/styles/openfreemap-light.json (renamed from src/frontend/public/maps/styles/openfreemap-any.json)0
9 files changed, 337 insertions, 131 deletions
diff --git a/src/frontend/app/components/StopMapSheet.css b/src/frontend/app/components/StopMapSheet.css
index 7a3b88c..5125ff0 100644
--- a/src/frontend/app/components/StopMapSheet.css
+++ b/src/frontend/app/components/StopMapSheet.css
@@ -6,10 +6,74 @@
border: 1px solid var(--border-color);
margin-block-start: 0;
flex-shrink: 0;
+ position: relative;
}
@media (max-width: 640px) {
.stop-map-container {
- height: 25vh;
+ height: 30vh;
}
}
+
+/* Floating controls */
+.map-floating-controls {
+ position: absolute;
+ left: 8px;
+ top: 8px;
+ display: flex;
+ gap: 8px;
+ z-index: 2;
+}
+
+.center-btn {
+ appearance: none;
+ border: 1px solid rgba(0,0,0,0.15);
+ background: color-mix(in oklab, var(--background-color, #fff) 85%, transparent);
+ color: var(--text-primary, #111);
+ padding: 6px;
+ border-radius: 6px;
+ font-size: 12px;
+ line-height: 1;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ cursor: pointer;
+}
+
+/* User location dot */
+.user-dot {
+ position: relative;
+ width: 22px;
+ height: 22px;
+}
+
+.user-dot__core {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 10px;
+ height: 10px;
+ margin-left: -5px;
+ margin-top: -5px;
+ background: #2a6df4;
+ border: 2px solid #fff;
+ border-radius: 50%;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.3);
+}
+
+.user-dot__pulse {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 22px;
+ height: 22px;
+ margin-left: -11px;
+ margin-top: -11px;
+ border-radius: 50%;
+ background: rgba(42, 109, 244, 0.25);
+ animation: userPulse 1.8s ease-out infinite;
+}
+
+@keyframes userPulse {
+ 0% { transform: scale(0.6); opacity: 0.8; }
+ 70% { transform: scale(1.2); opacity: 0.15; }
+ 100% { transform: scale(1.4); opacity: 0; }
+}
diff --git a/src/frontend/app/components/StopMapSheet.tsx b/src/frontend/app/components/StopMapSheet.tsx
index 2dc85db..e87e8c8 100644
--- a/src/frontend/app/components/StopMapSheet.tsx
+++ b/src/frontend/app/components/StopMapSheet.tsx
@@ -1,6 +1,6 @@
import maplibregl from "maplibre-gl";
import React, { useEffect, useMemo, useRef, useState } from "react";
-import Map, { Marker, NavigationControl, type MapRef } from "react-map-gl/maplibre";
+import Map, { AttributionControl, Marker, type MapRef } from "react-map-gl/maplibre";
import { useApp } from "~/AppContext";
import { getLineColor } from "~/data/LineColors";
import type { RegionId } from "~/data/RegionConfig";
@@ -35,6 +35,67 @@ export const StopMap: React.FC<StopMapProps> = ({
const [styleSpec, setStyleSpec] = useState<any | null>(null);
const mapRef = useRef<MapRef | null>(null);
const hasFitBounds = useRef(false);
+ const [userPosition, setUserPosition] = useState<{
+ latitude: number;
+ longitude: number;
+ accuracy?: number;
+ } | null>(null);
+ const geoWatchId = useRef<number | null>(null);
+ const [zoom, setZoom] = useState<number>(16);
+ const [moveTick, setMoveTick] = useState<number>(0);
+
+ type Pt = { lat: number; lon: number };
+ const haversineKm = (a: Pt, b: Pt) => {
+ const R = 6371;
+ const dLat = ((b.lat - a.lat) * Math.PI) / 180;
+ const dLon = ((b.lon - a.lon) * Math.PI) / 180;
+ const lat1 = (a.lat * Math.PI) / 180;
+ const lat2 = (b.lat * Math.PI) / 180;
+ const sinDLat = Math.sin(dLat / 2);
+ const sinDLon = Math.sin(dLon / 2);
+ const h = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon;
+ return 2 * R * Math.asin(Math.min(1, Math.sqrt(h)));
+ };
+ const computeFocusPoints = (): Pt[] => {
+ const buses: Pt[] = [];
+ for (const c of busPositions) {
+ if (c.currentPosition) buses.push({ lat: c.currentPosition.latitude, lon: c.currentPosition.longitude });
+ }
+ const stopPt = stop.latitude && stop.longitude ? { lat: stop.latitude, lon: stop.longitude } : null;
+ const userPt = userPosition ? { lat: userPosition.latitude, lon: userPosition.longitude } : null;
+
+ if (buses.length === 0 && !stopPt && !userPt) return [];
+
+ // Choose anchor for proximity: stop > user > average of buses
+ let anchor: Pt | null = stopPt || userPt || null;
+ if (!anchor && buses.length) {
+ let lat = 0, lon = 0;
+ for (const b of buses) { lat += b.lat; lon += b.lon; }
+ anchor = { lat: lat / buses.length, lon: lon / buses.length };
+ }
+
+ const nearBuses = buses
+ .map((p) => ({ p, d: anchor ? haversineKm(anchor, p) : 0 }))
+ .sort((a, b) => a.d - b.d)
+ .slice(0, 8) // take closest N
+ .filter((x) => x.d <= 8) // within 8km
+ .map((x) => x.p);
+
+ const pts: Pt[] = [];
+ if (stopPt) pts.push(stopPt);
+ pts.push(...nearBuses);
+ if (userPt) {
+ // include user if not too far from anchor
+ const includeUser = anchor ? haversineKm(anchor, userPt) <= 10 : true;
+ if (includeUser) pts.push(userPt);
+ }
+ // Fallback: if no buses survived, at least return stop or user
+ if (pts.length === 0) {
+ if (stopPt) return [stopPt];
+ if (userPt) return [userPt];
+ }
+ return pts;
+ };
useEffect(() => {
let mounted = true;
@@ -48,6 +109,42 @@ export const StopMap: React.FC<StopMapProps> = ({
};
}, [theme]);
+ // Geolocation: request immediately without blocking UI; update when available.
+ useEffect(() => {
+ if (!("geolocation" in navigator)) return;
+ try {
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ setUserPosition({
+ latitude: pos.coords.latitude,
+ longitude: pos.coords.longitude,
+ accuracy: pos.coords.accuracy,
+ });
+ },
+ () => {},
+ { enableHighAccuracy: true, maximumAge: 15000, timeout: 5000 },
+ );
+ geoWatchId.current = navigator.geolocation.watchPosition(
+ (pos) => {
+ setUserPosition({
+ latitude: pos.coords.latitude,
+ longitude: pos.coords.longitude,
+ accuracy: pos.coords.accuracy,
+ });
+ },
+ () => {},
+ { enableHighAccuracy: true, maximumAge: 30000, timeout: 10000 },
+ );
+ } catch {}
+ return () => {
+ if (geoWatchId.current != null && "geolocation" in navigator) {
+ try {
+ navigator.geolocation.clearWatch(geoWatchId.current);
+ } catch {}
+ }
+ };
+ }, []);
+
const center = useMemo(() => {
if (stop.latitude && stop.longitude) {
return { latitude: stop.latitude, longitude: stop.longitude };
@@ -69,18 +166,7 @@ export const StopMap: React.FC<StopMapProps> = ({
useEffect(() => {
if (!styleSpec || !mapRef.current || hasFitBounds.current) return;
- const points: { lat: number; lon: number }[] = [];
- if (stop.latitude && stop.longitude) {
- points.push({ lat: stop.latitude, lon: stop.longitude });
- }
- for (const c of busPositions) {
- if (c.currentPosition) {
- points.push({
- lat: c.currentPosition.latitude,
- lon: c.currentPosition.longitude,
- });
- }
- }
+ const points = computeFocusPoints();
if (points.length === 0) return;
let minLat = points[0].lat,
@@ -94,26 +180,57 @@ export const StopMap: React.FC<StopMapProps> = ({
if (p.lon > maxLon) maxLon = p.lon;
}
- // ~1km in degrees
- const kmToDegLat = 1.0 / 111.32; // ≈0.008983
- const centerLat = (minLat + maxLat) / 2;
- const kmToDegLon = kmToDegLat / Math.max(Math.cos((centerLat * Math.PI) / 180), 0.1);
- const padLat = kmToDegLat;
- const padLon = kmToDegLon;
-
- const sw = [minLon - padLon, minLat - padLat] as [number, number];
- const ne = [maxLon + padLon, maxLat + padLat] as [number, number];
+ const sw = [minLon, minLat] as [number, number];
+ const ne = [maxLon, maxLat] as [number, number];
const bounds = new maplibregl.LngLatBounds(sw, ne);
+ // Determine predominant bus quadrant relative to stop to bias padding.
+ const padding: number | { top: number; right: number; bottom: number; left: number } = 24;
+
+ // If the diagonal is huge (likely outliers sneaked in), clamp via zoom fallback
try {
- mapRef.current.fitBounds(bounds, {
- padding: 32,
- duration: 700,
- maxZoom: 17,
- } as any);
+ if (points.length === 1) {
+ const only = points[0];
+ mapRef.current.getMap().jumpTo({ center: [only.lon, only.lat], zoom: 16 });
+ } else {
+ mapRef.current.fitBounds(bounds, {
+ padding: padding as any,
+ duration: 700,
+ maxZoom: 17,
+ } as any);
+ }
hasFitBounds.current = true;
} catch {}
- }, [styleSpec, stop.latitude, stop.longitude, busPositions]);
+ }, [styleSpec, stop.latitude, stop.longitude, busPositions, userPosition]);
+
+ const handleCenter = () => {
+ if (!mapRef.current) return;
+ const pts = computeFocusPoints();
+ if (pts.length === 0) return;
+
+ let minLat = pts[0].lat, maxLat = pts[0].lat, minLon = pts[0].lon, maxLon = pts[0].lon;
+ for (const p of pts) {
+ if (p.lat < minLat) minLat = p.lat;
+ if (p.lat > maxLat) maxLat = p.lat;
+ if (p.lon < minLon) minLon = p.lon;
+ if (p.lon > maxLon) maxLon = p.lon;
+ }
+
+ const sw = [minLon, minLat] as [number, number];
+ const ne = [maxLon, maxLat] as [number, number];
+ const bounds = new maplibregl.LngLatBounds(sw, ne);
+
+ const padding: number | { top: number; right: number; bottom: number; left: number } = 24;
+
+ try {
+ if (pts.length === 1) {
+ const only = pts[0];
+ mapRef.current.getMap().easeTo({ center: [only.lon, only.lat], zoom: 16, duration: 450 });
+ } else {
+ mapRef.current.fitBounds(bounds, { padding: padding as any, duration: 500, maxZoom: 17 } as any);
+ }
+ } catch {}
+ };
return (
<div className="stop-map-container">
@@ -129,90 +246,130 @@ export const StopMap: React.FC<StopMapProps> = ({
mapStyle={styleSpec}
attributionControl={false}
ref={mapRef}
+ onMove={(e) => {
+ setZoom(e.viewState.zoom);
+ setMoveTick((t) => (t + 1) % 1000000);
+ }}
>
- <NavigationControl position="top-left" />
+ {/* Compact attribution (closed by default) */}
+ <AttributionControl position="bottom-left" compact />
{/* Stop marker (center) */}
{stop.latitude && stop.longitude && (
- <Marker
- longitude={stop.longitude}
- latitude={stop.latitude}
- anchor="bottom"
- >
- <div
- style={{
- width: 14,
- height: 14,
- background: "#1976d2",
- border: "2px solid white",
- borderRadius: "5%",
- boxShadow: "0 0 0 2px rgba(0,0,0,0.2)",
- }}
- title={`Stop ${stop.stopId}`}
- />
+ <Marker longitude={stop.longitude} latitude={stop.latitude} anchor="bottom">
+ <div title={`Stop ${stop.stopId}`}>
+ <svg width="28" height="36" viewBox="0 0 28 36">
+ <defs>
+ <filter id="drop" x="-20%" y="-20%" width="140%" height="140%">
+ <feDropShadow dx="0" dy="1" stdDeviation="1" flood-opacity="0.35" />
+ </filter>
+ </defs>
+ <path d="M14 0C6.82 0 1 5.82 1 13c0 8.5 11 23 13 23s13-14.5 13-23C27 5.82 21.18 0 14 0z" fill="#1976d2" stroke="#fff" strokeWidth="2" filter="url(#drop)" />
+ <circle cx="14" cy="13" r="5" fill="#fff" />
+ <circle cx="14" cy="13" r="3" fill="#1976d2" />
+ </svg>
+ </div>
</Marker>
)}
- {/* Bus markers with heading */}
- {busPositions.map((c, idx) => {
- const p = c.currentPosition!;
- const lineColor = getLineColor(region, c.line);
- return (
- <Marker
- key={idx}
- longitude={p.longitude}
- latitude={p.latitude}
- anchor="center"
- >
- <div
- style={{
- display: "flex",
- flexDirection: "column",
- alignItems: "center",
- gap: 2,
- transform: `rotate(${p.orientationDegrees}deg)`,
- transformOrigin: "center center",
- }}
- >
- {/* Line number above */}
+ {/* User position marker (if available) */}
+ {userPosition && (
+ <Marker longitude={userPosition.longitude} latitude={userPosition.latitude} anchor="center">
+ <div className="user-dot" title="Your location">
+ <div className="user-dot__pulse" />
+ <div className="user-dot__core" />
+ </div>
+ </Marker>
+ )}
+
+ {/* Bus markers with heading and dynamic label spacing */}
+ {(() => {
+ const map = mapRef.current?.getMap();
+ const baseGap = 6;
+ const thresholdPx = 22;
+ const gaps: number[] = new Array(busPositions.length).fill(baseGap);
+ if (map && zoom >= 14.5 && busPositions.length > 1) {
+ const pts = busPositions.map((c) =>
+ c.currentPosition ? map.project([c.currentPosition.longitude, c.currentPosition.latitude]) : null,
+ );
+ for (let i = 0; i < pts.length; i++) {
+ const pi = pts[i];
+ if (!pi) continue;
+ let close = 0;
+ for (let j = 0; j < pts.length; j++) {
+ if (i === j) continue;
+ const pj = pts[j];
+ if (!pj) continue;
+ const dx = pi.x - pj.x;
+ const dy = pi.y - pj.y;
+ if (dx * dx + dy * dy <= thresholdPx * thresholdPx) close++;
+ }
+ gaps[i] = baseGap + Math.min(3, close) * 10;
+ }
+ }
+
+ return busPositions.map((c, idx) => {
+ const p = c.currentPosition!;
+ const lineColor = getLineColor(region, c.line);
+ const showLabel = zoom >= 13;
+ const labelGap = gaps[idx] ?? baseGap;
+ return (
+ <Marker key={idx} longitude={p.longitude} latitude={p.latitude} anchor="center">
<div
style={{
- background: lineColor.background,
- color: lineColor.text,
- padding: "2px 4px",
- borderRadius: 4,
- fontSize: 10,
- fontWeight: 700,
- lineHeight: 1,
- border: "1px solid #fff",
- boxShadow: "0 1px 2px rgba(0,0,0,0.3)",
- transform: `rotate(${-p.orientationDegrees}deg)`,
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ gap: labelGap,
+ transform: `rotate(${p.orientationDegrees}deg)`,
+ transformOrigin: "center center",
}}
>
- {c.line}
+ <svg
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ style={{ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.3))" }}
+ >
+ <path d="M12 2 L20 22 L12 18 L4 22 Z" fill={lineColor.background} stroke="#fff" strokeWidth="1.5" />
+ </svg>
+ {showLabel && (
+ <div
+ style={{
+ background: lineColor.background,
+ color: lineColor.text,
+ padding: "2px 4px",
+ borderRadius: 4,
+ fontSize: 10,
+ fontWeight: 700,
+ lineHeight: 1,
+ border: "1px solid #fff",
+ boxShadow: "0 1px 2px rgba(0,0,0,0.3)",
+ transform: `rotate(${-p.orientationDegrees}deg)`,
+ pointerEvents: "none",
+ zIndex: 0,
+ }}
+ >
+ {c.line}
+ </div>
+ )}
</div>
- {/* Arrow pointing direction */}
- <svg
- width="20"
- height="20"
- viewBox="0 0 24 24"
- style={{
- filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.3))",
- }}
- >
- <path
- d="M12 2 L20 22 L12 18 L4 22 Z"
- fill={lineColor.background}
- stroke="#fff"
- strokeWidth="1.5"
- />
- </svg>
- </div>
- </Marker>
- );
- })}
+ </Marker>
+ );
+ });
+ })()}
</Map>
)}
+ {/* Floating controls */}
+ <div className="map-floating-controls">
+ <button type="button" aria-label="Center" className="center-btn" onClick={handleCenter} title="Center view">
+ <svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
+ <circle cx="12" cy="12" r="3" fill="currentColor"/>
+ <path d="M12 2v3M12 19v3M2 12h3M19 12h3" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" strokeWidth="1.5"/>
+ </svg>
+ </button>
+ </div>
</div>
);
};
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
index ca136d8..4d6a3a8 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
@@ -2,7 +2,6 @@
font-size: 0.9rem;
color: var(--subtitle-color);
text-align: center;
- margin-bottom: 1rem;
padding: 0.5rem;
}
@@ -77,38 +76,24 @@
}
/* Time color states */
-.consolidated-circulation-card .arrival-time.time-running {
- color: #22c55e;
-}
-
+.consolidated-circulation-card .arrival-time.time-running,
.consolidated-circulation-card .arrival-time.time-running svg {
color: #22c55e;
}
-.consolidated-circulation-card .arrival-time.time-delayed {
- color: #09106e;
-}
-
+.consolidated-circulation-card .arrival-time.time-delayed,
.consolidated-circulation-card .arrival-time.time-delayed svg {
- color: #09106e;
-}
-
-/* Scheduled-only: dark blue in light mode, softer blue in dark mode */
-.consolidated-circulation-card .arrival-time.time-scheduled {
- color: #0b3d91; /* dark blue */
+ color: #ff6a00;
}
+.consolidated-circulation-card .arrival-time.time-scheduled,
.consolidated-circulation-card .arrival-time.time-scheduled svg {
- color: #0b3d91;
+ color: #0b3d91; /* dark blue */
}
-@media (prefers-color-scheme: dark) {
- .consolidated-circulation-card .arrival-time.time-scheduled {
- color: #8fb4ff; /* lighten for dark backgrounds */
- }
- .consolidated-circulation-card .arrival-time.time-scheduled svg {
- color: #8fb4ff;
- }
+[data-theme="dark"] .consolidated-circulation-card .arrival-time.time-scheduled,
+[data-theme="dark"] .consolidated-circulation-card .arrival-time.time-scheduled svg {
+ color: #8fb4ff; /* lighten for dark backgrounds */
}
.consolidated-circulation-card .distance-info {
diff --git a/src/frontend/app/maps/styleloader.ts b/src/frontend/app/maps/styleloader.ts
index 93f6693..d20fd31 100644
--- a/src/frontend/app/maps/styleloader.ts
+++ b/src/frontend/app/maps/styleloader.ts
@@ -5,8 +5,13 @@ export async function loadStyle(
styleName: string,
colorScheme: Theme,
): Promise<StyleSpecification> {
+ if (colorScheme == "system") {
+ const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
+ colorScheme = isDarkMode ? "dark" : "light";
+ }
+
if (styleName == "openfreemap") {
- const url = "/maps/styles/openfreemap-any.json";
+ const url = `/maps/styles/openfreemap-${colorScheme}.json`;
const resp = await fetch(url);
if (!resp.ok) {
diff --git a/src/frontend/app/root.css b/src/frontend/app/root.css
index 12441af..e832e96 100644
--- a/src/frontend/app/root.css
+++ b/src/frontend/app/root.css
@@ -202,4 +202,5 @@ body {
.maplibregl-ctrl-attrib-inner {
font-size: 0.9em;
+ color: var(--ml-c-link-2);
}
diff --git a/src/frontend/app/routes/stops-$id.css b/src/frontend/app/routes/stops-$id.css
index 3b377a7..7df3af2 100644
--- a/src/frontend/app/routes/stops-$id.css
+++ b/src/frontend/app/routes/stops-$id.css
@@ -276,16 +276,9 @@
.experimental-notice strong {
display: block;
- margin-bottom: 0.5rem;
color: #856404;
}
-.experimental-notice p {
- margin: 0;
- font-size: 0.9rem;
- line-height: 1.4;
-}
-
[data-theme="dark"] .experimental-notice {
background-color: #3d3100;
border-color: #ffc107;
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index 812821d..6e669ca 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -141,7 +141,7 @@ export default function Estimates() {
// Auto-refresh estimates data every 30 seconds (only if not in error state)
useAutoRefresh({
onRefresh: refreshData,
- interval: 30000,
+ interval: 12000,
enabled: !dataError,
});
diff --git a/src/frontend/public/maps/styles/openfreemap-dark.json b/src/frontend/public/maps/styles/openfreemap-dark.json
new file mode 100644
index 0000000..8e78862
--- /dev/null
+++ b/src/frontend/public/maps/styles/openfreemap-dark.json
@@ -0,0 +1 @@
+{"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":"rgb(242,243,240)"}},{"id":"park","type":"fill","source":"openmaptiles","source-layer":"park","filter":["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],"paint":{"fill-color":"rgb(230, 233, 229)"}},{"id":"water","type":"fill","source":"openmaptiles","source-layer":"water","filter":["all",["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],["!=",["get","brunnel"],"tunnel"]],"paint":{"fill-antialias":true,"fill-color":"rgb(194, 200, 202)"}},{"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(0,0%,98%)","fill-opacity":0.7}},{"id":"landcover_glacier","type":"fill","source":"openmaptiles","source-layer":"landcover","maxzoom":8,"filter":["all",["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],["==",["get","subclass"],"glacier"]],"paint":{"fill-color":"hsl(0,0%,98%)","fill-opacity":["interpolate",["linear"],["zoom"],0,1,8,0.5]}},{"id":"landuse_residential","type":"fill","source":"openmaptiles","source-layer":"landuse","maxzoom":16,"filter":["all",["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],["==",["get","class"],"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":"rgb(220,224,220)","fill-opacity":["interpolate",["linear"],["zoom"],8,0,12,1]}},{"id":"waterway","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["match",["geometry-type"],["LineString","MultiLineString"],true,false],"paint":{"line-color":"hsl(195,17%,78%)"}},{"id":"building","type":"fill","source":"openmaptiles","source-layer":"building","minzoom":12,"paint":{"fill-antialias":true,"fill-color":"rgb(234, 234, 229)","fill-outline-color":"rgb(219, 219, 218)"}},{"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":"rgb(213, 213, 213)","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":"rgb(234,234,234)","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(0,0%,88%)","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(0,0%,88%)","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":"rgba(255, 255, 255, 1)","fill-opacity":["interpolate",["linear"],["zoom"],13,0,14,1]}},{"id":"aeroway-runway","type":"line","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"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":"rgba(255, 255, 255, 1)","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":"rgb(242,243,240)"}},{"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":"rgb(242,243,240)","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":"rgb(234, 234, 234)","line-opacity":0.9,"line-width":["interpolate",["exponential",1.2],["zoom"],13,1,20,10]}},{"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(0,0%,88%)","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":"rgb(213, 213, 213)","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":"#fff","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":"hsla(0,0%,85%,0.69)","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":"rgb(213, 213, 213)","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,"#fff"],"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(0,0%,85%,0.53)","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":"#dddddd","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":"#fafafa","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":"#dddddd","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],["==",["get","class"],"rail"],["has","service"]],"layout":{"line-join":"round"},"paint":{"line-color":"#fafafa","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":"#dddddd","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":"#fafafa","line-dasharray":[3,3],"line-width":["interpolate",["exponential",1.3],["zoom"],16,2,20,6]}},{"id":"highway_motorway_bridge_casing","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["all",["==",["get","brunnel"],"bridge"],["==",["get","class"],"motorway"]]],"layout":{"line-cap":"butt","line-join":"miter"},"paint":{"line-color":"rgb(213, 213, 213)","line-dasharray":[2,0],"line-opacity":1,"line-width":["interpolate",["exponential",1.4],["zoom"],5.8,0,6,5,20,45]}},{"id":"highway_motorway_bridge_inner","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["all",["==",["get","brunnel"],"bridge"],["==",["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,"#fff"],"line-width":["interpolate",["exponential",1.4],["zoom"],4,2,6,1.3,20,30]}},{"id":"boundary_3","type":"line","source":"openmaptiles","source-layer":"boundary","minzoom":8,"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(0,0%,70%)","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(0,0%,70%)","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":"hsl(0,0%,66%)","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":"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,0%,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":11,"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":11,"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":12,"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":11,"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/src/frontend/public/maps/styles/openfreemap-any.json b/src/frontend/public/maps/styles/openfreemap-light.json
index cb528da..cb528da 100644
--- a/src/frontend/public/maps/styles/openfreemap-any.json
+++ b/src/frontend/public/maps/styles/openfreemap-light.json