- stops[] now contains ONLY intermediate waypoints
- routeStart and routeEnd are separate sources of truth
- addIntermediateStop() adds empty placeholder to stops[]
- updateStop() and removeStop() manage intermediate waypoints
- computeRoute() chains sequential 2-point routes for multi-stop
- DirectionsPanel renders: origin -> stops.map() -> destination
- Each intermediate stop has remove button (Trash2 icon)
Test scenarios verified:
- Origin + destination routes normally (no stops involved)
- Add Stop creates empty input between origin and destination
- Setting intermediate location triggers route recalculation
- Multiple stops can be added sequentially
- Removing a stop recalculates route without it
- Clear all returns to empty state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Radial menu "From here" now sets origin and opens directions panel
- Radial menu "To here" now sets destination, opens directions panel,
and uses GPS as origin fallback when available
- DirectionsPanel "Add stop" button now creates intermediate stops
- Stops array initialized from routeStart/routeEnd when adding stops
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Wilderness maneuvers render with compass arrows and cardinal directions
- Network maneuvers prefixed with transport mode (Drive/Walk/Ride)
- Distances under 1 mile show feet with commas
- Pick-from-map mode replaces auto-fill-on-focus (crosshair + toast)
- ESC cancels pick mode
- Place card slides out right during active routing
- Removed debug toasts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add route legend showing wilderness (dashed orange) vs road (solid blue)
- Show place card below directions panel when clicking map during routing
- Clean up error messages to be user-friendly (no offroute text)
- Legend only appears when route has wilderness segments
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When clicking on a labeled feature (e.g., "Monument Peak"), the code
was using the feature's canonical coordinates instead of the actual
click coordinates. This caused wilderness clicks to snap to named
places that might be on roads, bypassing wilderness routing.
Fix: Always use click coordinates (e.lngLat) for routing purposes.
Feature coordinates are only used for display/detail fetching.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New UX for Get Directions:
- DirectionsPanel component with two stacked input fields
- LocationInput component with autocomplete, coordinate parsing
- Swap button to flip origin/destination
- Travel mode selector (Drive default, Foot, MTB, ATV, 4x4)
- Boundary selector (only visible for non-Drive modes)
- Map click fills active input field with crosshair cursor
- Auto-route when both endpoints are filled
- X button closes directions and returns to search view
Store changes:
- directionsMode state for panel switching
- activeDirectionsField for map click targeting
- startDirections now enters directions mode with destination pre-filled
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Drive (auto) as default route mode, first in travel modes list
- Hide boundary mode selector when Drive mode is active
- Restore Add stop radial menu wedge with stops system integration
- Unify routing through single computeRoute() function in store
- Add coordinate parsing to SearchBar for direct lat/lon input
- Bridge stops system with routeStart/routeEnd for seamless UX
- Support 3+ stops with Valhalla optimization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove contours-test.pmtiles and contours-test-10ft.pmtiles references
(files deleted, feature flags disabled)
- Update fallback tileset URL from na.pmtiles to planet/current.pmtiles
- Remove has_contours_test and has_contours_test_10ft from fallback config
- Delete 46 .bak* files from src/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Switch to @acalcutt/maplibre-contour-pmtiles for PMTiles support
- Use absolute URL for DemSource so Web Worker can resolve path
- Extend contour thresholds from z3-z15 for full zoom coverage
- Improve line styling with zoom-dependent width
- Improve label styling with bold font and better halo
Co-Authored-By: Claude <noreply@anthropic.com>
The protomaps theme generates text-field expressions using name:short,
but the PMTiles data doesn't have that property. States have 'ref' (e.g.
'CA', 'ON') and 'name' (e.g. 'California', 'Ontario').
- Use coalesce expression: name:short -> ref -> name
- Expand zoom ranges slightly: country z1-5, region z4-8
- Verified fix in built JS before deployment
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
places_country: z1-z4 (countries visible at world view, fade at z4)
places_region: z4-z7 (states appear at z4, fade as cities dominate)
places_locality: unchanged (follows natural min_zoom in tiles)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adjust label zoom ranges after style load for proper hierarchy:
- Countries (places_country): visible from z2+
- States/provinces (places_region): visible from z3+
- Cities follow their natural min_zoom in the tile data
This ensures states like Idaho and Oregon appear before cities
like Boise and Portland when zoomed out. The setLayerZoomRange
calls are made in applyBaseLabelStyling() which runs after style
load and theme changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix D: State and province administrative boundaries are now visible
at low zoom levels (z4-z7) with theme-aware styling.
- Added STATE_BOUNDARIES_LAYER constant
- Added addStateBoundaries() function that creates a line layer
filtering on kind_detail = 4 (state/province level)
- Uses dashed line style with opacity interpolation
- Layer uses theme boundaries color for consistency
- Layer is re-added on theme change to update colors
The layer renders below labels and provides subtle but visible
state/province boundaries when zoomed out viewing country-level
maps. Line width and opacity increase as you zoom in from z4 to z7.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix A: Label click now also queries polygon layers
- When clicking park/forest/cemetery labels, also query landuse_park
fill layer to get polygon geometry
- If polygon found, use it as boundary directly from rendered tiles
- Eliminates need for API round-trip for park boundaries
Fix B: Boundary fitBounds behavior changed
- If boundary EXISTS: ALWAYS fitBounds (zoom in OR out) to show
the full boundary - the boundary defines what user should see
- If NO boundary: NEVER change zoom for map clicks/label clicks
- Search results: fly to center but preserve current zoom level
Removed previous z14 cap and zoom-in-only logic - boundaries now
always control the camera as expected.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace blanket z14 cap with zoom-in-only logic for boundary fitBounds:
- target.zoom > currentZoom → zoom IN to fit boundary (always allowed)
- target.zoom < currentZoom → skip fitBounds (never zoom out)
- target.zoom == currentZoom → allow pan to center
This means:
- Click Portland at z5 → fly in to ~z10 to show city boundary
- Click Idaho at z5 → fly in to ~z6 to show state boundary
- Click Portland at z15 → boundary draws, camera stays at z15
The z14 cap for selectedPlace flyTo (search results without boundary)
is preserved - that only affects the initial flyTo to coordinates.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Track place identity with lastFlyTargetRef to avoid re-flying on
metadata updates (boundary, wikidata, etc.)
- Only flyTo on NEW place selection, not subsequent store updates
- Apply z14 threshold to all camera movements:
- flyTo for search results: only if currentZoom < 14
- fitBounds for boundaries: only if currentZoom < 14
- At z14+ camera stays put, boundary draws silently
Fixes zoom-out bug where clicking a feature at high zoom would
zoom back out to z14.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix 1: Never zoom out when clicking a feature - preserves user's
intentional zoom level by checking cameraForBounds before fitBounds
Fix 2: Single-click to switch between features - clicking outside
the current feature's circle clears selection and selects new feature
Fix 3: View mode toggle reflects saved state on load - initialize
viewMode from localStorage on store creation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When clicking basemap labels, reverse geocode is now skipped to avoid
entity corruption. The wikidata effect needs to set osm_type/osm_id
from the osm_relation_id in the response to trigger Effect 3 which
fetches the wiki summary from /api/place/R/{id}.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MultiPolygon coordinates need .flat(2) not .flat(1) to get actual
coordinate pairs. With flat(1), we were iterating over rings instead
of coordinates, causing invalid lat values > 90.
Also added bounds validation before fitBounds to catch future issues.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When clicking a basemap label (city, town, POI), we already know the
entity from the label properties (name, kind, wikidata). Running
fetchReverse at those coordinates would return the nearest POI which
could be a different entity (e.g., clicking Portland returns Stumptown
Coffee), corrupting the place identity.
Now skips reverse geocode when source=basemap_label and raw.kind exists.
The wikidata lookup path still handles fetching boundaries and OSM data.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Three bugs fixed:
1. Map mode now restores all hidden layers - using separate tracking
arrays for fills, lines, and symbols that persist across mode switches
2. Satellite mode now hides ALL vector layers (fills, lines, symbols)
for true satellite-only view
3. Hybrid mode keeps lines and symbols visible for road/label overlay
Each mode switch first restores all layers to a clean slate before
hiding the appropriate ones for that mode.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The background layer type was not being hidden, causing it to cover
the satellite imagery. Now hideVectorFills hides fill, fill-extrusion,
AND background layer types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add viewMode state to store with localStorage persistence
- Add satellite layer functions to MapView (ESRI World Imagery via nginx proxy)
- Add view mode segmented control in LayerControl popover
- Add view-mode-control CSS styles
- Hide/show vector fills and lines based on view mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Control cluster now at bottom: 80px (was 40px)
- Scale bar at bottom: 24px (above attribution)
- Mobile adjusted to bottom: 70px
- Clear visual separation: buttons > scale > attribution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Increase touch targets from 36px to 44px (meets accessibility guidelines)
- Wrap Locate and Layers buttons in unified .map-controls-br container
- Layer popover now opens LEFT of buttons (avoids collision with Locate)
- Add hover and active states with theme-aware styling
- Proper spacing for scale control below the cluster
- Increased icon sizes from 18px to 20px
- Mobile-responsive with proper max-height on layer popover
Layout:
[Locate] 44x44
[Layers] 44x44
──────────────
Scale: 0.5 mi
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace palette icon with current theme name as clickable text.
Hover changes from --text-secondary to --text-primary.
Popover behavior unchanged.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
8 themes no longer fit in a single row. Changed from flex row
to a 4x2 grid layout so all themes are visible.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Old-world cartography style inspired by hand-drawn maps. Medieval
manuscript aesthetic with warm aged parchment land, deep ultramarine
water, dark sepia ink labels, and burnt sienna roads.
Design features:
- IM Fell English font (1672 Fell typeface revival)
- Deep ultramarine water (#1a3a6a), not modern pale blue
- Olive-gold vegetation, NOT modern green
- Brown ink roads by importance
- Parchment-colored halos on all labels
- Warm dramatic hillshade terrain
The feel of a map from a monastery scriptorium or explorer journal.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tactical night operations display optimized for absolute darkness.
Inspired by military cockpit instruments, submarine control rooms,
and ship bridge displays designed for total darkness.
Design rules:
- ONLY red and black (pure monochrome red)
- Red preserves dark-adapted (scotopic) vision
- Water is pure black, no blue tint
- All features differentiated by brightness only
- Black halos on all labels
- More aggressive than Tactical for ZERO ambient light
Palette: #0a0000 base, #cc3333 primary, #551515 muted
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Military display aesthetic - classic green phosphor monochrome like NVGs,
submarine sonar screens, 1980s radar consoles, and green-screen terminals.
Design rules:
- ONLY green and black (exception: danger status uses orange-red)
- All features differentiated by brightness, not hue
- Water is pure black, not blue-tinted
- Text is phosphor green (#00cc44)
- Black halos on all labels
Named "Tactical" for the recon/military working display identity.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dark olive/charcoal base with sage green text and amber accents.
Designed for night operations and eventual ATAK/iTAK integration:
- Low contrast for night vision compatibility
- Prominent olive-brown contours (topo-first design)
- Subdued amber roads (preserves night vision better than blue/white)
- Muted field-appropriate danger red
- No bright blues or whites anywhere
- Darkened/desaturated satellite mode
Functional, not decorative.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Root cause: Vite's bundling of namedTheme through the registry re-export
broke MapLibre's Web Worker, silently preventing all GeoJSON rendering
(routes, boundaries, measure tool). Fixed by importing namedTheme
directly from protomaps-themes-base in MapView.jsx.
Also guards queryRenderedFeatures calls for optional overlay layers
(USFS trails, BLM roads) that may not exist in the current style.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add highlight section to overlay config for all themes with
theme-appropriate colors:
- dark: muted olive-green (#7a9a6b)
- light: forest green (#4a7040)
- clean: Google blue (#1a73e8)
- cyberpunk: electric cyan (#00f0ff)
Update addBoundaryLayer() to read config from
getOverlayConfig(themeId, "highlight") for consistent styling
across all themes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When clicking basemap labels with wikidata IDs (cities, parks, etc),
fetchReverse was returning the nearest POI instead of the clicked
entity, blocking the wikidata fallback that returns correct boundaries.
Changes:
- Effect 1: Skip reverse geocode when wikidataId is present
- Effect 3: Always use wikidata path when available, regardless of
osmType/osmId presence
This fixes missing dashed outline on area feature clicks.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a dark, neon-soaked, dystopian command center theme:
Map colors:
- Near-black base (#0a0a14) with blue-purple undertones
- Magenta motorways (#ff2d6b), purple primary roads (#8833aa)
- Inky dark water (#06061a), barely-visible dark teal vegetation
- Cool white labels with dark halos for readability
- Neon POI icons (cyan, magenta, green)
UI styling:
- Dark translucent panels with magenta accent borders
- Custom fonts: Orbitron (headings), Share Tech Mono (body)
- Entire UI renders in monospace terminal aesthetic
- Magenta shadows, cyan route lines, neon status colors
- Pin markers in magenta (origin) and cyan (destination)
Overlay adjustments:
- Dramatic hillshade with darker shadows
- Very subtle contours (opacityMod: 0.5)
- USFS/BLM trails in purple/magenta/cyan family
- Public lands with muted teal fills
Satellite adjustments:
- Darkened (brightnessMax: 0.30), desaturated, purple-shifted
Also adds --font-heading CSS variable to all themes for future
custom heading font support.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PART 1: Add missing CSS variables to all ui objects
- Add --bg-inset, --bg-muted for component backgrounds
- Add --success, --warning as aliases for --status-success/warning
- Add --warning-muted for warning background states
- Each theme now has 32 CSS variables
PART 2: Per-theme font support
- Move --font-sans and --font-mono from :root to ui objects
- Add fontImports array to theme config (for future custom fonts)
- applyThemeUI() now manages <link> tags for font imports
- Existing themes use empty fontImports (system fonts already loaded)
PART 3: Swatch preview colors
- Add swatch array (3 hex colors) to each theme for visual preview
- light: warm tan, sage green, khaki
- dark: dark brown, sage green, tan
- clean: light gray, Google blue, Google green
- themeList() now returns swatch in result shape
PART 4: Theme picker UI
- New ThemePicker component replaces icon toggle in header
- Palette icon trigger opens popover below
- Shows all themes as circular swatches (conic gradient)
- Active theme has accent ring indicator
- Click swatch to apply theme, closes popover
- Click outside or Escape closes popover
- Styled with current theme CSS variables
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a plain, utilitarian theme focused on readability and wayfinding:
- White/light gray land (#f5f5f5), soft pastel green parks (#c3ecb2)
- Gentle blue water (#aadaff) with classic road hierarchy
- White minor roads, yellow primary (#fbc02d), orange motorway (#f9a825)
- Pure white UI panels with Google-standard gray text
- All 73 protomaps flavor keys + pois + landcover objects
- Full UI CSS custom properties using Google color palette
- Overlay config for hillshade, contours, public lands, trails
Update theme switcher to cycle through all available themes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>