diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 5cbc5a7..8ec7842 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -63,6 +63,16 @@ function updateStat(modalWrap, statKey, value) { if (el) el.textContent = formatCount(value); } +function updateTruth(modalWrap, post) { + const rail = modalWrap.querySelector(".sw-truth-rail"); + if (!rail) return; + const score = truthScore(post); + const marker = rail.querySelector(".sw-truth-rail-marker"); + const label = rail.querySelector(".sw-truth-rail-score"); + if (marker) marker.style.top = `${100 - score}%`; + if (label) label.textContent = `${score}`; +} + function isAuthed() { try { const auth = loadAuth(); @@ -139,6 +149,7 @@ function applyPostDetails(modalWrap, post) { updateStat(modalWrap, "likes", likeCount); updateStat(modalWrap, "shares", shareCount); updateStat(modalWrap, "comments", commentCount); + updateTruth(modalWrap, post); } function renderComments(container, items) { @@ -162,22 +173,43 @@ function renderComments(container, items) { .join(""); } -function mountCard(container, spec, data, theme) { +function mountCard(container, spec, data, theme, post) { const root = createRoot(container); const tokens = cardTokens?.[theme] || cardTokens.blue; + const score = truthScore(post || data || {}); root.render( - React.createElement(CardRenderer, { - spec, - data, - themeTokens: tokens, - onAction: (action, value) => { - if (action?.type === "open_url" && value) { - try { window.open(value, "_blank"); } catch {} - } + React.createElement( + "div", + { + className: "sw-card-wrap", + style: { width: spec?.size?.w, height: spec?.size?.h }, }, - className: "", - }) + React.createElement(CardRenderer, { + spec, + data, + themeTokens: tokens, + onAction: (action, value) => { + if (action?.type === "open_url" && value) { + try { window.open(value, "_blank"); } catch {} + } + }, + className: "", + }), + React.createElement( + "div", + { className: "sw-truth-mini-bar" }, + React.createElement("div", { + className: "sw-truth-mini-marker", + style: { top: `${100 - score}%` }, + }) + ), + React.createElement( + "div", + { className: "sw-truth-mini-score", style: { background: truthColor(score) } }, + `${score}` + ), + ) ); return () => { @@ -249,6 +281,29 @@ function formatRelativeTime(createdAt) { } } +function truthScore(post) { + const raw = Number(post?.truth_score || post?.truthScore); + if (Number.isFinite(raw)) { + return clamp(Math.round(raw), 0, 100); + } + const seed = `${post?.id || ""}|${post?.url || ""}|${post?.title || ""}`; + let h = 0; + for (let i = 0; i < seed.length; i++) { + h = (h * 31 + seed.charCodeAt(i)) >>> 0; + } + return 35 + (h % 61); +} + +function truthColor(score) { + const s = clamp(score, 0, 100); + const hue = Math.round((s / 100) * 120); + return `hsl(${hue} 80% 42%)`; +} + +function clamp(v, min, max) { + return Math.min(Math.max(v, min), max); +} + function normalizeImageUrl(raw) { const value = (raw || "").toString().trim(); if (!value) return ""; @@ -300,6 +355,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { const metaLeft = author ? `by ${author}` : ""; const metaRight = sub ? sub : ""; const img = heroImageForPost(postData); + const truth = truthScore(postData); const url = data?.url || ""; const postID = postData?.id || postData?.ID || 0; const viewCount = readCount(postData, ["view_count", "views", "viewCount"]); @@ -347,12 +403,22 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { -
- ${ - img - ? `` - : `
${escapeHtml(normCatLabel(postData))}
` - } +
+
+
TRUE
+
+
+
+
${truth}
+
FALSE
+
+
+ ${ + img + ? `` + : `
${escapeHtml(normCatLabel(postData))}
` + } +
${escapeHtml(data?.headline || postData?.title || "Untitled")}
@@ -393,8 +459,6 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
- - `; @@ -595,13 +659,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { }; window.addEventListener("sociowire:auth-changed", onAuth); - // still mount template full (hidden) so you can switch back later instantly let unmountFull = null; - const fullWrap = modalWrap.querySelector(".sw-template-full-wrap"); - if (fullWrap) { - const spec = getTemplateSpecForPost(post, "full"); - unmountFull = mountCard(fullWrap, spec, data, theme); - } map.__swCenteredOverlay = { postId: postData?.id, @@ -735,7 +793,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the if (wrap) { const spec = getTemplateSpecForPost(post, "mini"); const data = adaptPostToTemplateData(post); - root.__swUnmount = mountCard(wrap, spec, data, theme); + root.__swUnmount = mountCard(wrap, spec, data, theme, post); } clearOcclusion(markersRef); diff --git a/src/styles/mapMarkers.css b/src/styles/mapMarkers.css index 94f6045..6c682ce 100644 --- a/src/styles/mapMarkers.css +++ b/src/styles/mapMarkers.css @@ -531,6 +531,107 @@ body[data-theme="light"] .sw-chat-bubble::before{ margin-bottom: 10px; } +.sw-modal-hero-row{ + display:flex; + gap:10px; + align-items: stretch; +} + +.sw-truth-rail{ + width: 16px; + min-width: 16px; + height: 100%; + display:flex; + flex-direction:column; + align-items:center; + justify-content:space-between; + font-size: 9px; + font-weight: 900; + letter-spacing: .08em; + text-transform: uppercase; + color:#e2e8f0; +} + +.sw-truth-rail-bar{ + flex: 1 1 auto; + width: 12px; + border-radius: 999px; + background: linear-gradient(180deg, #22c55e 0%, #facc15 50%, #ef4444 100%); + border: 2px solid rgba(0,0,0,0.45); + position: relative; + box-shadow: inset 0 0 10px rgba(0,0,0,0.35); +} + +.sw-truth-rail-marker{ + position:absolute; + left:50%; + transform: translate(-50%, -50%); + width: 20px; + height: 6px; + border-radius: 999px; + background:#0b1220; + border:2px solid #fff; + box-shadow: 0 4px 10px rgba(0,0,0,0.35); +} + +.sw-truth-rail-score{ + font-size: 11px; + font-weight: 900; + color:#fff; + background:#0b1220; + padding: 4px 6px; + border-radius: 999px; + box-shadow: 0 6px 12px rgba(0,0,0,0.35); +} + +.sw-modal-hero-row{ + display:flex; + gap:12px; + align-items: stretch; +} + +.sw-card-wrap{ + position: relative; +} + +.sw-truth-mini-bar{ + position:absolute; + left:0; + top:0; + bottom:0; + width: 8px; + background: linear-gradient(180deg, #22c55e 0%, #facc15 50%, #ef4444 100%); + border-top-left-radius: 18px; + border-bottom-left-radius: 18px; + box-shadow: inset 0 0 6px rgba(0,0,0,0.35); + pointer-events:none; +} + +.sw-truth-mini-marker{ + position:absolute; + left:50%; + transform: translate(-50%, -50%); + width: 14px; + height: 4px; + border-radius: 999px; + background:#0b1220; + border:1px solid #fff; + box-shadow: 0 3px 6px rgba(0,0,0,0.35); +} + +.sw-truth-mini-score{ + position:absolute; + right:6px; + bottom:6px; + padding: 3px 6px; + border-radius: 999px; + font-size: 10px; + font-weight: 900; + color:#fff; + letter-spacing: .03em; + pointer-events:none; +} + .sw-modal-hero img{ width:100%; height:100%; @@ -639,6 +740,13 @@ body[data-theme="light"] .sw-modal-hero-fallback{ width: min(96vw, 420px); height: min(92vh, 700px); } + .sw-modal-hero-row{ + align-items: stretch; + } + .sw-truth-rail{ + width: 12px; + min-width: 12px; + } } /* SW_CENTERED_MODAL_ANIM */