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 ( return (
<div className="map-view"> <div className="map-page">\n <div className="map-stage">
<div ref={containerRef} className="map-container" /> <div ref={containerRef} className="map-container" />
{status && <div className="map-status">{status}</div>} {status && <div className="map-status">{status}</div>}
@ -280,7 +280,12 @@ export default function MapView({ theme = "dark" }) {
</div> </div>
)} )}
<div className="map-overlay map-overlay-wall">
</div>
{/* BELOW THE MAP (page scroll) */}
<div className="below-stage">
<div className="below-row">
<div className="sociowall-panel"> <div className="sociowall-panel">
<div className="sociowall-header">Sociowall</div> <div className="sociowall-header">Sociowall</div>
<PostList <PostList
@ -291,6 +296,7 @@ export default function MapView({ theme = "dark" }) {
onSelectPost={handleSelectPost} onSelectPost={handleSelectPost}
/> />
</div> </div>
<div className="chat-panel"> <div className="chat-panel">
<div className="chat-header">Chat</div> <div className="chat-header">Chat</div>
<ul className="chat-users"> <ul className="chat-users">
@ -301,5 +307,9 @@ export default function MapView({ theme = "dark" }) {
</div> </div>
</div> </div>
</div> </div>
<div style={{ height: 24 }} />
</div>
); );
} }

View File

