show carpool areas on the map only while the accordion is open

This commit is contained in:
Arnaud Delcasse
2026-08-27 03:09:06 +02:00
parent e34b0d39c6
commit 671cfc4325
2 changed files with 116 additions and 50 deletions
+27 -4
View File
@@ -2429,31 +2429,54 @@ a {
} }
/* Map Markers */ /* Map Markers */
/* MapLibre writes `transform: translate(-50%,-50%) translate(Xpx,Ypx)` on
.map-marker to position it, and rewrites it on every map frame. Any
transform or transition declared here would fight that — a hover scale
would also scale the pixel offset, and a transition would make the marker
lag behind the map. So .map-marker only reserves space; everything visual
lives on .map-marker-inner, which MapLibre never touches. */
.map-marker { .map-marker {
width: 36px; width: 36px;
height: 36px; height: 36px;
cursor: pointer;
}
.map-marker-inner {
width: 100%;
height: 100%;
border-radius: 10px; border-radius: 10px;
border: 2px solid var(--color-white); border: 2px solid var(--color-white);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer;
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
.map-marker:hover { .map-marker-inner:hover {
transform: scale(1.1); transform: scale(1.1);
} }
.map-marker-departure { .map-marker-departure .map-marker-inner {
background-color: var(--color-secondary); background-color: var(--color-secondary);
} }
.map-marker-arrival { .map-marker-arrival .map-marker-inner {
background-color: var(--color-highlight); background-color: var(--color-highlight);
} }
/* Carpooling areas: smaller than trip endpoints, they are context not
destinations. */
.map-marker-area {
width: 24px;
height: 24px;
}
.map-marker-area .map-marker-inner {
background-color: var(--color-highlight);
border-width: 1px;
}
/* MapLibre Popup Styling */ /* MapLibre Popup Styling */
.maplibregl-popup-content { .maplibregl-popup-content {
padding: 12px 16px; padding: 12px 16px;
@@ -121,7 +121,8 @@
</div> </div>
<!-- Covoiturage --> <!-- Covoiturage -->
<div class="result-accordion" x-data="{ open: false }"> <div class="result-accordion" x-data="{ open: false }"
x-init="$watch('open', value => value ? showCarpoolAreaMarkers() : hideCarpoolAreaMarkers())">
<button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }"> <button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }">
<span class="accordion-title">Covoiturage</span> <span class="accordion-title">Covoiturage</span>
<span class="accordion-badge" x-text="results.carpools?.number || 0"></span> <span class="accordion-badge" x-text="results.carpools?.number || 0"></span>
@@ -733,47 +734,86 @@
}; };
} }
function addCarpoolAreasLayer(map, areas) { // Marker glyph per BNLC type — same set as the back-office compact view.
if (!areas.length || map.getSource('carpool-areas')) return; function carpoolAreaIcon(type) {
const glyph = (body, width) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="white" stroke-width="${width || 2}" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
map.addSource('carpool-areas', { switch (type) {
type: 'geojson', case 'Aire de covoiturage':
data: { return glyph('<circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/><path d="M5 17h-2v-6l2 -5h9l4 5h1a2 2 0 0 1 2 2v4h-2m-4 0h-6"/>');
type: 'FeatureCollection', case 'Parking':
features: areas.map(area => ({ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="13" height="13"><text x="12" y="18.5" text-anchor="middle" font-family="ui-sans-serif, system-ui, sans-serif" font-size="20" font-weight="700" fill="white">P</text></svg>`;
type: 'Feature', case 'Parking relais':
geometry: {type: 'Point', coordinates: area.coordinates}, return glyph('<circle cx="6" cy="17" r="2"/><circle cx="18" cy="17" r="2"/><path d="M4 17h-2v-11a1 1 0 0 1 1 -1h14a5 7 0 0 1 5 7v5h-2m-4 0h-8"/><path d="M2 10h15"/>');
properties: {name: area.name, city: area.city || '', type: area.type || ''} case "Sortie d'autoroute":
})) return glyph('<path d="M4 21v-8a4 4 0 0 1 4 -4h9"/><path d="M14 6l3 3l-3 3"/>');
case 'Supermarché':
return glyph('<circle cx="9" cy="19" r="1.5"/><circle cx="17" cy="19" r="1.5"/><path d="M3 4h2l2.4 10.5a2 2 0 0 0 2 1.5h7.6a2 2 0 0 0 2 -1.6l1 -5.4h-13"/>');
case 'Auto-stop':
return glyph('<circle cx="13" cy="4" r="1"/><path d="M7 21l3 -4"/><path d="M16 21l-2 -4l-3 -3l1 -6"/><path d="M6 12l2 -3l4 -1l3 3l3 1"/>');
case 'Délaissé routier':
return glyph('<path d="M12 3v4"/><path d="M12 10v4"/><path d="M12 17v4"/><path d="M5 3v18"/><path d="M19 3v18"/>');
default:
return glyph('<path d="M15 10.5a3 3 0 1 1 -6 0a3 3 0 0 1 6 0z"/><path d="M19.5 10.5c0 7.142 -7.5 11.25 -7.5 11.25s-7.5 -4.108 -7.5 -11.25a7.5 7.5 0 1 1 15 0z"/>', 1.8);
} }
});
map.addLayer({
id: 'carpool-areas-points',
type: 'circle',
source: 'carpool-areas',
paint: {
'circle-radius': 6,
'circle-color': getComputedStyle(document.documentElement)
.getPropertyValue('--color-highlight').trim() || '#F08F29',
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
} }
});
map.on('click', 'carpool-areas-points', (e) => { // The areas only show while the Covoiturage accordion is open. The flag
const props = e.features[0].properties; // outlives the map so the markers come back when the map is recreated on
const lines = [`<strong>${props.name}</strong>`]; // a desktop/mobile switch.
if (props.city) lines.push(props.city); let carpoolAreasVisible = false;
if (props.type) lines.push(props.type); let carpoolAreaMarkers = [];
new maplibregl.Popup()
.setLngLat(e.features[0].geometry.coordinates) function clearCarpoolAreaMarkers() {
.setHTML(lines.join('<br>')) carpoolAreaMarkers.forEach(marker => marker.remove());
carpoolAreaMarkers = [];
}
function renderCarpoolAreaMarkers() {
const map = window.parcoursmobMap;
clearCarpoolAreaMarkers();
if (!map || !carpoolAreasVisible) return;
const areas = carpoolAreasFromData();
if (!areas.length) return;
const data = window.__PARCOURSMOB_DATA__ || {};
const bounds = new maplibregl.LngLatBounds();
areas.forEach((area) => {
const el = document.createElement('div');
el.className = 'map-marker map-marker-area';
el.innerHTML = `<div class="map-marker-inner">${carpoolAreaIcon(area.type)}</div>`;
const lines = [`<strong>${area.name}</strong>`];
if (area.city) lines.push(area.city);
if (area.address) lines.push(area.address);
if (area.type) lines.push(area.type);
const marker = new maplibregl.Marker({ element: el })
.setLngLat(area.coordinates)
.setPopup(new maplibregl.Popup({ offset: 20 }).setHTML(lines.join('<br>')))
.addTo(map); .addTo(map);
carpoolAreaMarkers.push(marker);
bounds.extend(area.coordinates);
}); });
map.on('mouseenter', 'carpool-areas-points', () => { map.getCanvas().style.cursor = 'pointer'; }); // Keep the trip framed while bringing the areas into view, rather than
map.on('mouseleave', 'carpool-areas-points', () => { map.getCanvas().style.cursor = ''; }); // recentring on the areas alone.
if (data.departure) bounds.extend(data.departure.geometry.coordinates);
if (data.destination) bounds.extend(data.destination.geometry.coordinates);
map.fitBounds(bounds, { padding: 60, maxZoom: 14 });
}
function showCarpoolAreaMarkers() {
carpoolAreasVisible = true;
renderCarpoolAreaMarkers();
}
function hideCarpoolAreaMarkers() {
carpoolAreasVisible = false;
clearCarpoolAreaMarkers();
} }
function initMap() { function initMap() {
@@ -814,9 +854,11 @@
const departureEl = document.createElement('div'); const departureEl = document.createElement('div');
departureEl.className = 'map-marker map-marker-departure'; departureEl.className = 'map-marker map-marker-departure';
departureEl.innerHTML = ` departureEl.innerHTML = `
<div class="map-marker-inner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18" height="18"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18" height="18">
<path fill-rule="evenodd" d="M11.54 22.351l.07.04.028.016a.76.76 0 00.723 0l.028-.015.071-.041a16.975 16.975 0 001.144-.742 19.58 19.58 0 002.683-2.282c1.944-1.99 3.963-4.98 3.963-8.827a8.25 8.25 0 00-16.5 0c0 3.846 2.02 6.837 3.963 8.827a19.58 19.58 0 002.682 2.282 16.975 16.975 0 001.145.742zM12 13.5a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M11.54 22.351l.07.04.028.016a.76.76 0 00.723 0l.028-.015.071-.041a16.975 16.975 0 001.144-.742 19.58 19.58 0 002.683-2.282c1.944-1.99 3.963-4.98 3.963-8.827a8.25 8.25 0 00-16.5 0c0 3.846 2.02 6.837 3.963 8.827a19.58 19.58 0 002.682 2.282 16.975 16.975 0 001.145.742zM12 13.5a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg> </svg>
</div>
`; `;
new maplibregl.Marker({ element: departureEl }) new maplibregl.Marker({ element: departureEl })
.setLngLat(depCoords) .setLngLat(depCoords)
@@ -827,19 +869,20 @@
const arrivalEl = document.createElement('div'); const arrivalEl = document.createElement('div');
arrivalEl.className = 'map-marker map-marker-arrival'; arrivalEl.className = 'map-marker map-marker-arrival';
arrivalEl.innerHTML = ` arrivalEl.innerHTML = `
<div class="map-marker-inner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18" height="18"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18" height="18">
<path fill-rule="evenodd" d="M3 2.25a.75.75 0 01.75.75v.54l1.838-.46a9.75 9.75 0 016.725.738l.108.054a8.25 8.25 0 005.58.652l3.109-.732a.75.75 0 01.917.81 47.784 47.784 0 00.005 10.337.75.75 0 01-.574.812l-3.114.733a9.75 9.75 0 01-6.594-.77l-.108-.054a8.25 8.25 0 00-5.69-.625l-2.202.55V21a.75.75 0 01-1.5 0V3A.75.75 0 013 2.25z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M3 2.25a.75.75 0 01.75.75v.54l1.838-.46a9.75 9.75 0 016.725.738l.108.054a8.25 8.25 0 005.58.652l3.109-.732a.75.75 0 01.917.81 47.784 47.784 0 00.005 10.337.75.75 0 01-.574.812l-3.114.733a9.75 9.75 0 01-6.594-.77l-.108-.054a8.25 8.25 0 00-5.69-.625l-2.202.55V21a.75.75 0 01-1.5 0V3A.75.75 0 013 2.25z" clip-rule="evenodd" />
</svg> </svg>
</div>
`; `;
new maplibregl.Marker({ element: arrivalEl }) new maplibregl.Marker({ element: arrivalEl })
.setLngLat(destCoords) .setLngLat(destCoords)
.setPopup(new maplibregl.Popup().setHTML(`<strong>Arrivée</strong><br>${destination.properties.label}`)) .setPopup(new maplibregl.Popup().setHTML(`<strong>Arrivée</strong><br>${destination.properties.label}`))
.addTo(map); .addTo(map);
// Les aires de covoit : ajoutées à chaque création de carte, donc // window.parcoursmobMap is assigned by the caller, which has already
// aussi lors du basculement desktop/mobile qui recrée la carte. // returned by the time `load` fires.
const carpoolAreas = carpoolAreasFromData(); map.on('load', () => renderCarpoolAreaMarkers());
map.on('load', () => addCarpoolAreasLayer(map, carpoolAreas));
return map; return map;
} }