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 */