feat(project-overview): hover location card to highlight its map pin

Reverse direction of the existing pin→card flash on the project
overview map.  Hovering a location card now enlarges + reddens the
matching pin on the map and opens its tooltip.  Mouse-out reverts.

Why hover instead of click: clicking the card title navigates to the
location detail page, so any flash effect would never be visible.
Hover is the right interaction here.

Event delegation on document means cards that appear after htmx
swaps (e.g. after a reorder, remove/restore, or assign-modal close)
still get the behavior without rewiring.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 06:34:19 +00:00
parent 47c65268e3
commit 825c7370b8
@@ -161,7 +161,9 @@
maxZoom: 18,
}).addTo(map);
const markers = [];
// Marker store keyed by location id so card-click can find + flash
// the matching pin (bidirectional highlight).
const markersById = {};
const bounds = [];
withCoords.forEach(loc => {
const marker = L.circleMarker(loc.latlon, {
@@ -174,10 +176,53 @@
}).addTo(map);
marker.bindTooltip(loc.name, { direction: 'top', offset: [0, -6] });
marker.on('click', () => _flashLocationCard(loc.id));
markers.push(marker);
markersById[loc.id] = marker;
bounds.push(loc.latlon);
});
// Wire up the reverse direction: hovering a card highlights its
// pin on the map. Hover-based instead of click-based because
// clicking the card navigates to the location detail page, so the
// flash would never be visible. Event delegation so cards that
// appear later (htmx swap or reorder) still work.
let _hoverPinId = null;
document.addEventListener('mouseover', (e) => {
const card = e.target.closest('.location-card');
if (!card) return;
const locId = card.dataset.locationId;
if (locId === _hoverPinId) return;
if (_hoverPinId) _unhighlightPin(_hoverPinId);
_highlightPin(locId);
_hoverPinId = locId;
});
document.addEventListener('mouseout', (e) => {
const card = e.target.closest('.location-card');
if (!card) return;
// Only un-highlight if the mouse actually left the card,
// not just moved to a child element.
const related = e.relatedTarget && e.relatedTarget.closest('.location-card');
if (related === card) return;
if (_hoverPinId) {
_unhighlightPin(_hoverPinId);
_hoverPinId = null;
}
});
function _highlightPin(locId) {
const marker = markersById[locId];
if (!marker) return;
marker.setStyle({ radius: 12, fillColor: '#dc2626', weight: 3 });
marker.openTooltip();
marker.bringToFront();
}
function _unhighlightPin(locId) {
const marker = markersById[locId];
if (!marker) return;
marker.setStyle({ radius: 8, fillColor: '#f48b1c', weight: 2 });
marker.closeTooltip();
}
if (bounds.length === 1) {
map.setView(bounds[0], 14);
} else {