This commit is contained in:
Your Name 2025-12-18 15:19:25 -05:00
parent e52e451d98
commit 70251093a8
8 changed files with 855 additions and 132 deletions

View File

@ -185,7 +185,7 @@ export default function MapView({ theme = "dark" }) {
};
return (
<div className="map-view">
<div className="map-page">\n <div className="map-stage">
<div ref={containerRef} className="map-container" />
{status && <div className="map-status">{status}</div>}
@ -280,26 +280,36 @@ export default function MapView({ theme = "dark" }) {
</div>
)}
<div className="map-overlay map-overlay-wall">
<div className="sociowall-panel">
<div className="sociowall-header">Sociowall</div>
<PostList
posts={visiblePosts}
loading={loadingPosts}
error={loadError}
selectedPost={selectedPost}
onSelectPost={handleSelectPost}
/>
</div>
<div className="chat-panel">
<div className="chat-header">Chat</div>
<ul className="chat-users">
<li>Yan</li>
<li>Marc</li>
<li>Fiso</li>
</ul>
</div>
{/* BELOW THE MAP (page scroll) */}
<div className="below-stage">
<div className="below-row">
<div className="sociowall-panel">
<div className="sociowall-header">Sociowall</div>
<PostList
posts={visiblePosts}
loading={loadingPosts}
error={loadError}
selectedPost={selectedPost}
onSelectPost={handleSelectPost}
/>
</div>
<div className="chat-panel">
<div className="chat-header">Chat</div>
<ul className="chat-users">
<li>Yan</li>
<li>Marc</li>
<li>Fiso</li>
</ul>
</div>
</div>
</div>
<div style={{ height: 24 }} />
</div>
);
}

View File

