Add truth rail and remove duplicate modal content

This commit is contained in:
Your Name 2025-12-25 16:39:10 -05:00
parent c4b7f6cb90
commit 3d7a33b06e
2 changed files with 192 additions and 26 deletions

View File

@ -63,6 +63,16 @@ function updateStat(modalWrap, statKey, value) {
if (el) el.textContent = formatCount(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() { function isAuthed() {
try { try {
const auth = loadAuth(); const auth = loadAuth();
@ -139,6 +149,7 @@ function applyPostDetails(modalWrap, post) {
updateStat(modalWrap, "likes", likeCount); updateStat(modalWrap, "likes", likeCount);
updateStat(modalWrap, "shares", shareCount); updateStat(modalWrap, "shares", shareCount);
updateStat(modalWrap, "comments", commentCount); updateStat(modalWrap, "comments", commentCount);
updateTruth(modalWrap, post);
} }
function renderComments(container, items) { function renderComments(container, items) {
@ -162,22 +173,43 @@ function renderComments(container, items) {
.join(""); .join("");
} }
function mountCard(container, spec, data, theme) { function mountCard(container, spec, data, theme, post) {
const root = createRoot(container); const root = createRoot(container);
const tokens = cardTokens?.[theme] || cardTokens.blue; const tokens = cardTokens?.[theme] || cardTokens.blue;
const score = truthScore(post || data || {});
root.render( root.render(
React.createElement(CardRenderer, { React.createElement(
spec, "div",
data, {
themeTokens: tokens, className: "sw-card-wrap",
onAction: (action, value) => { style: { width: spec?.size?.w, height: spec?.size?.h },
if (action?.type === "open_url" && value) {
try { window.open(value, "_blank"); } catch {}
}
}, },
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 () => { 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) { function normalizeImageUrl(raw) {
const value = (raw || "").toString().trim(); const value = (raw || "").toString().trim();
if (!value) return ""; if (!value) return "";
@ -300,6 +355,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
const metaLeft = author ? `by ${author}` : ""; const metaLeft = author ? `by ${author}` : "";
const metaRight = sub ? sub : ""; const metaRight = sub ? sub : "";
const img = heroImageForPost(postData); const img = heroImageForPost(postData);
const truth = truthScore(postData);
const url = data?.url || ""; const url = data?.url || "";
const postID = postData?.id || postData?.ID || 0; const postID = postData?.id || postData?.ID || 0;
const viewCount = readCount(postData, ["view_count", "views", "viewCount"]); const viewCount = readCount(postData, ["view_count", "views", "viewCount"]);
@ -347,12 +403,22 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
<button class="sw-modal-x" type="button" aria-label="Close"></button> <button class="sw-modal-x" type="button" aria-label="Close"></button>
</div> </div>
<div class="sw-modal-hero"> <div class="sw-modal-hero-row">
${ <div class="sw-truth-rail" data-score="${truth}">
img <div class="sw-truth-rail-top">TRUE</div>
? `<img src="${escapeHtml(img)}" alt="" loading="lazy" />` <div class="sw-truth-rail-bar">
: `<div class="sw-modal-hero-fallback"><div class="sw-modal-hero-fallback-inner"><i class="${categoryIconClass(postData)}"></i><span>${escapeHtml(normCatLabel(postData))}</span></div></div>` <div class="sw-truth-rail-marker" style="top:${100 - truth}%"></div>
} </div>
<div class="sw-truth-rail-score">${truth}</div>
<div class="sw-truth-rail-bottom">FALSE</div>
</div>
<div class="sw-modal-hero">
${
img
? `<img src="${escapeHtml(img)}" alt="" loading="lazy" />`
: `<div class="sw-modal-hero-fallback"><div class="sw-modal-hero-fallback-inner"><i class="${categoryIconClass(postData)}"></i><span>${escapeHtml(normCatLabel(postData))}</span></div></div>`
}
</div>
</div> </div>
<div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div> <div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div>
@ -393,8 +459,6 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
</div> </div>
</div> </div>
<!-- (Optional) keep template renderer mounted but hidden for future -->
<div class="sw-template-full-wrap" style="display:none;"></div>
</div> </div>
`; `;
@ -595,13 +659,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
}; };
window.addEventListener("sociowire:auth-changed", onAuth); window.addEventListener("sociowire:auth-changed", onAuth);
// still mount template full (hidden) so you can switch back later instantly
let unmountFull = null; 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 = { map.__swCenteredOverlay = {
postId: postData?.id, postId: postData?.id,
@ -735,7 +793,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
if (wrap) { if (wrap) {
const spec = getTemplateSpecForPost(post, "mini"); const spec = getTemplateSpecForPost(post, "mini");
const data = adaptPostToTemplateData(post); const data = adaptPostToTemplateData(post);
root.__swUnmount = mountCard(wrap, spec, data, theme); root.__swUnmount = mountCard(wrap, spec, data, theme, post);
} }
clearOcclusion(markersRef); clearOcclusion(markersRef);

View File

@ -531,6 +531,107 @@ body[data-theme="light"] .sw-chat-bubble::before{
margin-bottom: 10px; 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{ .sw-modal-hero img{
width:100%; width:100%;
height:100%; height:100%;
@ -639,6 +740,13 @@ body[data-theme="light"] .sw-modal-hero-fallback{
width: min(96vw, 420px); width: min(96vw, 420px);
height: min(92vh, 700px); height: min(92vh, 700px);
} }
.sw-modal-hero-row{
align-items: stretch;
}
.sw-truth-rail{
width: 12px;
min-width: 12px;
}
} }
/* SW_CENTERED_MODAL_ANIM */ /* SW_CENTERED_MODAL_ANIM */