diff --git a/= b/=
new file mode 100644
index 0000000..e69de29
diff --git a/b.bottom b/b.bottom
new file mode 100644
index 0000000..e69de29
diff --git a/b.right b/b.right
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js
index 026d484..e8c8506 100644
--- a/src/components/Map/markerManager.js
+++ b/src/components/Map/markerManager.js
@@ -33,7 +33,8 @@ function rectsOverlap(a, b) {
/**
* Hide only markers that overlap the expanded card.
- * - expandedEl: the marker root that is expanded
+ * IMPORTANT: we hide ONLY markers that are BELOW the expanded card
+ * (so markers above stay visible).
*/
export function applyOcclusionForExpanded(markersRef, expandedEl) {
if (!expandedEl) return;
@@ -41,9 +42,9 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) {
const expandedCard = expandedEl.querySelector(".post-card");
if (!expandedCard) return;
- const PAD_X = 26; // left/right
- const PAD_TOP = 20; // top
- const PAD_BOTTOM = 95; // more bottom padding (image + actions + comments)
+ const PAD_X = 26; // left/right padding
+ const PAD_TOP = 20; // top padding
+ const PAD_BOTTOM = 140; // EXTRA bottom padding (image + buttons + comments)
const raw = expandedCard.getBoundingClientRect();
const expandedRect = {
@@ -52,17 +53,24 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) {
top: raw.top - PAD_TOP,
bottom: raw.bottom + PAD_BOTTOM,
};
+
const list = markersRef.current || [];
for (const item of list) {
const el = item?.el;
if (!el || el === expandedEl) continue;
- // Compute marker rect (bubble + pointer)
const r = el.getBoundingClientRect();
- const hide = rectsOverlap(expandedRect, r);
+ // ✅ only hide stuff that is BELOW the expanded card zone
+ const isBelow = r.top >= raw.top;
+ if (!isBelow) {
+ el.style.visibility = "visible";
+ el.style.pointerEvents = "auto";
+ continue;
+ }
+ const hide = rectsOverlap(expandedRect, r);
el.style.visibility = hide ? "hidden" : "visible";
el.style.pointerEvents = hide ? "none" : "auto";
}
@@ -82,20 +90,15 @@ export function clearOcclusion(markersRef) {
}
/**
- * Crée un marqueur pour un post :
- * - état compact : petite bulle + pointe
- * - état étendu : gros post (carte)
- *
- * Règles:
- * - 1 seul post ouvert à la fois
- * - clic sur la map => ferme (useMapCore)
- * - quand un post est ouvert => on cache SEULEMENT les markers qui le recouvrent
+ * Crée un marker stable:
+ * - root 0x0 anchored by MapLibre (anchor bottom)
+ * - UI is ABSOLUTE above the anchor, so expanded doesn't "shift" other markers
*/
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
const map = mapRef.current;
if (!map) return;
- // --- coordonnées ---
+ // --- coords ---
const lat =
typeof post.lat === "number"
? post.lat
@@ -145,7 +148,6 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
`;
- // bring back hidden markers
clearOcclusion(markersRef);
}
@@ -157,7 +159,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
`;
- // Wait a frame so DOM has final size, then hide only overlapping markers
requestAnimationFrame(() => {
applyOcclusionForExpanded(markersRef, root);
});
}
- // état initial : compact
+ // initial compact
renderCompact();
- // utilisé par useMapCore pour fermer via clic map
+ // used by map click to close
root.__renderCompact = renderCompact;
const marker = new maplibregl.Marker({
@@ -222,7 +215,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
root.addEventListener("click", (ev) => {
ev.stopPropagation();
- // Fermer l'ancien gros post
+ // close previous expanded
if (expandedElRef.current && expandedElRef.current !== root) {
if (expandedElRef.current.__renderCompact) {
expandedElRef.current.__renderCompact();
@@ -230,7 +223,6 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
expandedElRef.current = null;
}
- // Toggle
const isExpanded = root.classList.contains("post-pin--expanded");
if (isExpanded) {
renderCompact();
@@ -242,11 +234,11 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
});
}
-/* petite fonction pour éviter d’injecter du HTML sale */
+/* escape html */
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
-}
+}
\ No newline at end of file
diff --git a/src/components/Map/useMapCore.js b/src/components/Map/useMapCore.js
index f41cc28..166546c 100644
--- a/src/components/Map/useMapCore.js
+++ b/src/components/Map/useMapCore.js
@@ -4,7 +4,6 @@ import { LAST_VIEW_KEY } from "./mapConfig";
import { getViewFromMap } from "./mapGeo";
import { clearAllMarkers, applyOcclusionForExpanded } from "./markerManager";
-// renvoie le style MapLibre en fonction du thème
function getBaseStyle(theme) {
const t = theme || "dark";
let tiles;
@@ -32,13 +31,7 @@ function getBaseStyle(theme) {
attribution: "© OpenStreetMap contributors © CARTO",
},
},
- layers: [
- {
- id: "carto-base-layer",
- type: "raster",
- source: "carto-base",
- },
- ],
+ layers: [{ id: "carto-base-layer", type: "raster", source: "carto-base" }],
};
}
@@ -49,16 +42,13 @@ export function useMapCore(theme) {
const markersRef = useRef([]);
const expandedElRef = useRef(null);
- const [viewParams, setViewParams] = useState({
- center: null,
- radiusKm: 250,
- });
+ const [viewParams, setViewParams] = useState({ center: null, radiusKm: 250 });
const [hasLastView, setHasLastView] = useState(false);
function closeExpandedIfAny() {
const el = expandedElRef.current;
- if (el && el.__setExpanded) {
- el.__setExpanded(false);
+ if (el && el.__renderCompact) {
+ el.__renderCompact();
}
expandedElRef.current = null;
}
@@ -76,7 +66,7 @@ export function useMapCore(theme) {
antialias: true,
});
- // Reprise dernière vue
+ // restore last view
let hadLastView = false;
try {
const raw = localStorage.getItem(LAST_VIEW_KEY);
@@ -98,7 +88,7 @@ export function useMapCore(theme) {
map.addControl(new maplibregl.NavigationControl(), "top-right");
mapRef.current = map;
- // If a post is expanded, re-apply occlusion on zoom/move (only hides overlapping ones)
+ // re-apply occlusion during move/zoom (only hides below overlapping ones)
const reOcclude = () => {
const el = expandedElRef.current;
if (el) applyOcclusionForExpanded(markersRef, el);
@@ -106,18 +96,8 @@ export function useMapCore(theme) {
map.on("move", reOcclude);
map.on("zoom", reOcclude);
- // Close expanded post when clicking on the map (outside markers)
- map.on("click", () => {
- const el = expandedElRef.current;
- if (el && el.__renderCompact) {
- el.__renderCompact();
- expandedElRef.current = null;
- }
- });
-
map.on("load", () => {
- const vp = getViewFromMap(map);
- setViewParams(vp);
+ setViewParams(getViewFromMap(map));
});
map.on("moveend", () => {
@@ -127,16 +107,12 @@ export function useMapCore(theme) {
const center = vp.center;
localStorage.setItem(
LAST_VIEW_KEY,
- JSON.stringify({
- lat: center[1],
- lon: center[0],
- zoom: map.getZoom(),
- })
+ JSON.stringify({ lat: center[1], lon: center[0], zoom: map.getZoom() })
);
} catch {}
});
- // IMPORTANT: click/drag sur la map => fermer le gros post
+ // ✅ close expanded when clicking/dragging map
map.on("click", () => closeExpandedIfAny());
map.on("dragstart", () => closeExpandedIfAny());
@@ -148,6 +124,7 @@ export function useMapCore(theme) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ // theme switch
useEffect(() => {
const map = mapRef.current;
if (!map) return;
@@ -162,4 +139,4 @@ export function useMapCore(theme) {
viewParams,
hasLastView,
};
-}
+}
\ No newline at end of file
diff --git a/src/styles/mapMarkers.css b/src/styles/mapMarkers.css
index b5edd9d..8fc15c4 100644
--- a/src/styles/mapMarkers.css
+++ b/src/styles/mapMarkers.css
@@ -1,23 +1,31 @@
-/* MapLibre gère la position/transform via .maplibregl-marker, on ne touche PAS */
+/* ==========================================================
+ SOCIOWIRE MARKERS — ANCHOR STABLE (NO SHIFT)
+ Root = 0x0 on the exact geo point.
+ Bubble/Card are ABSOLUTE above the anchor.
+ ========================================================== */
+
+/* Root anchored by MapLibre at the geo point */
.post-pin {
- display: flex;
- flex-direction: column;
- align-items: center;
+ position: relative;
+ width: 0;
+ height: 0;
+ pointer-events: none; /* children clickable */
+ transform: translate3d(0, 0, 0);
+}
+
+.post-pin * {
pointer-events: auto;
-
- /* IMPORTANT: NE PAS mettre position ici, sinon on casse l'ancrage MapLibre */
- /* MapLibre applique position:absolute via .maplibregl-marker */
- z-index: 1;
+ box-sizing: border-box;
}
-/* quand expand => au-dessus de TOUT */
-.post-pin--expanded {
- z-index: 99999;
-}
-
-/* ===== ÉTAT COMPACT : petite bulle + petite pointe ===== */
-
+/* =========================
+ COMPACT
+ ========================= */
.post-pin--compact .post-pin-bubble {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transform: translate(-50%, calc(-100% - 12px));
display: inline-flex;
align-items: center;
padding: 4px 8px;
@@ -25,6 +33,7 @@
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 {
@@ -33,32 +42,45 @@
border-radius: 999px;
background: #ff8a00;
margin-right: 6px;
+ flex-shrink: 0;
}
.post-pin-text {
color: #e8f5ff;
font-size: 11px;
font-weight: 500;
- max-width: 120px;
+ max-width: 160px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
-/* petite pointe */
-.post-pin-pointer-small {
+/* little pointer tip */
+.post-pin--compact .post-pin-pointer-small {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transform: translate(-50%, -100%);
width: 0;
height: 0;
- margin-top: 2px;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 9px solid rgba(6, 40, 80, 0.95);
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6));
}
-/* ===== ÉTAT ÉTENDU : gros post (carte) ===== */
+/* =========================
+ EXPANDED
+ ========================= */
+.post-pin--expanded {
+ z-index: 999999;
+}
.post-pin--expanded .post-card {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transform: translate(-50%, calc(-100% - 14px));
min-width: 260px;
max-width: 320px;
background: rgba(8, 20, 40, 0.97);
@@ -67,7 +89,6 @@
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.8);
border: 1px solid rgba(90, 190, 255, 0.9);
color: #e8f5ff;
- box-sizing: border-box;
}
/* header : catégorie + heure/date */
@@ -96,7 +117,6 @@
margin-bottom: 6px;
}
-/* fake bloc image/vidéo */
.post-card-media {
width: 72px;
height: 72px;
@@ -105,6 +125,7 @@
display: flex;
align-items: center;
justify-content: center;
+ flex-shrink: 0;
}
.post-card-media-label {
@@ -113,9 +134,9 @@
color: #e8f5ff;
}
-/* texte principal */
.post-card-info {
flex: 1;
+ min-width: 0;
}
.post-card-title {
@@ -142,6 +163,7 @@
display: flex;
gap: 6px;
margin-bottom: 6px;
+ flex-wrap: wrap;
}
.post-card-btn {
@@ -154,7 +176,6 @@
cursor: pointer;
}
-/* zone commentaires */
.post-card-comments {
font-size: 10px;
color: #9eb7ff;
@@ -163,13 +184,16 @@
background: rgba(3, 14, 32, 0.95);
}
-/* Pointe sous le gros post */
-.post-card-pointer {
+/* pointer under expanded card */
+.post-pin--expanded .post-card-pointer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transform: translate(-50%, -100%);
width: 0;
height: 0;
- margin-top: 3px;
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));
-}
+}
\ No newline at end of file