update frontend
This commit is contained in:
parent
d7f12db601
commit
ef79700865
|
|
@ -7,6 +7,12 @@ import { cardTokens } from "../../theme/cardTokens";
|
|||
import { getTemplateSpecForPost, adaptPostToTemplateData } from "./templateSpecs";
|
||||
|
||||
export function clearAllMarkers(markersRef, expandedElRef) {
|
||||
// close overlay if any
|
||||
try {
|
||||
const el = expandedElRef?.current;
|
||||
if (el && el.__swClose) el.__swClose();
|
||||
} catch {}
|
||||
|
||||
if (markersRef.current) {
|
||||
for (const m of markersRef.current) {
|
||||
try {
|
||||
|
|
@ -46,9 +52,11 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) {
|
|||
const list = markersRef.current || [];
|
||||
for (const item of list) {
|
||||
const el = item?.el;
|
||||
if (!el || el === expandedEl) continue;
|
||||
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";
|
||||
|
|
@ -83,7 +91,9 @@ function mountCard(container, spec, data, theme) {
|
|||
themeTokens: tokens,
|
||||
onAction: (action, value) => {
|
||||
if (action?.type === "open_url" && value) {
|
||||
try { window.open(value, "_blank"); } catch {}
|
||||
try {
|
||||
window.open(value, "_blank");
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
className: "",
|
||||
|
|
@ -91,33 +101,169 @@ function mountCard(container, spec, data, theme) {
|
|||
);
|
||||
|
||||
return () => {
|
||||
try { root.unmount(); } catch {}
|
||||
try {
|
||||
root.unmount();
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the marker anchor to be displayed at:
|
||||
* - X: centered (50% width)
|
||||
* - Y: 6/7 of map height (so the popup sits "1/7 from bottom")
|
||||
* Overlay: fixed card at the bottom + pointer that follows the marker.
|
||||
* We only pan the minimum if the marker ends up behind the card.
|
||||
*/
|
||||
function panMarkerToPreferredAnchor(map, lngLat) {
|
||||
if (!map) return;
|
||||
function getOrCreateExpandedOverlay(map) {
|
||||
const container = map.getContainer();
|
||||
if (!container) return null;
|
||||
|
||||
const el = map.getContainer();
|
||||
if (!el) return;
|
||||
let root = container.querySelector(".sw-expanded-overlay-root");
|
||||
if (root) return root;
|
||||
|
||||
const rect = el.getBoundingClientRect();
|
||||
const targetX = rect.width * 0.5;
|
||||
const targetY = rect.height * (6 / 7); // 1/7 from bottom => anchor at 6/7 height
|
||||
root = document.createElement("div");
|
||||
root.className = "sw-expanded-overlay-root";
|
||||
|
||||
root.innerHTML = `
|
||||
<div class="sw-expanded-overlay-card">
|
||||
<div class="post-card sw-expanded-shell">
|
||||
<div class="sw-expanded-top">
|
||||
<div class="sw-expanded-left">
|
||||
<div class="sw-template-full-wrap"></div>
|
||||
</div>
|
||||
<div class="sw-expanded-right">
|
||||
<div class="sw-watch-title">Watching</div>
|
||||
<div class="sw-watch-list">
|
||||
<div class="sw-watch-item">☑ Julia ★<div class="sw-watch-msg">— I see it…</div></div>
|
||||
<div class="sw-watch-item">☑ Kim / CNN<div class="sw-watch-msg">— Blah blah blah</div></div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sw-news-generated">
|
||||
<div class="sw-news-title"></div>
|
||||
<div class="sw-news-sub">The news here (generated)</div>
|
||||
</div>
|
||||
|
||||
<div class="post-card-footer sw-actions-row">
|
||||
<button class="post-card-btn">Contact</button>
|
||||
<button class="post-card-btn">Like</button>
|
||||
<button class="post-card-btn">Share</button>
|
||||
<button class="post-card-btn">Fix</button>
|
||||
</div>
|
||||
|
||||
<div class="post-card-comments sw-livechat">
|
||||
<div class="sw-livechat-title">Live chat / comments</div>
|
||||
<div class="sw-livechat-body">
|
||||
<div class="sw-chat-line">— …</div>
|
||||
<div class="sw-chat-line">— what the f***?</div>
|
||||
<div class="sw-chat-line">— nice…</div>
|
||||
<div class="sw-chat-line">— my eyes!!</div>
|
||||
</div>
|
||||
<div class="sw-livechat-inputrow">
|
||||
<input class="sw-livechat-input" placeholder="Write a comment…" />
|
||||
<button class="sw-livechat-post" type="button">POST</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sw-overlay-line"></div>
|
||||
<div class="sw-overlay-pointer"></div>
|
||||
`;
|
||||
|
||||
// helper close fn
|
||||
root.__swClose = () => {
|
||||
try {
|
||||
if (root.__swUnmountFull) root.__swUnmountFull();
|
||||
} catch {}
|
||||
try {
|
||||
if (root.__swMoveHandler && root.__swMap) {
|
||||
root.__swMap.off("move", root.__swMoveHandler);
|
||||
root.__swMap.off("zoom", root.__swMoveHandler);
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
root.remove();
|
||||
} catch {}
|
||||
};
|
||||
|
||||
container.appendChild(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
function updateOverlayPointer(map, overlayRoot, lngLat) {
|
||||
if (!map || !overlayRoot || !lngLat) return;
|
||||
|
||||
const p = map.project(lngLat);
|
||||
const dx = targetX - p.x;
|
||||
const dy = targetY - p.y;
|
||||
const pointer = overlayRoot.querySelector(".sw-overlay-pointer");
|
||||
const line = overlayRoot.querySelector(".sw-overlay-line");
|
||||
const cardWrap = overlayRoot.querySelector(".sw-expanded-overlay-card");
|
||||
const card = overlayRoot.querySelector(".sw-expanded-overlay-card .post-card");
|
||||
|
||||
// tiny deltas -> skip
|
||||
if (Math.abs(dx) < 2 && Math.abs(dy) < 2) return;
|
||||
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;
|
||||
|
||||
// lock close/fetch/rebuild while panning
|
||||
try {
|
||||
const until = Date.now() + 2000;
|
||||
map.__swIgnoreMapClickUntil = until;
|
||||
|
|
@ -126,7 +272,7 @@ function panMarkerToPreferredAnchor(map, lngLat) {
|
|||
map.__swIgnoreRebuildUntil = until;
|
||||
} catch {}
|
||||
|
||||
map.panBy([dx, dy], { duration: 650 });
|
||||
map.panBy([0, dy], { duration: 350 });
|
||||
}
|
||||
|
||||
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
|
||||
|
|
@ -160,7 +306,9 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
|||
|
||||
function unmountIfAny() {
|
||||
if (root.__swUnmount) {
|
||||
try { root.__swUnmount(); } catch {}
|
||||
try {
|
||||
root.__swUnmount();
|
||||
} catch {}
|
||||
root.__swUnmount = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -185,97 +333,86 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
|||
clearOcclusion(markersRef);
|
||||
}
|
||||
|
||||
function renderExpanded() {
|
||||
unmountIfAny();
|
||||
root.className = "post-pin post-pin--expanded";
|
||||
root.style.zIndex = "999999";
|
||||
|
||||
// 2000ms locks so recenter/fetch/rebuild does not close this expanded popup
|
||||
function closeExpandedOverlayIfAny() {
|
||||
const cur = expandedElRef.current;
|
||||
if (cur && cur.__swClose) {
|
||||
try {
|
||||
const m = mapRef.current;
|
||||
if (m) {
|
||||
const until = Date.now() + 2000;
|
||||
m.__swIgnoreMapClickUntil = until;
|
||||
m.__swIgnoreCloseUntil = until;
|
||||
m.__swIgnoreFetchUntil = until;
|
||||
m.__swIgnoreRebuildUntil = until;
|
||||
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;
|
||||
|
||||
root.innerHTML = `
|
||||
<div class="post-card sw-expanded-shell">
|
||||
<div class="sw-expanded-top">
|
||||
<div class="sw-expanded-left">
|
||||
<div class="sw-template-full-wrap"></div>
|
||||
</div>
|
||||
// mount template full into the left area
|
||||
try {
|
||||
if (overlayRoot.__swUnmountFull) overlayRoot.__swUnmountFull();
|
||||
} catch {}
|
||||
overlayRoot.__swUnmountFull = null;
|
||||
|
||||
<div class="sw-expanded-right">
|
||||
<div class="sw-watch-title">Watching</div>
|
||||
<div class="sw-watch-list">
|
||||
<div class="sw-watch-item">☑ Julia ★<div class="sw-watch-msg">— I see it…</div></div>
|
||||
<div class="sw-watch-item">☑ Kim / CNN<div class="sw-watch-msg">— Blah blah blah</div></div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sw-news-generated">
|
||||
<div class="sw-news-title">${headline}</div>
|
||||
<div class="sw-news-sub">The news here (generated)</div>
|
||||
</div>
|
||||
|
||||
<div class="post-card-footer sw-actions-row">
|
||||
<button class="post-card-btn">Contact</button>
|
||||
<button class="post-card-btn">Like</button>
|
||||
<button class="post-card-btn">Share</button>
|
||||
<button class="post-card-btn">Fix</button>
|
||||
</div>
|
||||
|
||||
<div class="post-card-comments sw-livechat">
|
||||
<div class="sw-livechat-title">Live chat / comments</div>
|
||||
<div class="sw-livechat-body">
|
||||
<div class="sw-chat-line">— …</div>
|
||||
<div class="sw-chat-line">— what the f***?</div>
|
||||
<div class="sw-chat-line">— nice…</div>
|
||||
<div class="sw-chat-line">— my eyes!!</div>
|
||||
</div>
|
||||
<div class="sw-livechat-inputrow">
|
||||
<input class="sw-livechat-input" placeholder="Write a comment…" />
|
||||
<button class="sw-livechat-post" type="button">POST</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="post-card-pointer"></div>
|
||||
`;
|
||||
|
||||
const wrap = root.querySelector(".sw-template-full-wrap");
|
||||
const wrap = overlayRoot.querySelector(".sw-template-full-wrap");
|
||||
if (wrap) {
|
||||
wrap.innerHTML = ""; // ensure clean
|
||||
const spec = getTemplateSpecForPost(post, "full");
|
||||
const data = adaptPostToTemplateData(post);
|
||||
root.__swUnmount = mountCard(wrap, spec, data, theme);
|
||||
overlayRoot.__swUnmountFull = mountCard(wrap, spec, data, theme);
|
||||
}
|
||||
|
||||
// After render, pan so marker sits at the preferred anchor (1/7 from bottom)
|
||||
// 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 {
|
||||
const m = mapRef.current;
|
||||
if (!m) return;
|
||||
// 1) update pointer/line
|
||||
updateOverlayPointer(map, overlayRoot, lngLat);
|
||||
|
||||
// 1) put the marker anchor exactly where we want
|
||||
panMarkerToPreferredAnchor(m, lngLat);
|
||||
// 2) minimal pan so marker isn't hidden behind the fixed card
|
||||
minimalPanToKeepMarkerVisible(map, overlayRoot, lngLat);
|
||||
|
||||
// 2) re-occlude after the pan finishes
|
||||
m.once("moveend", () => {
|
||||
try { applyOcclusionForExpanded(markersRef, root); } catch {}
|
||||
});
|
||||
|
||||
// also occlude right away
|
||||
applyOcclusionForExpanded(markersRef, root);
|
||||
// 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();
|
||||
|
|
@ -290,26 +427,17 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
|||
root.addEventListener("click", (ev) => {
|
||||
ev.stopPropagation();
|
||||
|
||||
if (expandedElRef.current && expandedElRef.current !== root) {
|
||||
if (expandedElRef.current.__renderCompact) expandedElRef.current.__renderCompact();
|
||||
expandedElRef.current = null;
|
||||
// toggle: if overlay already open for this post, close it
|
||||
const cur = expandedElRef.current;
|
||||
if (cur && cur.__swForId && cur.__swForId === (post.id ?? `${lon},${lat}`)) {
|
||||
closeExpandedOverlayIfAny();
|
||||
return;
|
||||
}
|
||||
|
||||
const isExpanded = root.classList.contains("post-pin--expanded");
|
||||
if (isExpanded) {
|
||||
renderCompact();
|
||||
expandedElRef.current = null;
|
||||
} else {
|
||||
renderExpanded();
|
||||
expandedElRef.current = root;
|
||||
// open overlay
|
||||
renderExpandedOverlay();
|
||||
if (expandedElRef.current) {
|
||||
expandedElRef.current.__swForId = post.id ?? `${lon},${lat}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,23 @@ export function useMapCore(theme) {
|
|||
|
||||
function closeExpandedIfAny() {
|
||||
const el = expandedElRef.current;
|
||||
if (el && el.__renderCompact) el.__renderCompact();
|
||||
if (!el) return;
|
||||
|
||||
// overlay style
|
||||
if (el.__swClose) {
|
||||
try {
|
||||
el.__swClose();
|
||||
} catch {}
|
||||
expandedElRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// legacy marker-expanded style
|
||||
if (el.__renderCompact) {
|
||||
try {
|
||||
el.__renderCompact();
|
||||
} catch {}
|
||||
}
|
||||
expandedElRef.current = null;
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +104,7 @@ export function useMapCore(theme) {
|
|||
map.on("move", reOcclude);
|
||||
map.on("zoom", reOcclude);
|
||||
|
||||
// IMPORTANT: ignore close while we are auto-panning to keep popup visible
|
||||
// ignore close while auto actions are happening
|
||||
map.on("click", () => {
|
||||
try {
|
||||
const until = map.__swIgnoreMapClickUntil || 0;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
min-width:0;
|
||||
}
|
||||
|
||||
/* keep side-by-side on most phones; stack only ultra-small */
|
||||
@media (max-width: 420px){
|
||||
/* stack only ultra-small (NOT normal portrait phones) */
|
||||
@media (max-width: 340px){
|
||||
.below-row{ flex-direction:column; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,362 +37,86 @@
|
|||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
.post-pin--expanded { z-index: 999999; }
|
||||
|
||||
.post-pin--expanded .post-card {
|
||||
/* ===== Template mini wrapper (keeps pointer unchanged) ===== */
|
||||
.sw-template-mini-wrap{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-50%, calc(-100% - 14px));
|
||||
min-width: 260px;
|
||||
max-width: 320px;
|
||||
transform: translate(-50%, calc(-100% - 12px)) scale(0.62);
|
||||
transform-origin: bottom center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@media (max-width: 640px){
|
||||
.sw-template-mini-wrap{
|
||||
transform: translate(-50%, calc(-100% - 12px)) scale(0.52);
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
NEW: FIXED EXPANDED OVERLAY
|
||||
- Card is fixed near bottom (consistent)
|
||||
- Pointer follows marker (consistent)
|
||||
- No huge map recenter jumps
|
||||
========================================================= */
|
||||
|
||||
.sw-expanded-overlay-root{
|
||||
position:absolute;
|
||||
inset:0;
|
||||
z-index: 999999;
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
/* Card position: "dans le bas" (approx bottom zone), consistent on rotate */
|
||||
.sw-expanded-overlay-card{
|
||||
position:absolute;
|
||||
left: 50%;
|
||||
bottom: calc(12px + env(safe-area-inset-bottom) + var(--sw-below-overlap, 0px));
|
||||
transform: translateX(-50%);
|
||||
width: min(92vw, 420px);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Make card fit mobile nicely */
|
||||
.sw-expanded-overlay-card .post-card{
|
||||
width: 100%;
|
||||
max-height: min(58vh, 640px);
|
||||
overflow:auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: rgba(8, 20, 40, 0.97);
|
||||
border-radius: 14px;
|
||||
padding: 10px 12px;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.8);
|
||||
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.85);
|
||||
border: 1px solid rgba(90, 190, 255, 0.9);
|
||||
color: #e8f5ff;
|
||||
}
|
||||
.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; }
|
||||
.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; }
|
||||
.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);
|
||||
}
|
||||
.post-pin--expanded .post-card-pointer {
|
||||
|
||||
/* Pointer on the marker position */
|
||||
.sw-overlay-pointer{
|
||||
position:absolute;
|
||||
left:0; top:0;
|
||||
transform: translate(-50%, -100%);
|
||||
width:0; height:0;
|
||||
border-left:10px solid transparent;
|
||||
border-left: 10px solid transparent;
|
||||
border-right:10px solid transparent;
|
||||
border-top:11px solid rgba(8, 20, 40, 0.97);
|
||||
border-top: 11px solid rgba(8, 20, 40, 0.97);
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,.8));
|
||||
}
|
||||
|
||||
/* ===== Template marker wrappers (pointers stay the same classes) ===== */
|
||||
.sw-template-mini-wrap{
|
||||
position: absolute;
|
||||
transform: translate(-50%, -100%);
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-50%, calc(-100% - 12px));
|
||||
}
|
||||
|
||||
.sw-expanded-shell{
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.sw-expanded-top{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
align-items:flex-start;
|
||||
}
|
||||
|
||||
.sw-expanded-left{
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sw-expanded-right{
|
||||
width: 150px;
|
||||
min-width: 140px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:8px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.sw-watch-title{
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
color:#bfdbfe;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
|
||||
.sw-watch-list{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:6px;
|
||||
}
|
||||
|
||||
.sw-watch-item{
|
||||
font-size: 11px;
|
||||
color:#e8f5ff;
|
||||
background: rgba(3, 14, 32, 0.75);
|
||||
border: 1px solid rgba(90, 190, 255, 0.35);
|
||||
border-radius: 10px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.sw-watch-msg{
|
||||
opacity:.85;
|
||||
margin-top:2px;
|
||||
font-size: 10px;
|
||||
color:#c7e3ff;
|
||||
}
|
||||
|
||||
.sw-add-feed-btn{
|
||||
margin-top: 2px;
|
||||
border: 1px solid rgba(148,163,184,.7);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(5, 35, 70, 0.85);
|
||||
color:#e8f5ff;
|
||||
font-weight: 900;
|
||||
font-size: 11px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.sw-news-generated{
|
||||
margin-top: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(3, 14, 32, 0.75);
|
||||
border: 1px solid rgba(90, 190, 255, 0.35);
|
||||
}
|
||||
|
||||
.sw-news-title{
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
color:#e8f5ff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.sw-news-sub{
|
||||
font-size: 10px;
|
||||
color:#9eb7ff;
|
||||
}
|
||||
|
||||
.sw-actions-row{
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sw-livechat{
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sw-livechat-title{
|
||||
font-weight: 800;
|
||||
letter-spacing:.04em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.sw-livechat-body{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:3px;
|
||||
margin-bottom: 8px;
|
||||
opacity:.95;
|
||||
}
|
||||
|
||||
.sw-chat-line{
|
||||
font-size: 10px;
|
||||
color:#c7e3ff;
|
||||
}
|
||||
|
||||
.sw-livechat-inputrow{
|
||||
display:flex;
|
||||
gap:8px;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
.sw-livechat-input{
|
||||
flex:1;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(90, 190, 255, 0.35);
|
||||
background: rgba(3, 14, 32, 0.75);
|
||||
color:#e8f5ff;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sw-livechat-post{
|
||||
border:none;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(56,189,248,0.22);
|
||||
color:#e8f5ff;
|
||||
font-weight: 900;
|
||||
font-size: 11px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
/* ===== Template marker wrappers (pointers stay the same classes) ===== */
|
||||
.sw-template-mini-wrap{
|
||||
position: absolute;
|
||||
/* Simple line from marker to card */
|
||||
.sw-overlay-line{
|
||||
position:absolute;
|
||||
height: 2px;
|
||||
background: rgba(56,189,248,0.35);
|
||||
transform-origin: 0 50%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-50%, calc(-100% - 12px));
|
||||
}
|
||||
|
||||
.sw-expanded-shell{
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.sw-expanded-top{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
align-items:flex-start;
|
||||
}
|
||||
|
||||
.sw-expanded-left{
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sw-expanded-right{
|
||||
width: 150px;
|
||||
min-width: 140px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:8px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.sw-watch-title{
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
color:#bfdbfe;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
|
||||
.sw-watch-list{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:6px;
|
||||
}
|
||||
|
||||
.sw-watch-item{
|
||||
font-size: 11px;
|
||||
color:#e8f5ff;
|
||||
background: rgba(3, 14, 32, 0.75);
|
||||
border: 1px solid rgba(90, 190, 255, 0.35);
|
||||
border-radius: 10px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.sw-watch-msg{
|
||||
opacity:.85;
|
||||
margin-top:2px;
|
||||
font-size: 10px;
|
||||
color:#c7e3ff;
|
||||
}
|
||||
|
||||
.sw-add-feed-btn{
|
||||
margin-top: 2px;
|
||||
border: 1px solid rgba(148,163,184,.7);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(5, 35, 70, 0.85);
|
||||
color:#e8f5ff;
|
||||
font-weight: 900;
|
||||
font-size: 11px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.sw-news-generated{
|
||||
margin-top: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(3, 14, 32, 0.75);
|
||||
border: 1px solid rgba(90, 190, 255, 0.35);
|
||||
}
|
||||
|
||||
.sw-news-title{
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
color:#e8f5ff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.sw-news-sub{
|
||||
font-size: 10px;
|
||||
color:#9eb7ff;
|
||||
}
|
||||
|
||||
.sw-actions-row{
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sw-livechat{
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sw-livechat-title{
|
||||
font-weight: 800;
|
||||
letter-spacing:.04em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.sw-livechat-body{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:3px;
|
||||
margin-bottom: 8px;
|
||||
opacity:.95;
|
||||
}
|
||||
|
||||
.sw-chat-line{
|
||||
font-size: 10px;
|
||||
color:#c7e3ff;
|
||||
}
|
||||
|
||||
.sw-livechat-inputrow{
|
||||
display:flex;
|
||||
gap:8px;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
.sw-livechat-input{
|
||||
flex:1;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(90, 190, 255, 0.35);
|
||||
background: rgba(3, 14, 32, 0.75);
|
||||
color:#e8f5ff;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sw-livechat-post{
|
||||
border:none;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(56,189,248,0.22);
|
||||
color:#e8f5ff;
|
||||
font-weight: 900;
|
||||
font-size: 11px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
/* ===== Template marker wrappers (pointers stay the same classes) ===== */
|
||||
.sw-template-mini-wrap{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-50%, calc(-100% - 12px));
|
||||
width: 0;
|
||||
filter: drop-shadow(0 1px 2px rgba(0,0,0,.6));
|
||||
}
|
||||
|
||||
/* ===== Expanded layout pieces (same as before) ===== */
|
||||
.sw-expanded-shell{ padding: 10px 12px; }
|
||||
|
||||
.sw-expanded-top{
|
||||
|
|
@ -511,156 +235,15 @@
|
|||
cursor:pointer;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
RESPONSIVE SIZING FIX
|
||||
- Mini markers were too big (280x110 real size)
|
||||
- Expanded card was overflowing on mobile
|
||||
========================================================= */
|
||||
|
||||
/* Mini template wrapper: keep pointer EXACTLY as-is, only scale the card */
|
||||
.sw-template-mini-wrap{
|
||||
/* was: translate(-50%, calc(-100% - 12px)) */
|
||||
transform: translate(-50%, calc(-100% - 12px)) scale(var(--sw-mini-scale, 0.72));
|
||||
transform-origin: bottom center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Expanded card: make it fit phone screens nicely */
|
||||
.post-pin--expanded .post-card{
|
||||
max-width: min(92vw, 380px);
|
||||
width: auto;
|
||||
max-height: min(72vh, 640px);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Layout in expanded sketch area */
|
||||
.sw-expanded-top{
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Mobile tweaks */
|
||||
/* Mobile: keep overlay readable */
|
||||
@media (max-width: 640px){
|
||||
/* Shrink mini markers even more on phones */
|
||||
.sw-template-mini-wrap{
|
||||
--sw-mini-scale: 0.58;
|
||||
.sw-expanded-overlay-card{
|
||||
width: min(94vw, 360px);
|
||||
}
|
||||
|
||||
/* Expanded: stack instead of side-by-side */
|
||||
.sw-expanded-top{
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Right panel becomes horizontal strip (or you can hide it if you prefer) */
|
||||
.sw-expanded-right{
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.sw-watch-title{
|
||||
white-space: nowrap;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.sw-watch-list{
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sw-watch-item{
|
||||
min-width: 180px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sw-add-feed-btn{
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* SW_EXPANDED_OFFSET_FIX: lower expanded cards on phones (keep pointers unchanged) */
|
||||
@media (max-width: 640px){
|
||||
.post-pin--expanded .post-card{
|
||||
transform: translate(-50%, calc(-100% - 6px));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* SW_MOBILE_EXPANDED_TIGHTEN: make expanded marker card less tall on phones (no pointer change) */
|
||||
@media (max-width: 640px){
|
||||
.post-pin--expanded .post-card{
|
||||
padding: 8px 10px;
|
||||
max-width: 300px;
|
||||
}
|
||||
.post-pin--expanded .post-card-main{ gap: 6px; margin-bottom: 6px; }
|
||||
.post-pin--expanded .post-card-media{ width: 56px; height: 56px; border-radius: 10px; }
|
||||
.post-pin--expanded .post-card-title{ font-size: 12px; line-height: 15px; }
|
||||
.post-pin--expanded .post-card-body{ font-size: 11px; max-height: 56px; }
|
||||
}
|
||||
|
||||
/* SW_TEMPLATE_FULL_TIGHTEN: make template-full card less tall on phones */
|
||||
@media (max-width: 640px){
|
||||
.sw-template-full{
|
||||
transform: translate(-50%, calc(-100% - 14px)) scale(0.88);
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== MOBILE TUNING: expanded marker popup (template OR classic) ===== */
|
||||
@media (max-width: 640px){
|
||||
/* shrink the whole expanded marker a bit */
|
||||
.post-pin--expanded{
|
||||
transform: translate3d(0,0,0) scale(0.86);
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
/* keep it inside screen */
|
||||
.post-pin--expanded .post-card{
|
||||
max-width: min(92vw, 340px) !important;
|
||||
}
|
||||
|
||||
/* title/body: prevent giant blocks */
|
||||
.post-pin--expanded .post-card-title{ font-size: 12px !important; line-height: 15px !important; }
|
||||
.post-pin--expanded .post-card-body{ font-size: 11px !important; line-height: 14px !important; max-height: 54px !important; }
|
||||
|
||||
/* buttons/footer area: tighter */
|
||||
.post-pin--expanded .post-card-footer{ gap: 5px !important; }
|
||||
.post-pin--expanded .post-card-btn{ font-size: 10px !important; padding: 3px 7px !important; }
|
||||
}
|
||||
|
||||
/* === FINAL OVERRIDES (safe) === */
|
||||
|
||||
/* Keep pointers unchanged; only scale the mini template card */
|
||||
.sw-template-mini-wrap{
|
||||
transform: translate(-50%, calc(-100% - 12px)) scale(0.62) !important;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
@media (max-width: 640px){
|
||||
.sw-template-mini-wrap{
|
||||
transform: translate(-50%, calc(-100% - 12px)) scale(0.52) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* === SW MOBILE: make expanded popup shorter (no pointer change) === */
|
||||
@media (max-width: 640px){
|
||||
.post-pin--expanded .post-card{
|
||||
max-height: 58vh !important;
|
||||
overflow: auto !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
/* keep right panel from making it tall */
|
||||
.sw-expanded-right{
|
||||
display:none !important;
|
||||
}
|
||||
.sw-news-generated{
|
||||
margin-top: 8px;
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,15 @@
|
|||
display:flex; flex-direction:column; gap:.5rem;
|
||||
}
|
||||
|
||||
/* ✅ Sub-categories row MUST stay above the Sociowall overlap */
|
||||
.map-overlay-bottom { bottom: calc(3.2rem + var(--sw-below-overlap, 0px) + env(safe-area-inset-bottom)); left:50%; transform:translateX(-50%); display:flex; gap:.4rem; }
|
||||
/* ✅ Sub-categories row MUST stay above the Sociowall overlap
|
||||
✅ And move DOWN by ~10px (less bottom padding) */
|
||||
.map-overlay-bottom{
|
||||
bottom: calc(2.6rem + var(--sw-below-overlap, 0px) + env(safe-area-inset-bottom));
|
||||
left:50%;
|
||||
transform:translateX(-50%);
|
||||
display:flex;
|
||||
gap:.4rem;
|
||||
}
|
||||
|
||||
.map-overlay-myloc { right:.7rem; bottom:6rem; }
|
||||
|
||||
|
|
@ -31,7 +38,7 @@
|
|||
background:#38bdf8; transform:translate(-50%,-50%);
|
||||
}
|
||||
.crosshair-aim::before{ width:70%; height:1px; }
|
||||
.crosshair-aim::after{ width:1px; height:70%; }
|
||||
.crosshair-aim::after{ width:1px; height:70px; }
|
||||
|
||||
.create-post-panel {
|
||||
left:50%; bottom:20%; transform:translateX(-50%);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
gap:.6rem;
|
||||
align-items:stretch;
|
||||
min-width:0;
|
||||
flex-wrap: nowrap; /* ✅ keep side-by-side */
|
||||
}
|
||||
|
||||
/* Small top nudge down (as requested) */
|
||||
.sociowall-panel,
|
||||
.chat-panel{
|
||||
margin-top: 5px; /* ✅ descend 5px */
|
||||
}
|
||||
|
||||
.sociowall-panel{
|
||||
|
|
@ -48,7 +55,7 @@
|
|||
cursor:pointer;
|
||||
}
|
||||
|
||||
/* SCALE the fixed 280px template so it never forces width */
|
||||
/* SCALE the fixed template so it never forces width */
|
||||
.post-card .sw-wall-mini{
|
||||
--sw-wall-scale: 0.86;
|
||||
transform: scale(var(--sw-wall-scale));
|
||||
|
|
@ -62,10 +69,12 @@
|
|||
.status-text{ font-size:.72rem; opacity:.85; margin:.15rem 0; }
|
||||
.status-error{ color:#fecaca; }
|
||||
|
||||
/* ✅ Chat fixed column that can shrink a bit on narrow screens */
|
||||
.chat-panel{
|
||||
flex: 0 0 190px;
|
||||
width: 190px;
|
||||
min-width: 190px;
|
||||
max-width: 220px;
|
||||
min-width: 160px; /* allow shrink in portrait */
|
||||
max-width: min(220px, 42vw);
|
||||
min-height: 160px;
|
||||
background: rgba(15, 23, 42, 0.96);
|
||||
border-radius: 14px;
|
||||
|
|
@ -98,9 +107,9 @@
|
|||
.chat-users li{ display:flex; align-items:center; gap:.3rem; padding:.18rem .1rem; }
|
||||
.chat-users li::before{ content:"👤"; font-size:.72rem; opacity:.9; }
|
||||
|
||||
/* stack only very small */
|
||||
@media (max-width: 420px){
|
||||
/* Stack only ultra-ultra-small */
|
||||
@media (max-width: 340px){
|
||||
.below-row{ flex-direction: column; }
|
||||
.chat-panel{ width:100%; min-width:0; max-width:none; }
|
||||
.chat-panel{ width:100%; min-width:0; max-width:none; flex: 1 1 auto; }
|
||||
.post-list{ max-height: 46vh; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue