diff --git a/src/components/Map/mapMarkers.css b/src/components/Map/mapMarkers.css new file mode 100644 index 0000000..00d62b2 --- /dev/null +++ b/src/components/Map/mapMarkers.css @@ -0,0 +1,185 @@ +/* MapLibre: on stabilise l'ancrage en gardant l'élément root à 0x0 + et tout le UI en absolute au-dessus d'une pointe FIXE. */ + +/* Root du marker = ancre stable */ +.post-pin { + position: relative; + width: 0; + height: 0; + pointer-events: none; /* on active sur les enfants */ + transform: translate3d(0,0,0); +} + +/* Conteneurs cliquables */ +.post-pin * { + pointer-events: auto; + box-sizing: border-box; +} + +/* Pointe fixe (tip) — le bas du marker */ +.post-pin-tip { + position: absolute; + left: 0; + top: 0; + transform: translate(-50%, -100%); /* remonte le tip au-dessus du point d'ancre */ + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 11px solid rgba(8, 20, 40, 0.97); + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8)); +} + +/* Wrapper commun (compact/expanded) positionné AU-DESSUS du tip */ +.post-pin-layer { + position: absolute; + left: 0; + top: 0; + transform: translate(-50%, calc(-100% - 12px)); /* 12px = hauteur tip approx */ + display: flex; + flex-direction: column; + align-items: center; +} + +/* ========================= + COMPACT + ========================= */ +.post-pin-compact { + display: inline-flex; + align-items: center; + padding: 4px 8px; + border-radius: 999px; + background: rgba(6, 40, 80, 0.95); + border: 1px solid rgba(80, 180, 255, 0.9); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + max-width: 180px; +} + +.post-pin-dot { + width: 10px; + height: 10px; + border-radius: 999px; + background: #ff8a00; + margin-right: 6px; + flex-shrink: 0; +} + +.post-pin-text { + color: #e8f5ff; + font-size: 11px; + font-weight: 500; + max-width: 160px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ========================= + EXPANDED + ========================= */ +.post-pin-expanded { + min-width: 260px; + max-width: 320px; + background: rgba(8, 20, 40, 0.97); + border-radius: 14px; + padding: 10px 12px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.8); + border: 1px solid rgba(90, 190, 255, 0.9); + color: #e8f5ff; +} + +/* header : catégorie + heure/date */ +.post-card-header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 11px; + margin-bottom: 6px; +} + +.post-card-cat { + font-weight: 600; + color: #8fc5ff; +} + +.post-card-time { + color: #9eb7ff; +} + +/* corps : media + texte */ +.post-card-main { + display: flex; + flex-direction: row; + gap: 8px; + margin-bottom: 6px; +} + +.post-card-media { + width: 72px; + height: 72px; + border-radius: 10px; + background: radial-gradient(circle at 30% 30%, #4a9dff, #07152b); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.post-card-media-label { + font-size: 9px; + text-align: center; + color: #e8f5ff; +} + +.post-card-info { + flex: 1; + min-width: 0; +} + +.post-card-title { + font-size: 13px; + font-weight: 600; + margin-bottom: 2px; +} + +.post-card-meta { + font-size: 10px; + color: #9eb7ff; + margin-bottom: 4px; +} + +.post-card-body { + font-size: 11px; + color: #c7e3ff; + max-height: 70px; + overflow: hidden; +} + +/* footer boutons */ +.post-card-footer { + display: flex; + gap: 6px; + margin-bottom: 6px; + flex-wrap: wrap; +} + +.post-card-btn { + border: none; + border-radius: 999px; + padding: 3px 8px; + font-size: 10px; + background: rgba(5, 35, 70, 0.95); + color: #e8f5ff; + cursor: pointer; +} + +.post-card-comments { + font-size: 10px; + color: #9eb7ff; + padding: 4px 6px; + border-radius: 8px; + background: rgba(3, 14, 32, 0.95); +} + +/* Helpers */ +.post-hidden { display: none !important; } diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index f632aec..8cb2ef8 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -20,9 +20,8 @@ export function clearAllMarkers(markersRef, expandedElRef) { } /** - * Crée un marqueur pour un post : - * - état compact : petite bulle + pointe - * - état étendu : gros post (carte) comme sur ton dessin + * Marker stable: root 0x0 + UI en absolute au-dessus d'un tip fixe. + * => Le pin ne "jump" plus quand tu expand/collapse. */ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { const map = mapRef.current; @@ -64,103 +63,103 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { const category = (post.category || post.Category || "").toUpperCase() || ""; const subCat = (post.sub_category || post.SubCategory || "").toString(); + // Root ancré stable const root = document.createElement("div"); - root.className = "post-pin post-pin--compact"; + root.className = "post-pin"; - function renderCompact() { - root.className = "post-pin post-pin--compact"; - root.innerHTML = ` -
-
-
${escapeHtml(title)}
+ // Layer UI (au-dessus du tip) + const layer = document.createElement("div"); + layer.className = "post-pin-layer"; + + // Compact UI + const compact = document.createElement("div"); + compact.className = "post-pin-compact"; + compact.innerHTML = ` +
+
${escapeHtml(title)}
+ `; + + // Expanded UI + const expanded = document.createElement("div"); + expanded.className = "post-pin-expanded post-hidden"; + expanded.innerHTML = ` +
+
+ ${escapeHtml(category || "NEWS")} + ${subCat ? "· " + escapeHtml(subCat) : ""}
-
- `; - } +
${escapeHtml(created || "")}
+
- function renderExpanded() { - root.className = "post-pin post-pin--expanded"; - root.innerHTML = ` -
-
-
- ${escapeHtml(category || "NEWS")} - ${subCat ? "· " + escapeHtml(subCat) : ""} -
-
- ${escapeHtml(created || "")} -
-
- -
-
-
- Image / Vidéo -
-
- -
-
- ${escapeHtml(title)} -
-
- by ${escapeHtml(author)} · lat ${lat.toFixed(3)} · lon ${lon.toFixed(3)} -
-
- ${escapeHtml(body)} -
-
-
- -
- - - - -
- -
- Live chat / comments (placeholder) -
+
+
+
Image / Vidéo
-
- `; - } - // état initial : compact - renderCompact(); +
+
${escapeHtml(title)}
+
+ by ${escapeHtml(author)} · lat ${lat.toFixed(3)} · lon ${lon.toFixed(3)} +
+
${escapeHtml(body)}
+
+
+ +
+ + + + +
+ +
Live chat / comments (placeholder)
+ `; + + // Tip fixe (le point de la map) + const tip = document.createElement("div"); + tip.className = "post-pin-tip"; + + layer.appendChild(compact); + layer.appendChild(expanded); + root.appendChild(layer); + root.appendChild(tip); const marker = new maplibregl.Marker({ element: root, - anchor: "bottom", // la pointe touche exactement la coordonnée + anchor: "bottom", }) .setLngLat([lon, lat]) .addTo(map); markersRef.current.push({ id: post.id, marker, el: root, post }); + function setExpanded(isOn) { + if (isOn) { + compact.classList.add("post-hidden"); + expanded.classList.remove("post-hidden"); + expandedElRef.current = root; + } else { + expanded.classList.add("post-hidden"); + compact.classList.remove("post-hidden"); + if (expandedElRef.current === root) expandedElRef.current = null; + } + } + root.addEventListener("click", (ev) => { ev.stopPropagation(); - // Fermer l'ancien gros post + // Fermer l'ancien expanded if (expandedElRef.current && expandedElRef.current !== root) { - expandedElRef.current.__renderCompact && - expandedElRef.current.__renderCompact(); - expandedElRef.current = null; + const prev = expandedElRef.current; + if (prev && prev.__setExpanded) prev.__setExpanded(false); } - // Toggle pour ce post - const isExpanded = root.classList.contains("post-pin--expanded"); - if (isExpanded) { - renderCompact(); - expandedElRef.current = null; - } else { - renderExpanded(); - expandedElRef.current = root; - // on garde les fonctions pour pouvoir re-compactifier - root.__renderCompact = renderCompact; - } + const isExpanded = !expanded.classList.contains("post-hidden"); + setExpanded(!isExpanded); }); + + // expose pour pouvoir fermer depuis un autre marker + root.__setExpanded = setExpanded; } /* petite fonction pour éviter d’injecter du HTML sale */