Initial skills documentation — 25 categories, all SKILL.md + references + scripts
This commit is contained in:
+209
@@ -0,0 +1,209 @@
|
||||
# Leaflet.js + OpenStreetMap Map Integration
|
||||
|
||||
Replace SVG world maps with real interactive Leaflet.js maps using OpenStreetMap tiles.
|
||||
|
||||
## Setup
|
||||
|
||||
```html
|
||||
<!-- In <head> -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
```
|
||||
|
||||
## Container div (replaces old SVG `viewBox`)
|
||||
|
||||
```html
|
||||
<div id="leafletMap"></div>
|
||||
```
|
||||
|
||||
```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: '© <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> 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(`<div class="lp-region">Content</div>`, {
|
||||
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(`
|
||||
<div class="lp-region">${mr.emoji} ${escapeHtml(mr.name)}</div>
|
||||
<div class="lp-stats">👁️${sightings} Sightings</div>
|
||||
<button class="map-popup-btn draft" onclick="makePickFromMap(${mr.id})">🦈 Draft</button>
|
||||
`, { ... });
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user