@ -2,17 +2,17 @@ import maplibregl from "maplibre-gl";
import React from "react";
import { createRoot } from "react-dom/client";
import TemplateMarkerCard from "./TemplateMarkerCard";
import { getDefaultTemplateCached } from "../../api/templates";
import CardRenderer from "../Cards/CardRenderer";
import { cardTokens } from "../../theme/cardTokens";
import { getTemplateSpecForPost, adaptPostToTemplateData } from "./templateSpecs";
export function clearAllMarkers(markersRef, expandedElRef) {
if (markersRef.current) {
for (const m of markersRef.current) {
try {
if (m?.el && m.el.__reactRoot) {
try { m.el.__reactRoot.unmount(); } catch {}
m.el.__reactRoot = null;
}
if (m?.el && m.el.__swUnmount) m.el.__swUnmount();
} catch {}
try {
m.marker.remove();
} catch {}
}
@ -72,29 +72,30 @@ export function clearOcclusion(markersRef) {
}
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
function mountCard(container, spec, data, theme) {
const root = createRoot(container);
const tokens = cardTokens?.[theme] || cardTokens.blue;
root.render(
React.createElement(CardRenderer, {
spec,
data,
themeTokens: tokens,
onAction: (action, value) => {
if (action?.type === "open_url" && value) {
try { window.open(value, "_blank"); } catch {}
}
},
className: "",
})
);
return () => {
try { root.unmount(); } catch {}
};
}
function currentTheme() {
try {
const t = document?.body?.getAttribute("data-theme") || "dark";
if (t === "dark" || t === "blue" || t === "light") return t;
} catch {}
return "dark";
}
function ensureReactRoot(el) {
if (el.__reactRoot) return el.__reactRoot;
el.__reactRoot = createRoot(el);
return el.__reactRoot;
}
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
const map = mapRef.current;
if (!map) return;
@ -117,65 +118,98 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
return;
}
const title = (post.title || "").trim() || "Untitled";
const root = document.createElement("div");
root.className = "post-pin post-pin--compact";
root.style.zIndex = "1";
function unmountIfAny() {
if (root.__swUnmount) {
try { root.__swUnmount(); } catch {}
root.__swUnmount = null;
}
}
function renderCompact() {
unmountIfAny();
root.className = "post-pin post-pin--compact";
root.style.zIndex = "1";
// pointer stays: .post-pin-pointer-small (UNCHANGED)
root.innerHTML = `
<div class="post-pin-bubble">
<div class="post-pin-dot"></div>
<div class="post-pin-text">${escapeHtml(title)}</div>
</div>
<div class="sw-template-mini-wrap"></div>
<div class="post-pin-pointer-small"></div>
`;
if (root.__reactRoot) {
try { root.__reactRoot.unmount(); } catch {}
root.__reactRoot = null;
const wrap = root.querySelector(".sw-template-mini-wrap");
if (wrap) {
const spec = getTemplateSpecForPost(post, "mini");
const data = adaptPostToTemplateData(post);
root.__swUnmount = mountCard(wrap, spec, data, theme);
}
clearOcclusion(markersRef);
}
async function renderExpanded() {
function renderExpanded() {
unmountIfAny();
root.className = "post-pin post-pin--expanded";
root.style.zIndex = "999999";
// pointer stays: .post-card-pointer (UNCHANGED)
const headline = escapeHtml(post?.title || "Untitled");
root.innerHTML = `
<div class="post-card">
<div class="post-card-body" style="font-size:11px; opacity:.9;">Loading template</div>
<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">${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 cardEl = root.querySelector(".post-card");
if (!cardEl) return;
const theme = currentTheme();
try {
const tpl = await getDefaultTemplateCached("news");
const rr = ensureReactRoot(cardEl);
rr.render(
React.createElement(TemplateMarkerCard, {
post,
template: tpl,
theme,
mode: "full",
})
);
} catch (e) {
cardEl.innerHTML = `
<div style="font-weight:800; font-size:13px; margin-bottom:4px;">${escapeHtml(title)}</div>
<div style="font-size:11px; opacity:.9;">(template load failed)</div>
`;
const wrap = root.querySelector(".sw-template-full-wrap");
if (wrap) {
const spec = getTemplateSpecForPost(post, "full");
const data = adaptPostToTemplateData(post);
root.__swUnmount = mountCard(wrap, spec, data, theme);
}
requestAnimationFrame(() => {
@ -210,3 +244,11 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
}
});
}
function escapeHtml(str) {
return String(str ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}

View File

@ -0,0 +1,71 @@
/**
* Frontend-only template specs (no DB changes).
* Keep pointers as-is:
* - compact triangle: .post-pin-pointer-small
* - expanded triangle: .post-card-pointer
*/
export const TEMPLATE_SPECS = {
news: {
mini_spec: {
size: { w: 280, h: 110 },
background: { type: "gradient", value: "newsBlue" },
radius: 18,
layers: [
{ id: "badge", type: "chip", x: 14, y: 12, text: "NEWS", style: "chip.news" },
{ id: "title", type: "text", x: 14, y: 40, w: 252, h: 44, bind: "data.headline", style: "text.title", maxLines: 2 },
{ id: "meta", type: "text", x: 14, y: 88, w: 252, h: 16, bind: "data.source", style: "text.meta", maxLines: 1 }
],
},
full_spec: {
size: { w: 360, h: 520 },
background: { type: "solid", value: "surface" },
radius: 22,
layers: [
{ id: "hero", type: "image", x: 0, y: 0, w: 360, h: 220, bind: "data.image" },
{ id: "badge", type: "chip", x: 16, y: 16, text: "NEWS", style: "chip.news" },
{ 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: 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" }
}
],
},
},
};
function normCat(post) {
const c = (post?.category || "").toString().toUpperCase();
if (c === "NEWS") return "news";
if (c === "EVENT") return "news";
if (c === "FRIENDS") return "news";
if (c === "MARKET") return "news";
return "news";
}
export function getTemplateKeyForPost(post) {
// Future mapping: if (post.template_id === 101) return "news";
return normCat(post);
}
export function getTemplateSpecForPost(post, variant /* "mini"|"full" */) {
const key = getTemplateKeyForPost(post);
const t = TEMPLATE_SPECS[key] || TEMPLATE_SPECS.news;
return variant === "full" ? t.full_spec : t.mini_spec;
}
export function adaptPostToTemplateData(post) {
const headline = post?.title || "Untitled";
const author = post?.author ? `by ${post.author}` : "";
const sub = post?.sub_category ? String(post.sub_category) : "";
const source = author || sub || "";
const summary = post?.snippet || post?.body || "";
const url = post?.url || "";
const image = post?.image_large || post?.image_small || post?.image || post?.media_url || "";
return { headline, source, summary, url, image };
}

View File

@ -1,24 +1,28 @@
import React from "react";
function getKmFromPost(post) {
return post.km ?? post.distance_km ?? post.distanceKm ?? post.dist_km ?? null;
}
import CardRenderer from "../Cards/CardRenderer";
import { cardTokens } from "../../theme/cardTokens";
import { getTemplateSpecForPost, adaptPostToTemplateData } from "../Map/templateSpecs";
export default function PostCard({ post, selected, onSelect }) {
const title = post.title || post.name || "Post";
const fullBody = post.body || post.content || post.text || "";
const body = fullBody.length > 80 ? fullBody.slice(0, 77) + "..." : fullBody;
const km = getKmFromPost(post);
function handleClick() {
if (onSelect) onSelect(post);
}
const spec = getTemplateSpecForPost(post, "mini");
const data = adaptPostToTemplateData(post);
return (
<article className={"post-card" + (selected ? " selected" : "")} onClick={handleClick}>
<h3>{title}</h3>
<p>{body}</p>
{km != null && <span className="post-km">{km} km</span>}
<article
className={"post-card" + (selected ? " selected" : "")}
onClick={() => onSelect && onSelect(post)}
style={{ padding: 6 }}
>
<div className="sw-wall-mini">
<CardRenderer
spec={spec}
data={data}
themeTokens={cardTokens.blue}
onAction={(action, value) => {
if (action?.type === "open_url" && value) window.open(value, "_blank");
}}
/>
</div>
</article>
);
}

View File

@ -1,3 +1,5 @@
.app-root { height: 100vh; display: flex; flex-direction: column; }
.main-shell { flex: 1; display: flex; padding: 0; }
.map-shell { position: relative; flex: 1; border-radius: 0; overflow: hidden; border: none; background: #020617; }
.app-root{ min-height:100vh; display:flex; flex-direction:column; }
/* IMPORTANT: allow scrolling under the map */
.main-shell{ flex: 0 0 auto; display:block; padding:0; }
.map-shell{ position:relative; width:100%; height:62vh; border-radius:0; overflow:hidden; border:none; background:#020617; }

View File

@ -1,5 +1,19 @@
.map-view { position: relative; width: 100%; height: 100%; }
.map-container, .maplibre-container { position: absolute; inset: 0; width: 100%; height: 100%; }
.map-page { display: flex; flex-direction: column; }
/* Map stage (visible first). Then you scroll down for the wall. */
.map-stage {
position: relative;
width: 100%;
height: clamp(520px, 72vh, 860px);
}
.map-container, .maplibre-container {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.map-status {
position: absolute;
left: 0.5rem;
@ -8,4 +22,21 @@
padding: 0.2rem 0.4rem;
background: rgba(0, 0, 0, 0.6);
border-radius: 0.5rem;
z-index: 30;
}
/* Below map area */
.below-stage{
padding: 0.25rem 0.7rem 1.0rem;
}
.below-row{
display:flex;
gap:.6rem;
align-items:stretch;
}
/* Mobile: stack */
@media (max-width: 640px){
.map-stage{ height: clamp(520px, 78vh, 900px); }
.below-row{ flex-direction: column; }
}

View File

@ -88,3 +88,548 @@
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;
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));
}
.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));
}
.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;
}
/* =========================================================
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 */
@media (max-width: 640px){
/* Shrink mini markers even more on phones */
.sw-template-mini-wrap{
--sw-mini-scale: 0.58;
}
/* 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; }
}

View File

@ -1,34 +1,39 @@
.map-overlay-wall {
position:absolute;
left:.7rem; right:.7rem; bottom:.6rem;
display:flex; gap:.6rem; align-items:stretch;
pointer-events:none; z-index:10;
/* Sociowall + Chat BELOW the map */
.below-row{
display:flex;
gap:.6rem;
align-items:stretch;
}
.map-overlay-wall * { pointer-events:auto; }
.sociowall-panel {
flex: 1.5;
min-height: 80px;
max-height: 150px;
flex: 1.8;
min-height: 160px;
background: rgba(15, 23, 42, 0.96);
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.9);
box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45);
padding: .45rem .55rem;
display:flex; flex-direction:column;
}
.sociowall-header {
font-size: .78rem; font-weight: 600;
letter-spacing: .06em; text-transform: uppercase;
color:#bfdbfe; margin-bottom:.25rem;
display:flex;
flex-direction:column;
}
.post-list { flex:1; overflow-y:auto; padding-right:.15rem; }
.post-list-header { display:flex; align-items:center; gap:.5rem; margin-bottom:.2rem; font-size:.7rem; color:#cbd5f5; }
.km-filter input[type="range"] { width:130px; }
.sociowall-header {
font-size: .78rem;
font-weight: 700;
letter-spacing: .06em;
text-transform: uppercase;
color:#bfdbfe;
margin-bottom:.25rem;
}
.post-list { flex: 1; overflow-y: auto; padding-right: .15rem; max-height: 44vh; }
.post-list-header { display:flex; align-items:center; gap:.5rem; margin-bottom:.25rem; font-size:.7rem; color:#cbd5f5; }
.km-filter input[type="range"] { width:160px; }
.status-text { font-size:.72rem; opacity:.85; margin:.15rem 0; }
.status-error { color:#fecaca; }
/* list items compact */
.post-card {
border-radius:10px;
border:1px solid rgba(55, 65, 81, 0.9);
@ -53,28 +58,41 @@
.post-card h3 { margin:0 0 .12rem 0; font-size:.8rem; }
.post-card p { margin:0; font-size:.72rem; opacity:.9; }
.post-km {
display:inline-block; margin-top:.12rem; font-size:.68rem;
padding:.05rem .35rem; border-radius:999px;
background: rgba(15, 118, 110, 0.2);
border: 1px solid rgba(45, 212, 191, 0.7);
color: #a5f3fc;
}
.chat-panel {
width:150px; min-width:130px;
max-height:150px;
width: 170px;
min-width: 170px;
background: rgba(15, 23, 42, 0.96);
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.9);
box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45);
padding:.4rem .45rem;
display:flex; flex-direction:column;
display:flex;
flex-direction:column;
/* stays visible beside wall while scrolling inside the list */
position: sticky;
top: 10px;
align-self: flex-start;
}
.chat-header {
font-size:.78rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
color:#bfdbfe; margin-bottom:.2rem;
font-size:.78rem;
font-weight:700;
letter-spacing:.06em;
text-transform:uppercase;
color:#bfdbfe;
margin-bottom:.2rem;
}
.chat-users { list-style:none; padding:0; margin:0; font-size:.76rem; }
.chat-users li { display:flex; align-items:center; gap:.3rem; padding:.18rem .1rem; }
.chat-users li::before { content:"👤"; font-size:.72rem; opacity:.9; }
/* Mobile: stack */
@media (max-width: 640px){
.below-row{ flex-direction: column; }
.chat-panel{ width: 100%; min-width: 0; position: static; }
.post-list{ max-height: 46vh; }
}
/* Guard: never render Sociowall overlay on top of the map */
.map-overlay-wall{ display:none !important; }