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);
}
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,11 +173,18 @@ 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(
"div",
{
className: "sw-card-wrap",
style: { width: spec?.size?.w, height: spec?.size?.h },
},
React.createElement(CardRenderer, {
spec,
data,
@ -177,7 +195,21 @@ function mountCard(container, spec, data, theme) {
}
},
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,6 +403,15 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
<button class="sw-modal-x" type="button" aria-label="Close"></button>
</div>
<div class="sw-modal-hero-row">
<div class="sw-truth-rail" data-score="${truth}">
<div class="sw-truth-rail-top">TRUE</div>
<div class="sw-truth-rail-bar">
<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
@ -354,6 +419,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
: `<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 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>
<!-- (Optional) keep template renderer mounted but hidden for future -->
<div class="sw-template-full-wrap" style="display:none;"></div>
</div>
`;
@ -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);

View File

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