# Leaflet.js + OpenStreetMap Map Integration Replace SVG world maps with real interactive Leaflet.js maps using OpenStreetMap tiles. ## Setup ```html ``` ## Container div (replaces old SVG `viewBox`) ```html
``` ```css #leafletMap { width: 100%; height: 500px; border-radius: 12px; overflow: hidden; border: 1px solid rgba(14, 165, 233, 0.25); } ``` ## Dark theme overrides Apply these to match a dark mockup theme: ```css .leaflet-container { background: #0a1628; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .leaflet-popup-content-wrapper { background: #0f2b4a; color: #e2e8f0; border: 1px solid rgba(14, 165, 233, 0.25); border-radius: 12px; padding: 0; box-shadow: 0 4px 20px rgba(0,0,0,0.5); } .leaflet-popup-tip { background: #0f2b4a; border: 1px solid rgba(14, 165, 233, 0.25); } .leaflet-popup-content { margin: 12px 16px; font-size: 13px; line-height: 1.5; min-width: 200px; } .leaflet-popup-close-button { color: #94a3b8 !important; } .leaflet-popup-close-button:hover { color: #e2e8f0 !important; } .leaflet-control-zoom a { background: #0f2b4a !important; color: #e2e8f0 !important; border-color: rgba(14, 165, 233, 0.25) !important; } .leaflet-control-zoom a:hover { background: #1a3a5c !important; } .leaflet-control-attribution { background: rgba(10, 22, 40, 0.8) !important; color: #64748b !important; font-size: 10px !important; } .leaflet-control-attribution a { color: #0ea5e9 !important; } ``` ## Region data (real coordinates) Old SVG pattern used pixel coords + display strings. New pattern uses real lat/lng: ```js const MAP_REGIONS = [ { id: 1, name: "Florida East Coast", emoji: "🏖️", lat: 29.0, lng: -80.5 }, // ... ]; ``` ## Map initialization + markers ```js function renderMapView() { // Destroy previous instance (important for view-switching UIs) if (window._leafletMap) { window._leafletMap.remove(); window._leafletMap = null; } const map = L.map('leafletMap', { center: [20, 0], zoom: 2, maxZoom: 5, minZoom: 2, zoomControl: true, }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxNativeZoom: 5, }).addTo(map); window._leafletMap = map; MAP_REGIONS.forEach(mr => { // Color by state (available / yours / taken / waiting) const marker = L.circleMarker([mr.lat, mr.lng], { radius: 10, color: '#0ea5e9', fillColor: '#0ea5e9', fillOpacity: 0.5, opacity: 0.8, weight: 2, }).addTo(map); marker.bindPopup(`
Content
`, { closeButton: true, className: 'dark-popup', maxWidth: 280, }); }); // Fix layout after container becomes visible setTimeout(() => map.invalidateSize(), 100); } ``` ### State-based marker styling | State | Color | Radius | Opacity | |---|---|---|---| | Available | `#0ea5e9` (teal) | 10 | 0.8 | | Your pick | `#f59e0b` (amber) | 12 | 0.9 | | Taken/drafted | `#dc2626` (red) | 8 | 0.5 | | Waiting/draft-not-started | `#64748b` (gray) | 8 | 0.4 | ## View-switching lifecycle When toggling between list and map views, **destroy the Leaflet instance** on leaving the map view: ```js function switchView(view) { if (view === 'list') { if (window._leafletMap) { window._leafletMap.remove(); window._leafletMap = null; } } else { renderMapView(); } } ``` Without this, Leaflet doesn't re-render correctly when its container is shown again — `invalidateSize()` alone is unreliable after display toggles. ## Popup buttons with inline onclick Buttons inside Leaflet popups can call global functions: ```js marker.bindPopup(`
${mr.emoji} ${escapeHtml(mr.name)}
👁️${sightings} Sightings
`, { ... }); ``` ## Programmatic popup open/close ```js function openMapPopup(regionId) { const mr = MAP_REGIONS.find(r => r.id === regionId); if (mr && window._leafletMap) { window._leafletMap.eachLayer(layer => { if (layer instanceof L.CircleMarker && layer.getLatLng().lat === mr.lat && layer.getLatLng().lng === mr.lng) { layer.openPopup(); } }); } } function closeMapPopup() { if (window._leafletMap) { window._leafletMap.closePopup(); } } ``` ## Removing custom popup overlays When switching to Leaflet popups, remove the old custom overlay HTML and CSS: - **HTML to remove:** custom overlay divs (`#mapPopupOverlay`, `#mapPopup`, `#mapPopupContent`) - **CSS to remove:** `.map-popup-overlay`, `.map-popup`, `.map-popup-handle`, `.map-popup-header`, `.map-popup-flag`, `.map-popup-title`, `.map-popup-stats`, `.map-popup-stat`, `.map-popup-footer` - **CSS to keep:** `.map-popup-btn` and its variants — these still style buttons inside Leaflet popups - **JS to replace:** `openMapPopup()` / `closeMapPopup()` — redirect to Leaflet API as shown above