diff --git a/src/components/LayerControl.jsx b/src/components/LayerControl.jsx
index 35b2313..b62484b 100644
--- a/src/components/LayerControl.jsx
+++ b/src/components/LayerControl.jsx
@@ -1,306 +1,343 @@
-import { useState, useEffect, useRef } from 'react'
-import { Layers, Trees, Mountain } from 'lucide-react'
-import { hasFeature, getConfig } from '../config'
-
-const STORAGE_KEY = 'navi-layer-prefs'
-
-function loadPrefs() {
- try {
- const raw = localStorage.getItem(STORAGE_KEY)
- if (raw) return JSON.parse(raw)
- } catch {}
- return null
-}
-
-function savePrefs(prefs) {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs))
-}
-
-export default function LayerControl({ mapRef }) {
- const [open, setOpen] = useState(false)
- const [hillshade, setHillshade] = useState(false)
- const [traffic, setTraffic] = useState(false)
- const [publicLands, setPublicLands] = useState(false)
- const [contours, setContours] = useState(false)
- const [contoursTest, setContoursTest] = useState(false)
- const [contoursTest10ft, setContoursTest10ft] = useState(false)
- const panelRef = useRef(null)
-
- // Initialize from localStorage or defaults on mount
- useEffect(() => {
- const saved = loadPrefs()
- const hsAvailable = hasFeature('has_hillshade')
- const trAvailable = hasFeature('has_traffic_overlay')
-
- const plAvailable = hasFeature('has_public_lands_layer')
- const ctAvailable = hasFeature('has_contours')
- const ctTestAvailable = hasFeature('has_contours_test')
- const ctTest10ftAvailable = hasFeature('has_contours_test_10ft')
-
- if (saved) {
- setHillshade(hsAvailable && (saved.hillshade ?? true))
- setTraffic(trAvailable && (saved.traffic ?? false))
- setPublicLands(plAvailable && (saved.publicLands ?? false))
- setContours(ctAvailable && (saved.contours ?? false))
- setContoursTest(ctTestAvailable && (saved.contoursTest ?? false))
- setContoursTest10ft(ctTest10ftAvailable && (saved.contoursTest10ft ?? false))
- } else {
- // Defaults: hillshade ON if available, others OFF
- setHillshade(hsAvailable)
- setTraffic(false)
- setPublicLands(false)
- setContours(false)
- setContoursTest(false)
- setContoursTest10ft(false)
- }
- }, [])
-
- // Apply layers when prefs change
- useEffect(() => {
- const mapView = mapRef?.current
- if (!mapView) return
- const map = mapView.getMap?.()
- if (!map) return
-
- const apply = () => {
- if (hillshade && hasFeature('has_hillshade')) {
- mapView.addHillshadeLayer?.()
- } else {
- mapView.removeHillshadeLayer?.()
- }
- }
-
- if (map.isStyleLoaded()) {
- apply()
- } else {
- map.once('style.load', apply)
- }
- savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft })
- return () => map.off('style.load', apply)
- }, [hillshade, mapRef])
-
- useEffect(() => {
- const mapView = mapRef?.current
- if (!mapView) return
- const map = mapView.getMap?.()
- if (!map) return
-
- const apply = () => {
- if (traffic && hasFeature('has_traffic_overlay')) {
- mapView.addTrafficLayer?.()
- } else {
- mapView.removeTrafficLayer?.()
- }
- }
-
- if (map.isStyleLoaded()) {
- apply()
- } else {
- map.once('style.load', apply)
- }
- savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft })
- return () => map.off('style.load', apply)
- }, [traffic, mapRef])
-
- useEffect(() => {
- const mapView = mapRef?.current
- if (!mapView) return
- const map = mapView.getMap?.()
- if (!map) return
-
- const apply = () => {
- if (publicLands && hasFeature('has_public_lands_layer')) {
- mapView.addPublicLandsLayer?.()
- } else {
- mapView.removePublicLandsLayer?.()
- }
- }
-
- if (map.isStyleLoaded()) {
- apply()
- } else {
- map.once('style.load', apply)
- }
- savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft })
- return () => map.off('style.load', apply)
- }, [publicLands, mapRef])
-
- useEffect(() => {
- const mapView = mapRef?.current
- if (!mapView) return
- const map = mapView.getMap?.()
- if (!map) return
-
- const apply = () => {
- if (contours && hasFeature('has_contours')) {
- mapView.addContoursLayer?.()
- } else {
- mapView.removeContoursLayer?.()
- }
- }
-
- if (map.isStyleLoaded()) {
- apply()
- } else {
- map.once('style.load', apply)
- }
- savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft })
- return () => map.off('style.load', apply)
- }, [contours, mapRef])
-
- useEffect(() => {
- const mapView = mapRef?.current
- if (!mapView) return
- const map = mapView.getMap?.()
- if (!map) return
-
- const apply = () => {
- if (contoursTest && hasFeature('has_contours_test')) {
- mapView.addContoursTestLayer?.()
- } else {
- mapView.removeContoursTestLayer?.()
- }
- }
-
- if (map.isStyleLoaded()) {
- apply()
- } else {
- map.once('style.load', apply)
- }
- savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft })
- return () => map.off('style.load', apply)
- }, [contoursTest, mapRef])
-
- // Apply contoursTest10ft layer
- useEffect(() => {
- const map = mapRef.current?.getMap?.()
- if (!map) return
-
- const apply = () => {
- if (contoursTest10ft && hasFeature('has_contours_test_10ft')) {
- mapRef.current?.addContoursTest10ftLayer?.()
- } else {
- mapRef.current?.removeContoursTest10ftLayer?.()
- }
- }
-
- if (map.isStyleLoaded()) {
- apply()
- } else {
- map.once('style.load', apply)
- }
- }, [contoursTest10ft, mapRef])
-
- // Close on outside click
- useEffect(() => {
- if (!open) return
- function handleClick(e) {
- if (panelRef.current && !panelRef.current.contains(e.target)) {
- setOpen(false)
- }
- }
- document.addEventListener('pointerdown', handleClick)
- return () => document.removeEventListener('pointerdown', handleClick)
- }, [open])
-
- const showHillshade = hasFeature('has_hillshade')
- const showTraffic = hasFeature('has_traffic_overlay')
- const showPublicLands = hasFeature('has_public_lands_layer')
- const showContours = hasFeature('has_contours')
- const showContoursTest = hasFeature('has_contours_test')
- const showContoursTest10ft = hasFeature('has_contours_test_10ft')
-
- // Don't render if no overlay features available
- if (!showHillshade && !showTraffic && !showPublicLands && !showContours && !showContoursTest && !showContoursTest10ft) return null
-
- return (
-
-
-
- {open && (
-
- )}
-
- )
-}
+import { useState, useEffect, useRef } from 'react'
+import { Layers, Trees, Mountain } from 'lucide-react'
+import { hasFeature, getConfig } from '../config'
+
+const STORAGE_KEY = 'navi-layer-prefs'
+
+function loadPrefs() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY)
+ if (raw) return JSON.parse(raw)
+ } catch {}
+ return null
+}
+
+function savePrefs(prefs) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs))
+}
+
+export default function LayerControl({ mapRef }) {
+ const [open, setOpen] = useState(false)
+ const [hillshade, setHillshade] = useState(false)
+ const [traffic, setTraffic] = useState(false)
+ const [publicLands, setPublicLands] = useState(false)
+ const [contours, setContours] = useState(false)
+ const [contoursTest, setContoursTest] = useState(false)
+ const [contoursTest10ft, setContoursTest10ft] = useState(false)
+ const [usfsTrails, setUsfsTrails] = useState(false)
+ const panelRef = useRef(null)
+
+ // Initialize from localStorage or defaults on mount
+ useEffect(() => {
+ const saved = loadPrefs()
+ const hsAvailable = hasFeature('has_hillshade')
+ const trAvailable = hasFeature('has_traffic_overlay')
+ const plAvailable = hasFeature('has_public_lands_layer')
+ const ctAvailable = hasFeature('has_contours')
+ const ctTestAvailable = hasFeature('has_contours_test')
+ const ctTest10ftAvailable = hasFeature('has_contours_test_10ft')
+ const usfsAvailable = hasFeature('has_usfs_trails')
+
+ if (saved) {
+ setHillshade(hsAvailable && (saved.hillshade ?? true))
+ setTraffic(trAvailable && (saved.traffic ?? false))
+ setPublicLands(plAvailable && (saved.publicLands ?? false))
+ setContours(ctAvailable && (saved.contours ?? false))
+ setContoursTest(ctTestAvailable && (saved.contoursTest ?? false))
+ setContoursTest10ft(ctTest10ftAvailable && (saved.contoursTest10ft ?? false))
+ setUsfsTrails(usfsAvailable && (saved.usfsTrails ?? false))
+ } else {
+ // Defaults: hillshade ON if available, others OFF
+ setHillshade(hsAvailable)
+ setTraffic(false)
+ setPublicLands(false)
+ setContours(false)
+ setContoursTest(false)
+ setContoursTest10ft(false)
+ setUsfsTrails(false)
+ }
+ }, [])
+
+ // Apply layers when prefs change
+ useEffect(() => {
+ const mapView = mapRef?.current
+ if (!mapView) return
+ const map = mapView.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (hillshade && hasFeature('has_hillshade')) {
+ mapView.addHillshadeLayer?.()
+ } else {
+ mapView.removeHillshadeLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails })
+ return () => map.off('style.load', apply)
+ }, [hillshade, mapRef])
+
+ useEffect(() => {
+ const mapView = mapRef?.current
+ if (!mapView) return
+ const map = mapView.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (traffic && hasFeature('has_traffic_overlay')) {
+ mapView.addTrafficLayer?.()
+ } else {
+ mapView.removeTrafficLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails })
+ return () => map.off('style.load', apply)
+ }, [traffic, mapRef])
+
+ useEffect(() => {
+ const mapView = mapRef?.current
+ if (!mapView) return
+ const map = mapView.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (publicLands && hasFeature('has_public_lands_layer')) {
+ mapView.addPublicLandsLayer?.()
+ } else {
+ mapView.removePublicLandsLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails })
+ return () => map.off('style.load', apply)
+ }, [publicLands, mapRef])
+
+ useEffect(() => {
+ const mapView = mapRef?.current
+ if (!mapView) return
+ const map = mapView.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (contours && hasFeature('has_contours')) {
+ mapView.addContoursLayer?.()
+ } else {
+ mapView.removeContoursLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails })
+ return () => map.off('style.load', apply)
+ }, [contours, mapRef])
+
+ useEffect(() => {
+ const mapView = mapRef?.current
+ if (!mapView) return
+ const map = mapView.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (contoursTest && hasFeature('has_contours_test')) {
+ mapView.addContoursTestLayer?.()
+ } else {
+ mapView.removeContoursTestLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails })
+ return () => map.off('style.load', apply)
+ }, [contoursTest, mapRef])
+
+ // Apply contoursTest10ft layer
+ useEffect(() => {
+ const map = mapRef.current?.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (contoursTest10ft && hasFeature('has_contours_test_10ft')) {
+ mapRef.current?.addContoursTest10ftLayer?.()
+ } else {
+ mapRef.current?.removeContoursTest10ftLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ }, [contoursTest10ft, mapRef])
+
+ // Apply usfsTrails layer
+ useEffect(() => {
+ const map = mapRef.current?.getMap?.()
+ if (!map) return
+
+ const apply = () => {
+ if (usfsTrails && hasFeature('has_usfs_trails')) {
+ mapRef.current?.addUsfsTrailsLayer?.()
+ } else {
+ mapRef.current?.removeUsfsTrailsLayer?.()
+ }
+ }
+
+ if (map.isStyleLoaded()) {
+ apply()
+ } else {
+ map.once('style.load', apply)
+ }
+ savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails })
+ }, [usfsTrails, mapRef])
+
+ // Close on outside click
+ useEffect(() => {
+ if (!open) return
+ function handleClick(e) {
+ if (panelRef.current && !panelRef.current.contains(e.target)) {
+ setOpen(false)
+ }
+ }
+ document.addEventListener('pointerdown', handleClick)
+ return () => document.removeEventListener('pointerdown', handleClick)
+ }, [open])
+
+ const showHillshade = hasFeature('has_hillshade')
+ const showTraffic = hasFeature('has_traffic_overlay')
+ const showPublicLands = hasFeature('has_public_lands_layer')
+ const showContours = hasFeature('has_contours')
+ const showContoursTest = hasFeature('has_contours_test')
+ const showContoursTest10ft = hasFeature('has_contours_test_10ft')
+ const showUsfsTrails = hasFeature('has_usfs_trails')
+
+ // Don't render if no overlay features available
+ if (!showHillshade && !showTraffic && !showPublicLands && !showContours && !showContoursTest && !showContoursTest10ft && !showUsfsTrails) return null
+
+ return (
+
+
+
+ {open && (
+
+ )}
+
+ )
+}
diff --git a/src/components/MapView.jsx b/src/components/MapView.jsx
index 367207f..477d8cc 100644
--- a/src/components/MapView.jsx
+++ b/src/components/MapView.jsx
@@ -42,6 +42,12 @@ const CONTOUR_TEST_10FT_LABEL = 'contour-test-10ft-label'
const MEASURE_SOURCE = 'measure-source'
const MEASURE_LINE_LAYER = 'measure-line-layer'
const MEASURE_POINT_LAYER = 'measure-point-layer'
+const USFS_SOURCE = 'usfs-trails-source'
+const USFS_ROADS_LAYER = 'usfs-roads-layer'
+const USFS_TRAILS_LAYER = 'usfs-trails-layer'
+const USFS_ROADS_LABEL = 'usfs-roads-label'
+const USFS_TRAILS_LABEL = 'usfs-trails-label'
+
// Highlight state - use data-driven expressions to target specific features
const INTERACTIVE_LABEL_LAYERS = ['pois', 'places_subplace', 'places_locality', 'places_region', 'places_country']
@@ -813,6 +819,119 @@ function removeContoursTest10ft(map) {
if (map.getLayer(CONTOUR_TEST_10FT_MINOR)) map.removeLayer(CONTOUR_TEST_10FT_MINOR)
if (map.getSource(CONTOUR_TEST_10FT_SOURCE)) map.removeSource(CONTOUR_TEST_10FT_SOURCE)
}
+/** Add USFS trails and roads vector tile overlay */
+function addUsfsTrails(map) {
+ if (!map || map.getSource(USFS_SOURCE)) return
+
+ map.addSource(USFS_SOURCE, {
+ type: "vector",
+ url: "pmtiles:///tiles/usfs-trails-roads.pmtiles",
+ })
+
+ // Insert below first symbol layer (above other overlays, below labels)
+ let beforeId = undefined
+ for (const layer of map.getStyle().layers) {
+ if (layer.type === "symbol") {
+ beforeId = layer.id
+ break
+ }
+ }
+
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark"
+ const opMod = isDark ? 0.8 : 1.0
+
+ // Roads layer - solid khaki/tan line
+ map.addLayer({
+ id: USFS_ROADS_LAYER,
+ type: "line",
+ source: USFS_SOURCE,
+ "source-layer": "roads",
+ minzoom: 10,
+ paint: {
+ "line-color": isDark ? "#9a8b70" : "#b8a87a",
+ "line-opacity": 0.65 * opMod,
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 1.0, 14, 2.0, 16, 3.0],
+ },
+ }, beforeId)
+
+ // Trails layer - dashed earth-tone brown
+ map.addLayer({
+ id: USFS_TRAILS_LAYER,
+ type: "line",
+ source: USFS_SOURCE,
+ "source-layer": "trails",
+ minzoom: 10,
+ paint: {
+ "line-color": isDark ? "#a88960" : "#8b7355",
+ "line-opacity": 0.7 * opMod,
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 1.5, 14, 2.5, 16, 3.5],
+ "line-dasharray": [2, 1.5],
+ },
+ }, beforeId)
+
+ // Road labels (zoom 12+)
+ map.addLayer({
+ id: USFS_ROADS_LABEL,
+ type: "symbol",
+ source: USFS_SOURCE,
+ "source-layer": "roads",
+ minzoom: 12,
+ filter: ["has", "NAME"],
+ layout: {
+ "text-field": ["get", "NAME"],
+ "text-size": 10,
+ "text-font": ["Noto Sans Regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "symbol-spacing": 300,
+ "text-max-angle": 25,
+ "text-allow-overlap": false,
+ },
+ paint: {
+ "text-color": isDark ? "#c0b090" : "#6a5a40",
+ "text-halo-color": isDark ? "#1a1a1a" : "#ffffff",
+ "text-halo-width": 1.5,
+ "text-opacity": 0.85,
+ },
+ })
+
+ // Trail labels (zoom 12+)
+ map.addLayer({
+ id: USFS_TRAILS_LABEL,
+ type: "symbol",
+ source: USFS_SOURCE,
+ "source-layer": "trails",
+ minzoom: 12,
+ filter: ["has", "TRAIL_NAME"],
+ layout: {
+ "text-field": ["get", "TRAIL_NAME"],
+ "text-size": 10,
+ "text-font": ["Noto Sans Regular"],
+ "symbol-placement": "line",
+ "text-anchor": "center",
+ "symbol-spacing": 300,
+ "text-max-angle": 25,
+ "text-allow-overlap": false,
+ },
+ paint: {
+ "text-color": isDark ? "#c8a878" : "#5a4530",
+ "text-halo-color": isDark ? "#1a1a1a" : "#ffffff",
+ "text-halo-width": 1.5,
+ "text-opacity": 0.85,
+ },
+ })
+}
+
+/** Remove USFS trails/roads layers and source */
+function removeUsfsTrails(map) {
+ if (!map) return
+ if (map.getLayer(USFS_TRAILS_LABEL)) map.removeLayer(USFS_TRAILS_LABEL)
+ if (map.getLayer(USFS_ROADS_LABEL)) map.removeLayer(USFS_ROADS_LABEL)
+ if (map.getLayer(USFS_TRAILS_LAYER)) map.removeLayer(USFS_TRAILS_LAYER)
+ if (map.getLayer(USFS_ROADS_LAYER)) map.removeLayer(USFS_ROADS_LAYER)
+ if (map.getSource(USFS_SOURCE)) map.removeSource(USFS_SOURCE)
+}
+
/** Add boundary polygon layers with computed accent color (MapLibre rejects CSS vars in paint) */
const BOUNDARY_FILL_LAYER = 'boundary-fill-layer'
@@ -871,7 +990,7 @@ const MapView = forwardRef(function MapView(_, ref) {
const watchIdRef = useRef(null)
const currentThemeRef = useRef('dark')
// Track which overlay layers are currently active (for theme swap re-add)
- const activeLayersRef = useRef({ hillshade: false, traffic: false, contours: false, contoursTest: false, contoursTest10ft: false })
+ const activeLayersRef = useRef({ hillshade: false, traffic: false, contours: false, contoursTest: false, contoursTest10ft: false, usfsTrails: false })
// Flag to suppress map-click when a stop pin was clicked
const pinClickedRef = useRef(false)
const highlightedFeatureRef = useRef(null) // { source, sourceLayer, id } for setFeatureState
@@ -1317,6 +1436,19 @@ const MapView = forwardRef(function MapView(_, ref) {
removeContoursTest10ft(map)
activeLayersRef.current.contoursTest10ft = false
},
+ addUsfsTrailsLayer() {
+ const map = mapInstance.current
+ if (!map) return
+ addUsfsTrails(map)
+ activeLayersRef.current.usfsTrails = true
+ },
+ removeUsfsTrailsLayer() {
+ const map = mapInstance.current
+ if (!map) return
+ removeUsfsTrails(map)
+ activeLayersRef.current.usfsTrails = false
+ },
+
}))
// Initialize map
@@ -1446,6 +1578,55 @@ const MapView = forwardRef(function MapView(_, ref) {
const { lng, lat } = e.lngLat
const MARKER_RADIUS_PX = 14 // half of 28px preview marker
+ // Check for USFS trails/roads click (show info popup)
+ const usfsLayers = [USFS_TRAILS_LAYER, USFS_ROADS_LAYER]
+ const usfsFeatures = map.queryRenderedFeatures(e.point, { layers: usfsLayers })
+ const usfsFeature = usfsFeatures.find(f => f.properties)
+ if (usfsFeature && hasFeature('has_usfs_trails')) {
+ const props = usfsFeature.properties
+ const isTrail = usfsFeature.layer?.id === USFS_TRAILS_LAYER
+ const name = isTrail ? (props.TRAIL_NAME || 'Unnamed Trail') : (props.NAME || 'Unnamed Road')
+ const typeLabel = isTrail ? 'USFS Trail' : 'USFS Road'
+
+ // Build popup content
+ let html = ''
+ html += '
' + name + ''
+ html += '
' + typeLabel + '
'
+
+ if (isTrail) {
+ // Trail-specific info
+ if (props.TRAIL_TYPE) html += '
Type: ' + props.TRAIL_TYPE + '
'
+ if (props.TRAIL_SURF) html += '
Surface: ' + props.TRAIL_SURF + '
'
+ if (props.GIS_MILES) html += '
Length: ' + parseFloat(props.GIS_MILES).toFixed(1) + ' mi
'
+ // Allowed uses
+ const uses = []
+ if (props.HIKER_PEDE === 'Y') uses.push('Hiking')
+ if (props.BICYCLE_MA === 'Y') uses.push('Biking')
+ if (props.MOTORCYCLE === 'Y') uses.push('Motorcycle')
+ if (props.ATV_MANAGE === 'Y') uses.push('ATV')
+ if (props.HORSE_MANA === 'Y') uses.push('Horse')
+ if (uses.length > 0) html += '
Allowed: ' + uses.join(', ') + '
'
+ } else {
+ // Road-specific info
+ if (props.OPER_MAINT) html += '
Maintenance: ' + props.OPER_MAINT + '
'
+ if (props.SURFACE_TY) html += '
Surface: ' + props.SURFACE_TY + '
'
+ if (props.ROUTE_STAT) html += '
Status: ' + props.ROUTE_STAT + '
'
+ }
+ html += '
'
+
+ // Remove existing popup
+ if (popupRef.current) popupRef.current.remove()
+
+ const popup = new maplibregl.Popup({ offset: 10, closeButton: true })
+ .setLngLat([lng, lat])
+ .setHTML(html)
+ .addTo(map)
+ popupRef.current = popup
+ return
+ }
+
+
+
// Query rendered features at click point (label/POI priority)
const labelLayers = ['pois', 'places_subplace', 'places_locality', 'places_region', 'places_country']
const features = map.queryRenderedFeatures(e.point, { layers: labelLayers })
diff --git a/src/config.js b/src/config.js
index 37ec84e..3bd129c 100644
--- a/src/config.js
+++ b/src/config.js
@@ -33,6 +33,7 @@ const FALLBACK_CONFIG = {
has_contours_test: true,
has_contours_test_10ft: false,
has_address_book_write: false,
+ has_usfs_trails: false,
has_contacts: false,
},
defaults: {