@ -2,17 +2,17 @@ import maplibregl from "maplibre-gl";
import React from "react"; import React from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import TemplateMarkerCard from "./TemplateMarkerCard"; import CardRenderer from "../Cards/CardRenderer";
import { getDefaultTemplateCached } from "../../api/templates"; import { cardTokens } from "../../theme/cardTokens";
import { getTemplateSpecForPost, adaptPostToTemplateData } from "./templateSpecs";
export function clearAllMarkers(markersRef, expandedElRef) { export function clearAllMarkers(markersRef, expandedElRef) {
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.__reactRoot) { if (m?.el && m.el.__swUnmount) m.el.__swUnmount();
try { m.el.__reactRoot.unmount(); } catch {} } catch {}
m.el.__reactRoot = null; try {
}
m.marker.remove(); m.marker.remove();
} catch {} } catch {}
} }
@ -72,29 +72,30 @@ export function clearOcclusion(markersRef) {
} }
} }
function escapeHtml(str) { function mountCard(container, spec, data, theme) {
return String(str) const root = createRoot(container);
.replace(/&/g, "&amp;") const tokens = cardTokens?.[theme] || cardTokens.blue;
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") root.render(
.replace(/"/g, "&quot;"); 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() { export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
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) {
const map = mapRef.current; const map = mapRef.current;
if (!map) return; if (!map) return;
@ -117,65 +118,98 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
return; return;
} }
const title = (post.title || "").trim() || "Untitled";
const root = document.createElement("div"); const root = document.createElement("div");
root.className = "post-pin post-pin--compact"; root.className = "post-pin post-pin--compact";
root.style.zIndex = "1"; root.style.zIndex = "1";
function unmountIfAny() {
if (root.__swUnmount) {
try { root.__swUnmount(); } catch {}
root.__swUnmount = null;
}
}
function renderCompact() { function renderCompact() {
unmountIfAny();
root.className = "post-pin post-pin--compact"; root.className = "post-pin post-pin--compact";
root.style.zIndex = "1"; root.style.zIndex = "1";
// pointer stays: .post-pin-pointer-small (UNCHANGED)
root.innerHTML = ` root.innerHTML = `
<div class="post-pin-bubble"> <div class="sw-template-mini-wrap"></div>
<div class="post-pin-dot"></div>
<div class="post-pin-text">${escapeHtml(title)}</div>
</div>
<div class="post-pin-pointer-small"></div> <div class="post-pin-pointer-small"></div>
`; `;
if (root.__reactRoot) { const wrap = root.querySelector(".sw-template-mini-wrap");
try { root.__reactRoot.unmount(); } catch {} if (wrap) {
root.__reactRoot = null; const spec = getTemplateSpecForPost(post, "mini");
const data = adaptPostToTemplateData(post);
root.__swUnmount = mountCard(wrap, spec, data, theme);
} }
clearOcclusion(markersRef); clearOcclusion(markersRef);
} }
async function renderExpanded() { function renderExpanded() {
unmountIfAny();
root.className = "post-pin post-pin--expanded"; root.className = "post-pin post-pin--expanded";
root.style.zIndex = "999999"; root.style.zIndex = "999999";
// pointer stays: .post-card-pointer (UNCHANGED)
const headline = escapeHtml(post?.title || "Untitled");
root.innerHTML = ` root.innerHTML = `
<div class="post-card"> <div class="post-card sw-expanded-shell">
<div class="post-card-body" style="font-size:11px; opacity:.9;">Loading template</div> <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>
<div class="post-card-pointer"></div> <div class="post-card-pointer"></div>
`; `;
const cardEl = root.querySelector(".post-card"); const wrap = root.querySelector(".sw-template-full-wrap");
if (!cardEl) return; if (wrap) {
const spec = getTemplateSpecForPost(post, "full");
const theme = currentTheme(); const data = adaptPostToTemplateData(post);
root.__swUnmount = mountCard(wrap, spec, data, theme);
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>
`;
} }
requestAnimationFrame(() => { 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"; import React from "react";
import CardRenderer from "../Cards/CardRenderer";
function getKmFromPost(post) { import { cardTokens } from "../../theme/cardTokens";
return post.km ?? post.distance_km ?? post.distanceKm ?? post.dist_km ?? null; import { getTemplateSpecForPost, adaptPostToTemplateData } from "../Map/templateSpecs";
}
export default function PostCard({ post, selected, onSelect }) { export default function PostCard({ post, selected, onSelect }) {
const title = post.title || post.name || "Post"; const spec = getTemplateSpecForPost(post, "mini");
const fullBody = post.body || post.content || post.text || ""; const data = adaptPostToTemplateData(post);
const body = fullBody.length > 80 ? fullBody.slice(0, 77) + "..." : fullBody;
const km = getKmFromPost(post);
function handleClick() {
if (onSelect) onSelect(post);
}
return ( return (
<article className={"post-card" + (selected ? " selected" : "")} onClick={handleClick}> <article
<h3>{title}</h3> className={"post-card" + (selected ? " selected" : "")}
<p>{body}</p> onClick={() => onSelect && onSelect(post)}
{km != null && <span className="post-km">{km} km</span>} 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> </article>
); );
} }

View File

@ -1,3 +1,5 @@
.app-root { height: 100vh; display: flex; flex-direction: column; } .app-root{ min-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; } /* 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-page { display: flex; flex-direction: column; }
.map-container, .maplibre-container { position: absolute; inset: 0; width: 100%; height: 100%; }
/* 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 { .map-status {
position: absolute; position: absolute;
left: 0.5rem; left: 0.5rem;
@ -8,4 +22,21 @@
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
border-radius: 0.5rem; 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); border-top:11px solid rgba(8, 20, 40, 0.97);
filter: drop-shadow(0 2px 4px rgba(0,0,0,.8)); 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 { /* Sociowall + Chat BELOW the map */
position:absolute; .below-row{
left:.7rem; right:.7rem; bottom:.6rem; display:flex;
display:flex; gap:.6rem; align-items:stretch; gap:.6rem;
pointer-events:none; z-index:10; align-items:stretch;
} }
.map-overlay-wall * { pointer-events:auto; }
.sociowall-panel { .sociowall-panel {
flex: 1.5; flex: 1.8;
min-height: 80px; min-height: 160px;
max-height: 150px;
background: rgba(15, 23, 42, 0.96); background: rgba(15, 23, 42, 0.96);
border-radius: 14px; border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.9); 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); box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45);
padding: .45rem .55rem; padding: .45rem .55rem;
display:flex; flex-direction:column; display:flex;
} flex-direction:column;
.sociowall-header {
font-size: .78rem; font-weight: 600;
letter-spacing: .06em; text-transform: uppercase;
color:#bfdbfe; margin-bottom:.25rem;
} }
.post-list { flex:1; overflow-y:auto; padding-right:.15rem; } .sociowall-header {
.post-list-header { display:flex; align-items:center; gap:.5rem; margin-bottom:.2rem; font-size:.7rem; color:#cbd5f5; } font-size: .78rem;
.km-filter input[type="range"] { width:130px; } 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-text { font-size:.72rem; opacity:.85; margin:.15rem 0; }
.status-error { color:#fecaca; } .status-error { color:#fecaca; }
/* list items compact */
.post-card { .post-card {
border-radius:10px; border-radius:10px;
border:1px solid rgba(55, 65, 81, 0.9); 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 h3 { margin:0 0 .12rem 0; font-size:.8rem; }
.post-card p { margin:0; font-size:.72rem; opacity:.9; } .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 { .chat-panel {
width:150px; min-width:130px; width: 170px;
max-height:150px; min-width: 170px;
background: rgba(15, 23, 42, 0.96); background: rgba(15, 23, 42, 0.96);
border-radius: 14px; border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.9); 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); box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45);
padding:.4rem .45rem; 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 { .chat-header {
font-size:.78rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase; font-size:.78rem;
color:#bfdbfe; margin-bottom:.2rem; 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 { 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 { display:flex; align-items:center; gap:.3rem; padding:.18rem .1rem; }
.chat-users li::before { content:"👤"; font-size:.72rem; opacity:.9; } .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; }