pewsqie pardair
This commit is contained in:
parent
68c3648768
commit
87394a44eb
|
|
@ -7,69 +7,17 @@ import { cardTokens } from "../../theme/cardTokens";
|
||||||
import { getTemplateSpecForPost, adaptPostToTemplateData } from "./templateSpecs";
|
import { getTemplateSpecForPost, adaptPostToTemplateData } from "./templateSpecs";
|
||||||
|
|
||||||
export function clearAllMarkers(markersRef, expandedElRef) {
|
export function clearAllMarkers(markersRef, expandedElRef) {
|
||||||
// close overlay if any
|
|
||||||
try {
|
|
||||||
const el = expandedElRef?.current;
|
|
||||||
if (el && el.__swClose) el.__swClose();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (markersRef.current) {
|
if (markersRef.current) {
|
||||||
for (const m of markersRef.current) {
|
for (const m of markersRef.current) {
|
||||||
try {
|
try { if (m?.el && m.el.__swUnmount) m.el.__swUnmount(); } catch {}
|
||||||
if (m?.el && m.el.__swUnmount) m.el.__swUnmount();
|
try { m.marker.remove(); } catch {}
|
||||||
} catch {}
|
|
||||||
try {
|
|
||||||
m.marker.remove();
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
markersRef.current = [];
|
markersRef.current = [];
|
||||||
if (expandedElRef) expandedElRef.current = null;
|
if (expandedElRef) expandedElRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rectsOverlap(a, b) {
|
export function applyOcclusionForExpanded() {}
|
||||||
return !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyOcclusionForExpanded(markersRef, expandedEl) {
|
|
||||||
if (!expandedEl) return;
|
|
||||||
|
|
||||||
const expandedCard = expandedEl.querySelector(".post-card");
|
|
||||||
if (!expandedCard) return;
|
|
||||||
|
|
||||||
const PAD_X = 26;
|
|
||||||
const PAD_TOP = 20;
|
|
||||||
const PAD_BOTTOM = 140;
|
|
||||||
|
|
||||||
const raw = expandedCard.getBoundingClientRect();
|
|
||||||
const expandedRect = {
|
|
||||||
left: raw.left - PAD_X,
|
|
||||||
right: raw.right + PAD_X,
|
|
||||||
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) continue;
|
|
||||||
|
|
||||||
const r = el.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Only hide markers that are below/behind the expanded card
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearOcclusion(markersRef) {
|
export function clearOcclusion(markersRef) {
|
||||||
const list = markersRef.current || [];
|
const list = markersRef.current || [];
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
|
|
@ -91,9 +39,7 @@ function mountCard(container, spec, data, theme) {
|
||||||
themeTokens: tokens,
|
themeTokens: tokens,
|
||||||
onAction: (action, value) => {
|
onAction: (action, value) => {
|
||||||
if (action?.type === "open_url" && value) {
|
if (action?.type === "open_url" && value) {
|
||||||
try {
|
try { window.open(value, "_blank"); } catch {}
|
||||||
window.open(value, "_blank");
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: "",
|
className: "",
|
||||||
|
|
@ -101,9 +47,7 @@ function mountCard(container, spec, data, theme) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
try {
|
try { root.unmount(); } catch {}
|
||||||
root.unmount();
|
|
||||||
} catch {}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,48 +59,129 @@ function escapeHtml(str) {
|
||||||
.replace(/"/g, """);
|
.replace(/"/g, """);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function normCatLabel(post) {
|
||||||
* Overlay: fixed card at the bottom + pointer that follows the marker.
|
const c = (post?.category || "").toString().toUpperCase();
|
||||||
* We only pan the minimum if the marker ends up behind the card.
|
if (c === "NEWS") return "NEWS";
|
||||||
*/
|
if (c === "EVENT" || c === "EVENTS") return "EVENTS";
|
||||||
function getOrCreateExpandedOverlay(map) {
|
if (c === "FRIENDS") return "FRIENDS";
|
||||||
const container = map.getContainer();
|
if (c === "MARKET") return "MARKET";
|
||||||
if (!container) return null;
|
return "NEWS";
|
||||||
|
}
|
||||||
|
|
||||||
let root = container.querySelector(".sw-expanded-overlay-root");
|
function formatRelativeTime(createdAt) {
|
||||||
if (root) return root;
|
try {
|
||||||
|
if (!createdAt) return "now";
|
||||||
|
const d = createdAt instanceof Date ? createdAt : new Date(String(createdAt));
|
||||||
|
if (Number.isNaN(d.getTime())) return "now";
|
||||||
|
|
||||||
root = document.createElement("div");
|
const s = Math.floor((Date.now() - d.getTime()) / 1000);
|
||||||
root.className = "sw-expanded-overlay-root";
|
if (s < 60) return "now";
|
||||||
|
const m = Math.floor(s / 60);
|
||||||
|
if (m < 60) return `${m}m`;
|
||||||
|
const h = Math.floor(m / 60);
|
||||||
|
if (h < 48) return `${h}h`;
|
||||||
|
const days = Math.floor(h / 24);
|
||||||
|
return `${days}d`;
|
||||||
|
} catch {
|
||||||
|
return "now";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
root.innerHTML = `
|
function closeCenteredOverlay(map) {
|
||||||
<div class="sw-expanded-overlay-card">
|
try {
|
||||||
<div class="post-card sw-expanded-shell">
|
const ov = map?.__swCenteredOverlay;
|
||||||
<div class="sw-expanded-top">
|
if (!ov) return;
|
||||||
<div class="sw-expanded-left">
|
|
||||||
<div class="sw-template-full-wrap"></div>
|
if (ov.onKey) window.removeEventListener("keydown", ov.onKey);
|
||||||
</div>
|
try { if (ov.unmountFull) ov.unmountFull(); } catch {}
|
||||||
<div class="sw-expanded-right">
|
try { if (ov.unmountMini) ov.unmountMini(); } catch {}
|
||||||
<div class="sw-watch-title">Watching</div>
|
try { ov.backdrop?.remove?.(); } catch {}
|
||||||
<div class="sw-watch-list">
|
|
||||||
<div class="sw-watch-item">☑ Julia ★<div class="sw-watch-msg">— I see it…</div></div>
|
map.__swCenteredOverlay = null;
|
||||||
<div class="sw-watch-item">☑ Kim / CNN<div class="sw-watch-msg">— Blah blah blah</div></div>
|
} catch {}
|
||||||
<div class="sw-watch-item">☑ Yan ★<div class="sw-watch-msg">— Watch this!!</div></div>
|
}
|
||||||
</div>
|
|
||||||
<button class="sw-add-feed-btn" type="button">ADD TO FEED</button>
|
function openCenteredOverlay({ map, post, theme }) {
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
closeCenteredOverlay(map);
|
||||||
|
|
||||||
|
const data = adaptPostToTemplateData(post);
|
||||||
|
const cat = normCatLabel(post);
|
||||||
|
const relTime = formatRelativeTime(post?.created_at || post?.CreatedAt || post?.createdAt);
|
||||||
|
const author = (post?.author || post?.Author || "").toString().trim();
|
||||||
|
const sub = (post?.sub_category || post?.subCategory || "").toString().trim();
|
||||||
|
const metaLeft = author ? `by ${author}` : "";
|
||||||
|
const metaRight = sub ? sub : "";
|
||||||
|
const img = data?.image || "";
|
||||||
|
const url = data?.url || "";
|
||||||
|
|
||||||
|
// Backdrop catches outside click to close
|
||||||
|
const backdrop = document.createElement("div");
|
||||||
|
backdrop.style.position = "fixed";
|
||||||
|
backdrop.style.inset = "0";
|
||||||
|
backdrop.style.zIndex = "9999999";
|
||||||
|
backdrop.style.background = "rgba(0,0,0,0.08)";
|
||||||
|
backdrop.style.backdropFilter = "blur(1px)";
|
||||||
|
backdrop.style.display = "flex";
|
||||||
|
backdrop.style.alignItems = "center";
|
||||||
|
backdrop.style.justifyContent = "center";
|
||||||
|
backdrop.style.padding = "12px";
|
||||||
|
backdrop.style.pointerEvents = "auto";
|
||||||
|
|
||||||
|
// Wrapper uses existing styling hooks
|
||||||
|
const modalWrap = document.createElement("div");
|
||||||
|
modalWrap.className = "post-pin--expanded";
|
||||||
|
modalWrap.style.pointerEvents = "auto";
|
||||||
|
|
||||||
|
// Keep size behavior from your CSS (max-width/min(92vw,...), max-height etc.)
|
||||||
|
modalWrap.innerHTML = `
|
||||||
|
<div class="post-card sw-expanded-shell sw-modal">
|
||||||
|
<div class="sw-modal-toprow">
|
||||||
|
<div class="sw-modal-left">
|
||||||
|
<div class="sw-modal-badge">${escapeHtml(cat)}</div>
|
||||||
|
<div class="sw-modal-meta">
|
||||||
|
${metaLeft ? `<span>${escapeHtml(metaLeft)}</span>` : ""}
|
||||||
|
${metaRight ? `<span>• ${escapeHtml(metaRight)}</span>` : ""}
|
||||||
|
<span>• ${escapeHtml(relTime)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sw-news-generated">
|
<button class="sw-modal-x" type="button" aria-label="Close">✕</button>
|
||||||
<div class="sw-news-title"></div>
|
|
||||||
<div class="sw-news-sub">The news here (generated)</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="post-card-footer sw-actions-row">
|
<div class="sw-modal-hero">
|
||||||
<button class="post-card-btn">Contact</button>
|
${
|
||||||
<button class="post-card-btn">Like</button>
|
img
|
||||||
<button class="post-card-btn">Share</button>
|
? `<img src="${escapeHtml(img)}" alt="" loading="lazy" />`
|
||||||
<button class="post-card-btn">Fix</button>
|
: `<div class="sw-modal-hero-fallback"></div>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sw-modal-title">${escapeHtml(data?.headline || post?.title || "Untitled")}</div>
|
||||||
|
|
||||||
|
<div class="sw-modal-summary">
|
||||||
|
${escapeHtml(data?.summary || post?.snippet || "")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sw-stat-row">
|
||||||
|
<div class="sw-stat-pill">👁 12.4k</div>
|
||||||
|
<div class="sw-stat-pill">❤️ 1.3k</div>
|
||||||
|
<div class="sw-stat-pill">💬 248</div>
|
||||||
|
<div class="sw-stat-pill">📍 ${escapeHtml(post?.city || post?.location_name || "Nearby")}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sw-modal-cta-row">
|
||||||
|
<button class="sw-cta-primary" type="button" ${url ? "" : "disabled"} data-url="${escapeHtml(url)}">
|
||||||
|
Open source
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="sw-cta-actions">
|
||||||
|
<button class="post-card-btn" type="button">Contact</button>
|
||||||
|
<button class="post-card-btn" type="button">Like</button>
|
||||||
|
<button class="post-card-btn" type="button">Share</button>
|
||||||
|
<button class="post-card-btn" type="button">Fix</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="post-card-comments sw-livechat">
|
<div class="post-card-comments sw-livechat">
|
||||||
|
|
@ -172,107 +197,55 @@ function getOrCreateExpandedOverlay(map) {
|
||||||
<button class="sw-livechat-post" type="button">POST</button>
|
<button class="sw-livechat-post" type="button">POST</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sw-overlay-line"></div>
|
<!-- (Optional) keep template renderer mounted but hidden for future -->
|
||||||
<div class="sw-overlay-pointer"></div>
|
<div class="sw-template-full-wrap" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// helper close fn
|
backdrop.appendChild(modalWrap);
|
||||||
root.__swClose = () => {
|
document.body.appendChild(backdrop);
|
||||||
try {
|
|
||||||
if (root.__swUnmountFull) root.__swUnmountFull();
|
// outside click closes
|
||||||
} catch {}
|
backdrop.addEventListener("click", () => closeCenteredOverlay(map));
|
||||||
try {
|
// inside click doesn't close
|
||||||
if (root.__swMoveHandler && root.__swMap) {
|
modalWrap.addEventListener("click", (e) => e.stopPropagation());
|
||||||
root.__swMap.off("move", root.__swMoveHandler);
|
|
||||||
root.__swMap.off("zoom", root.__swMoveHandler);
|
// close button
|
||||||
}
|
const xbtn = modalWrap.querySelector(".sw-modal-x");
|
||||||
} catch {}
|
if (xbtn) xbtn.addEventListener("click", () => closeCenteredOverlay(map));
|
||||||
try {
|
|
||||||
root.remove();
|
// ESC closes
|
||||||
} catch {}
|
const onKey = (e) => {
|
||||||
|
if (e.key === "Escape") closeCenteredOverlay(map);
|
||||||
};
|
};
|
||||||
|
window.addEventListener("keydown", onKey);
|
||||||
|
|
||||||
container.appendChild(root);
|
// CTA open url
|
||||||
return root;
|
const cta = modalWrap.querySelector(".sw-cta-primary");
|
||||||
}
|
if (cta) {
|
||||||
|
cta.addEventListener("click", () => {
|
||||||
|
const u = cta.getAttribute("data-url") || "";
|
||||||
|
if (!u) return;
|
||||||
|
try { window.open(u, "_blank", "noopener,noreferrer"); } catch {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function updateOverlayPointer(map, overlayRoot, lngLat) {
|
// still mount template full (hidden) so you can switch back later instantly
|
||||||
if (!map || !overlayRoot || !lngLat) return;
|
let unmountFull = null;
|
||||||
|
const fullWrap = modalWrap.querySelector(".sw-template-full-wrap");
|
||||||
|
if (fullWrap) {
|
||||||
|
const spec = getTemplateSpecForPost(post, "full");
|
||||||
|
unmountFull = mountCard(fullWrap, spec, data, theme);
|
||||||
|
}
|
||||||
|
|
||||||
const p = map.project(lngLat);
|
map.__swCenteredOverlay = {
|
||||||
const pointer = overlayRoot.querySelector(".sw-overlay-pointer");
|
postId: post?.id,
|
||||||
const line = overlayRoot.querySelector(".sw-overlay-line");
|
backdrop,
|
||||||
const cardWrap = overlayRoot.querySelector(".sw-expanded-overlay-card");
|
onKey,
|
||||||
const card = overlayRoot.querySelector(".sw-expanded-overlay-card .post-card");
|
unmountFull,
|
||||||
|
unmountMini: null,
|
||||||
if (!pointer || !line || !cardWrap || !card) return;
|
};
|
||||||
|
|
||||||
// pointer follows marker
|
|
||||||
pointer.style.left = `${p.x}px`;
|
|
||||||
pointer.style.top = `${p.y}px`;
|
|
||||||
|
|
||||||
// simple line from marker to bottom center of the card
|
|
||||||
const contRect = map.getContainer().getBoundingClientRect();
|
|
||||||
const cardRect = cardWrap.getBoundingClientRect();
|
|
||||||
|
|
||||||
const x1 = p.x;
|
|
||||||
const y1 = p.y;
|
|
||||||
|
|
||||||
const x2 = (cardRect.left - contRect.left) + cardRect.width / 2;
|
|
||||||
const y2 = (cardRect.top - contRect.top) + cardRect.height; // bottom of card
|
|
||||||
|
|
||||||
const dx = x2 - x1;
|
|
||||||
const dy = y2 - y1;
|
|
||||||
const len = Math.sqrt(dx * dx + dy * dy);
|
|
||||||
|
|
||||||
// place a thin line
|
|
||||||
line.style.left = `${x1}px`;
|
|
||||||
line.style.top = `${y1}px`;
|
|
||||||
line.style.width = `${len}px`;
|
|
||||||
line.style.transform = `rotate(${Math.atan2(dy, dx)}rad)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function minimalPanToKeepMarkerVisible(map, overlayRoot, lngLat) {
|
|
||||||
if (!map || !overlayRoot || !lngLat) return;
|
|
||||||
|
|
||||||
const p = map.project(lngLat);
|
|
||||||
const cont = map.getContainer();
|
|
||||||
if (!cont) return;
|
|
||||||
|
|
||||||
const contRect = cont.getBoundingClientRect();
|
|
||||||
const cardWrap = overlayRoot.querySelector(".sw-expanded-overlay-card");
|
|
||||||
if (!cardWrap) return;
|
|
||||||
|
|
||||||
const cardRect = cardWrap.getBoundingClientRect();
|
|
||||||
|
|
||||||
const topPad = 18;
|
|
||||||
const bottomSafe = (cardRect.top - contRect.top) - 18;
|
|
||||||
|
|
||||||
let dy = 0;
|
|
||||||
|
|
||||||
// marker too high -> bring it down a bit
|
|
||||||
if (p.y < topPad) dy = (topPad - p.y);
|
|
||||||
|
|
||||||
// marker behind the card -> bring it up a bit
|
|
||||||
if (p.y > bottomSafe) dy = (bottomSafe - p.y);
|
|
||||||
|
|
||||||
// clamp so it never "vent dalle trop loin"
|
|
||||||
dy = Math.max(-160, Math.min(160, dy));
|
|
||||||
|
|
||||||
if (Math.abs(dy) < 2) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const until = Date.now() + 2000;
|
|
||||||
map.__swIgnoreMapClickUntil = until;
|
|
||||||
map.__swIgnoreCloseUntil = until;
|
|
||||||
map.__swIgnoreFetchUntil = until;
|
|
||||||
map.__swIgnoreRebuildUntil = until;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
map.panBy([0, dy], { duration: 350 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
|
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
|
||||||
|
|
@ -293,10 +266,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
? post.lng
|
? post.lng
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (lat == null || lon == null) {
|
if (lat == null || lon == null) return;
|
||||||
console.warn("post sans coordonnée:", post);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lngLat = [lon, lat];
|
const lngLat = [lon, lat];
|
||||||
|
|
||||||
|
|
@ -306,9 +276,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
|
|
||||||
function unmountIfAny() {
|
function unmountIfAny() {
|
||||||
if (root.__swUnmount) {
|
if (root.__swUnmount) {
|
||||||
try {
|
try { root.__swUnmount(); } catch {}
|
||||||
root.__swUnmount();
|
|
||||||
} catch {}
|
|
||||||
root.__swUnmount = null;
|
root.__swUnmount = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -333,88 +301,6 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
clearOcclusion(markersRef);
|
clearOcclusion(markersRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeExpandedOverlayIfAny() {
|
|
||||||
const cur = expandedElRef.current;
|
|
||||||
if (cur && cur.__swClose) {
|
|
||||||
try {
|
|
||||||
cur.__swClose();
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
expandedElRef.current = null;
|
|
||||||
clearOcclusion(markersRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderExpandedOverlay() {
|
|
||||||
// locks so fetch/rebuild doesn't close while opening
|
|
||||||
try {
|
|
||||||
const until = Date.now() + 2000;
|
|
||||||
map.__swIgnoreMapClickUntil = until;
|
|
||||||
map.__swIgnoreCloseUntil = until;
|
|
||||||
map.__swIgnoreFetchUntil = until;
|
|
||||||
map.__swIgnoreRebuildUntil = until;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
closeExpandedOverlayIfAny();
|
|
||||||
|
|
||||||
const overlayRoot = getOrCreateExpandedOverlay(map);
|
|
||||||
if (!overlayRoot) return;
|
|
||||||
|
|
||||||
// fill headline
|
|
||||||
const headline = escapeHtml(post?.title || "Untitled");
|
|
||||||
const titleEl = overlayRoot.querySelector(".sw-news-title");
|
|
||||||
if (titleEl) titleEl.innerHTML = headline;
|
|
||||||
|
|
||||||
// mount template full into the left area
|
|
||||||
try {
|
|
||||||
if (overlayRoot.__swUnmountFull) overlayRoot.__swUnmountFull();
|
|
||||||
} catch {}
|
|
||||||
overlayRoot.__swUnmountFull = null;
|
|
||||||
|
|
||||||
const wrap = overlayRoot.querySelector(".sw-template-full-wrap");
|
|
||||||
if (wrap) {
|
|
||||||
wrap.innerHTML = ""; // ensure clean
|
|
||||||
const spec = getTemplateSpecForPost(post, "full");
|
|
||||||
const data = adaptPostToTemplateData(post);
|
|
||||||
overlayRoot.__swUnmountFull = mountCard(wrap, spec, data, theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pointer updater
|
|
||||||
const moveHandler = () => {
|
|
||||||
try {
|
|
||||||
updateOverlayPointer(map, overlayRoot, lngLat);
|
|
||||||
applyOcclusionForExpanded(markersRef, overlayRoot);
|
|
||||||
} catch {}
|
|
||||||
};
|
|
||||||
|
|
||||||
overlayRoot.__swMap = map;
|
|
||||||
overlayRoot.__swMoveHandler = moveHandler;
|
|
||||||
|
|
||||||
map.on("move", moveHandler);
|
|
||||||
map.on("zoom", moveHandler);
|
|
||||||
|
|
||||||
// initial layout after paint
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
try {
|
|
||||||
// 1) update pointer/line
|
|
||||||
updateOverlayPointer(map, overlayRoot, lngLat);
|
|
||||||
|
|
||||||
// 2) minimal pan so marker isn't hidden behind the fixed card
|
|
||||||
minimalPanToKeepMarkerVisible(map, overlayRoot, lngLat);
|
|
||||||
|
|
||||||
// 3) occlusion now + after moveend
|
|
||||||
applyOcclusionForExpanded(markersRef, overlayRoot);
|
|
||||||
map.once("moveend", () => {
|
|
||||||
try {
|
|
||||||
updateOverlayPointer(map, overlayRoot, lngLat);
|
|
||||||
applyOcclusionForExpanded(markersRef, overlayRoot);
|
|
||||||
} catch {}
|
|
||||||
});
|
|
||||||
} catch {}
|
|
||||||
});
|
|
||||||
|
|
||||||
expandedElRef.current = overlayRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompact();
|
renderCompact();
|
||||||
root.__renderCompact = renderCompact;
|
root.__renderCompact = renderCompact;
|
||||||
|
|
||||||
|
|
@ -427,17 +313,13 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
root.addEventListener("click", (ev) => {
|
root.addEventListener("click", (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
// toggle: if overlay already open for this post, close it
|
const existing = map.__swCenteredOverlay;
|
||||||
const cur = expandedElRef.current;
|
if (existing && existing.postId === post?.id) {
|
||||||
if (cur && cur.__swForId && cur.__swForId === (post.id ?? `${lon},${lat}`)) {
|
closeCenteredOverlay(map);
|
||||||
closeExpandedOverlayIfAny();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// open overlay
|
openCenteredOverlay({ map, post, theme });
|
||||||
renderExpandedOverlay();
|
if (expandedElRef) expandedElRef.current = null;
|
||||||
if (expandedElRef.current) {
|
|
||||||
expandedElRef.current.__swForId = post.id ?? `${lon},${lat}`;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
export const TEMPLATE_SPECS = {
|
export const TEMPLATE_SPECS = {
|
||||||
news: {
|
news: {
|
||||||
mini_spec: {
|
mini_spec: {
|
||||||
size: { w: 240, h: 96 }, /* ✅ smaller to avoid layout push */
|
size: { w: 240, h: 96 },
|
||||||
background: { type: "gradient", value: "newsBlue" },
|
background: { type: "gradient", value: "newsBlue" },
|
||||||
radius: 18,
|
radius: 18,
|
||||||
layers: [
|
layers: [
|
||||||
|
|
@ -16,23 +16,16 @@ export const TEMPLATE_SPECS = {
|
||||||
{ id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }
|
{ id: "meta", type: "text", x: 12, y: 76, w: 216, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ✅ FULL: remove useless empty top (no hero image block), keep SAME overall size
|
||||||
full_spec: {
|
full_spec: {
|
||||||
size: { w: 360, h: 520 },
|
size: { w: 360, h: 260 },
|
||||||
background: { type: "solid", value: "surface" },
|
background: { type: "solid", value: "surface" },
|
||||||
radius: 22,
|
radius: 22,
|
||||||
layers: [
|
layers: [
|
||||||
{ id: "hero", type: "image", x: 0, y: 0, w: 360, h: 220, bind: "data.image" },
|
{ id: "badge", type: "chip", x: 16, y: 12, text: "NEWS", style: "chip.news" },
|
||||||
{ id: "badge", type: "chip", x: 16, y: 16, text: "NEWS", style: "chip.news" },
|
{ id: "title", type: "text", x: 16, y: 52, w: 328, h: 72, bind: "data.headline", style: "text.h1", maxLines: 3 },
|
||||||
{ id: "title", type: "text", x: 16, y: 240, w: 328, h: 78, bind: "data.headline", style: "text.h1", maxLines: 3 },
|
{ id: "summary", type: "text", x: 16, y: 132, w: 328, h: 78, bind: "data.summary", style: "text.body", maxLines: 4 }
|
||||||
{ id: "summary", type: "text", x: 16, y: 325, w: 328, h: 110, bind: "data.summary", style: "text.body", maxLines: 5 },
|
|
||||||
{
|
|
||||||
id: "cta",
|
|
||||||
type: "button",
|
|
||||||
x: 16, y: 450, w: 328, h: 48,
|
|
||||||
text: "Open source",
|
|
||||||
style: "button.primary",
|
|
||||||
action: { type: "open_url", bind: "data.url" }
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -105,21 +105,9 @@ export function useMapCore(theme) {
|
||||||
map.on("zoom", reOcclude);
|
map.on("zoom", reOcclude);
|
||||||
|
|
||||||
// ignore close while auto actions are happening
|
// ignore close while auto actions are happening
|
||||||
map.on("click", () => {
|
map.on("click", () => { closeExpandedIfAny(); });
|
||||||
try {
|
|
||||||
const until = map.__swIgnoreMapClickUntil || 0;
|
|
||||||
if (until && Date.now() < until) return;
|
|
||||||
} catch {}
|
|
||||||
closeExpandedIfAny();
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on("dragstart", () => {
|
map.on("dragstart", () => { closeExpandedIfAny(); });
|
||||||
try {
|
|
||||||
const until = map.__swIgnoreCloseUntil || 0;
|
|
||||||
if (until && Date.now() < until) return;
|
|
||||||
} catch {}
|
|
||||||
closeExpandedIfAny();
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on("load", () => setViewParams(getViewFromMap(map)));
|
map.on("load", () => setViewParams(getViewFromMap(map)));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,21 +22,13 @@ export function usePostsEngine({
|
||||||
const [loadingPosts, setLoadingPosts] = useState(false);
|
const [loadingPosts, setLoadingPosts] = useState(false);
|
||||||
const [loadError, setLoadError] = useState("");
|
const [loadError, setLoadError] = useState("");
|
||||||
|
|
||||||
const isExpandedOpen = () => {
|
|
||||||
try {
|
|
||||||
return !!expandedElRef?.current;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rebuildMarkersForFilters = useCallback(
|
const rebuildMarkersForFilters = useCallback(
|
||||||
(tf) => {
|
(tf) => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
// ✅ If expanded overlay/popup is open: NEVER rebuild markers (it closes the expanded)
|
// ✅ If expanded popup is open, NEVER rebuild markers (rebuild would close it)
|
||||||
if (isExpandedOpen()) return;
|
if (expandedElRef?.current) return;
|
||||||
|
|
||||||
clearAllMarkers(markersRef, expandedElRef);
|
clearAllMarkers(markersRef, expandedElRef);
|
||||||
|
|
||||||
|
|
@ -62,7 +54,6 @@ export function usePostsEngine({
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// ✅ Sociowall list can still update via visiblePosts; markers rebuild is blocked while expanded open.
|
|
||||||
rebuildMarkersForFilters(timeFilter);
|
rebuildMarkersForFilters(timeFilter);
|
||||||
}, [timeFilter, mainFilter, subFilter, rebuildMarkersForFilters]);
|
}, [timeFilter, mainFilter, subFilter, rebuildMarkersForFilters]);
|
||||||
|
|
||||||
|
|
@ -74,13 +65,26 @@ export function usePostsEngine({
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
// ✅ If expanded is open: delay fetch indefinitely until user closes it
|
const mapObj = mapRef.current;
|
||||||
if (isExpandedOpen()) {
|
|
||||||
|
// ✅ If expanded popup is open: delay fetch (do NOT clear/rebuild while user reads)
|
||||||
|
if (expandedElRef?.current) {
|
||||||
if (delayedFetchRef.current) clearTimeout(delayedFetchRef.current);
|
if (delayedFetchRef.current) clearTimeout(delayedFetchRef.current);
|
||||||
delayedFetchRef.current = setTimeout(load, 1200);
|
delayedFetchRef.current = setTimeout(load, 2500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also respect ignore window (during auto-pan)
|
||||||
|
try {
|
||||||
|
const until = mapObj?.__swIgnoreFetchUntil || 0;
|
||||||
|
if (until && Date.now() < until) {
|
||||||
|
const wait = Math.min(4000, until - Date.now());
|
||||||
|
if (delayedFetchRef.current) clearTimeout(delayedFetchRef.current);
|
||||||
|
delayedFetchRef.current = setTimeout(load, wait + 25);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
const [lng, lat] = viewParams.center;
|
const [lng, lat] = viewParams.center;
|
||||||
const radiusKm = viewParams.radiusKm || 750;
|
const radiusKm = viewParams.radiusKm || 750;
|
||||||
const filterKey = `${mainFilter}|${subFilter}`;
|
const filterKey = `${mainFilter}|${subFilter}`;
|
||||||
|
|
@ -93,6 +97,7 @@ export function usePostsEngine({
|
||||||
|
|
||||||
const lastR = last.radiusKm || 0;
|
const lastR = last.radiusKm || 0;
|
||||||
const radiusChanged = !lastR || Math.abs(radiusKm - lastR) / Math.max(lastR, 1) >= 0.25;
|
const radiusChanged = !lastR || Math.abs(radiusKm - lastR) / Math.max(lastR, 1) >= 0.25;
|
||||||
|
|
||||||
const minMoveKm = Math.max(50, radiusKm * 0.25);
|
const minMoveKm = Math.max(50, radiusKm * 0.25);
|
||||||
|
|
||||||
if (!radiusChanged && distKm < minMoveKm) return;
|
if (!radiusChanged && distKm < minMoveKm) return;
|
||||||
|
|
@ -132,9 +137,30 @@ export function usePostsEngine({
|
||||||
allPostsRef.current = Array.from(byId.values());
|
allPostsRef.current = Array.from(byId.values());
|
||||||
lastFetchRef.current = { center: [...viewParams.center], radiusKm, filterKey };
|
lastFetchRef.current = { center: [...viewParams.center], radiusKm, filterKey };
|
||||||
|
|
||||||
// ✅ rebuild markers only if expanded is NOT open (double-safety)
|
// If expanded popup is open, don't rebuild markers (keeps it open)
|
||||||
if (!isExpandedOpen()) rebuildMarkersForFilters(timeFilter);
|
try {
|
||||||
|
if (expandedElRef?.current) {
|
||||||
|
const posts = allPostsRef.current || [];
|
||||||
|
const catCode2 = categoryCode(mainFilter);
|
||||||
|
|
||||||
|
const visible2 = posts.filter((p) => {
|
||||||
|
if (catCode2) {
|
||||||
|
const pc = (p.category || p.Category || "").toString().toUpperCase();
|
||||||
|
if (pc !== catCode2) return false;
|
||||||
|
}
|
||||||
|
if (!matchesSubFilter(p, subFilter)) return false;
|
||||||
|
if (!matchesTimeFilter(p.created_at || p.CreatedAt, timeFilter)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
setVisiblePosts(visible2);
|
||||||
|
setStatus(visible2.length ? "" : "No posts found.");
|
||||||
|
setLoadingPosts(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
rebuildMarkersForFilters(timeFilter);
|
||||||
setLoadingPosts(false);
|
setLoadingPosts(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Erreur chargement posts:", err);
|
console.error("Erreur chargement posts:", err);
|
||||||
|
|
@ -150,13 +176,11 @@ export function usePostsEngine({
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
if (delayedFetchRef.current) {
|
if (delayedFetchRef.current) {
|
||||||
try {
|
try { clearTimeout(delayedFetchRef.current); } catch {}
|
||||||
clearTimeout(delayedFetchRef.current);
|
|
||||||
} catch {}
|
|
||||||
delayedFetchRef.current = null;
|
delayedFetchRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [viewParams, mainFilter, subFilter, mapRef, rebuildMarkersForFilters, timeFilter]);
|
}, [viewParams, mainFilter, subFilter, mapRef, rebuildMarkersForFilters, timeFilter, expandedElRef]);
|
||||||
|
|
||||||
const handleIncomingPost = useCallback(
|
const handleIncomingPost = useCallback(
|
||||||
(p) => {
|
(p) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
:root{
|
:root{
|
||||||
|
--sw-subcats-nudge: -35px; /* subcats vertical nudge */
|
||||||
--sw-below-overlap: 40px; /* wall overlaps map a bit */
|
--sw-below-overlap: 40px; /* wall overlaps map a bit */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,3 +247,263 @@
|
||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* SW_FIX_FULL_WRAP_COLLAPSE:BEGIN */
|
||||||
|
/* Fix: expanded template wrapper collapsing into a thin line */
|
||||||
|
.post-pin--expanded .post-card.sw-expanded-shell{
|
||||||
|
width: 360px !important;
|
||||||
|
min-width: 360px !important;
|
||||||
|
max-width: 360px !important;
|
||||||
|
height: 520px !important;
|
||||||
|
max-height: 520px !important;
|
||||||
|
overflow: hidden !important; /* no useless scroll */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-template-full-wrap{
|
||||||
|
display: block !important;
|
||||||
|
width: 360px !important;
|
||||||
|
height: 520px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-template-full-wrap > *{
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
/* SW_FIX_FULL_WRAP_COLLAPSE:END */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Expanded header (mock) ===== */
|
||||||
|
.sw-expanded-badge{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 12px;
|
||||||
|
background: rgba(56,189,248,0.18);
|
||||||
|
color: #D7F3FF;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-expanded-title{
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.05;
|
||||||
|
color: #F0F7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-expanded-snippet{
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #B5C7E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
SW_EXPANDED_POLISH (visual only)
|
||||||
|
- make expanded look like the mini gradients / modern UI
|
||||||
|
========================================================= */
|
||||||
|
.post-pin--expanded .post-card{
|
||||||
|
background: linear-gradient(135deg, rgba(56,189,248,0.16), rgba(7,18,37,0.98) 55%, rgba(2,6,23,0.98));
|
||||||
|
border: 1px solid rgba(96,165,250,0.75);
|
||||||
|
box-shadow: 0 18px 60px rgba(0,0,0,0.55), 0 0 24px rgba(56,189,248,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-expanded-badge{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
color: #d7f3ff;
|
||||||
|
background: rgba(56,189,248,0.16);
|
||||||
|
border: 1px solid rgba(56,189,248,0.35);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-expanded-title{
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 950;
|
||||||
|
line-height: 1.05;
|
||||||
|
color: #f0f7ff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-expanded-meta{
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(181,199,230,0.95);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-expanded-snippet{
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: rgba(215,233,255,0.95);
|
||||||
|
background: rgba(3,14,32,0.45);
|
||||||
|
border: 1px solid rgba(90,190,255,0.22);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card-btn{
|
||||||
|
background: rgba(56,189,248,0.14);
|
||||||
|
border: 1px solid rgba(56,189,248,0.35);
|
||||||
|
color: #e8f5ff;
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card-btn:active{
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CENTER MODAL CONTENT (pretty + consistent) ===== */
|
||||||
|
.sw-modal{
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-toprow{
|
||||||
|
display:flex;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:space-between;
|
||||||
|
gap:10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-badge{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
background: rgba(56,189,248,0.16);
|
||||||
|
border: 1px solid rgba(90, 190, 255, 0.28);
|
||||||
|
color:#D7F3FF;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-meta{
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color:#9eb7ff;
|
||||||
|
opacity:.95;
|
||||||
|
display:flex;
|
||||||
|
gap:6px;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-x{
|
||||||
|
border:none;
|
||||||
|
border-radius:999px;
|
||||||
|
width:34px;
|
||||||
|
height:34px;
|
||||||
|
background: rgba(3,14,32,0.55);
|
||||||
|
color:#e8f5ff;
|
||||||
|
font-weight:900;
|
||||||
|
cursor:pointer;
|
||||||
|
border:1px solid rgba(90,190,255,0.25);
|
||||||
|
flex-shrink:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-hero{
|
||||||
|
width:100%;
|
||||||
|
height: 180px;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow:hidden;
|
||||||
|
border: 1px solid rgba(90, 190, 255, 0.22);
|
||||||
|
background: radial-gradient(circle at 30% 30%, rgba(56,189,248,0.18), rgba(3,14,32,0.85));
|
||||||
|
box-shadow: 0 10px 24px rgba(0,0,0,0.35);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-hero img{
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-hero-fallback{
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
background: radial-gradient(circle at 35% 35%, rgba(56,189,248,0.22), rgba(3,14,32,0.85));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-title{
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 26px;
|
||||||
|
color:#F0F7FF;
|
||||||
|
margin: 6px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-summary{
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 17px;
|
||||||
|
color:#D7E9FF;
|
||||||
|
opacity:.95;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-stat-row{
|
||||||
|
display:flex;
|
||||||
|
gap:8px;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
margin: 6px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-stat-pill{
|
||||||
|
font-size: 11px;
|
||||||
|
color:#c7e3ff;
|
||||||
|
background: rgba(3, 14, 32, 0.58);
|
||||||
|
border: 1px solid rgba(90, 190, 255, 0.22);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-cta-row{
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-cta-primary{
|
||||||
|
width:100%;
|
||||||
|
border:none;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 14px 12px;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 14px;
|
||||||
|
color:#e8f5ff;
|
||||||
|
cursor:pointer;
|
||||||
|
background: rgba(56,189,248,0.22);
|
||||||
|
box-shadow: 0 10px 18px rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-cta-primary:disabled{
|
||||||
|
opacity:.55;
|
||||||
|
cursor:default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-cta-actions{
|
||||||
|
display:flex;
|
||||||
|
gap:8px;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mobile tighten */
|
||||||
|
@media (max-width: 640px){
|
||||||
|
.sw-modal-hero{ height: 150px; }
|
||||||
|
.sw-modal-title{ font-size: 20px; line-height: 24px; }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,9 @@
|
||||||
display:flex; flex-direction:column; gap:.5rem;
|
display:flex; flex-direction:column; gap:.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✅ sub-cats: keep above wall overlap + moved DOWN by 10px more */
|
/* ✅ Sub-categories row MUST stay above the Sociowall overlap */
|
||||||
.map-overlay-bottom{
|
/* ✅ DESCEND 25px (was 0 / 10 / 20): */
|
||||||
bottom: calc(2.6rem + var(--sw-below-overlap, 0px) + env(safe-area-inset-bottom) - 20px);
|
.map-overlay-bottom { bottom: calc(3.2rem + var(--sw-below-overlap, 0px) + env(safe-area-inset-bottom) + var(--sw-subcats-nudge, 0px)); left:50%; transform:translateX(-50%); display:flex; gap:.4rem; }
|
||||||
left:50%;
|
|
||||||
transform:translateX(-50%);
|
|
||||||
display:flex;
|
|
||||||
gap:.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-overlay-myloc { right:.7rem; bottom:6rem; }
|
.map-overlay-myloc { right:.7rem; bottom:6rem; }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue