Fix fakalyzer UI and realtime updates
This commit is contained in:
parent
b8ac7d2010
commit
55ecfbd34e
|
|
@ -246,6 +246,40 @@ export async function fetchPostById(postId) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function fetchPostTruth(postId) {
|
||||
if (!postId) return null;
|
||||
const qs = new URLSearchParams();
|
||||
qs.set("post_id", String(postId));
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/post/truth?${qs.toString()}`, {
|
||||
credentials: "include",
|
||||
headers: { ...authHeaders() },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json().catch(() => null);
|
||||
return data?.truth || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function votePostTruth(postId, vote) {
|
||||
if (!postId) throw new Error("post_id required");
|
||||
const body = JSON.stringify({ post_id: postId, vote });
|
||||
const res = await fetch(`${API_BASE}/post/truth-vote`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...authHeaders() },
|
||||
credentials: "include",
|
||||
body,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(text || "truth vote failed");
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return data?.truth || null;
|
||||
}
|
||||
|
||||
export async function updatePostVisibility(postId, payload = {}, token) {
|
||||
if (!postId) throw new Error("post_id required");
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ export default function MapView({
|
|||
const overlayTimerRef = useRef(null);
|
||||
const pendingOpenPostIdRef = useRef(null);
|
||||
const pendingOpenFullRef = useRef(false);
|
||||
const searchFitQueryRef = useRef("");
|
||||
const searchUserMovedRef = useRef(false);
|
||||
|
||||
const openOverlayWhenReady = useCallback(
|
||||
(post, opts = {}) => {
|
||||
|
|
@ -95,7 +97,7 @@ export default function MapView({
|
|||
[mapRef, theme]
|
||||
);
|
||||
|
||||
const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } =
|
||||
const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost, handlePostUpdate } =
|
||||
usePostsEngine({
|
||||
theme,
|
||||
mapRef,
|
||||
|
|
@ -312,12 +314,18 @@ export default function MapView({
|
|||
ws.onmessage = (ev) => {
|
||||
try {
|
||||
const msg = JSON.parse(ev.data);
|
||||
if (msg.type === "new_post" && msg.post) handleIncomingPost(msg.post);
|
||||
if (msg.type === "new_post" && msg.post) {
|
||||
handleIncomingPost(msg.post);
|
||||
return;
|
||||
}
|
||||
if (msg.type === "post_update") {
|
||||
handlePostUpdate(msg);
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
return () => ws && ws.close();
|
||||
}, [handleIncomingPost]);
|
||||
}, [handleIncomingPost, handlePostUpdate]);
|
||||
|
||||
const handleFlyToMe = () => {
|
||||
const map = mapRef.current;
|
||||
|
|
@ -422,13 +430,15 @@ export default function MapView({
|
|||
}
|
||||
};
|
||||
|
||||
// Zoom intelligent: quand on cherche, zoom pour voir tous les résultats
|
||||
// Zoom intelligent: quand on cherche, zoom pour voir tous les résultats (une seule fois)
|
||||
useEffect(() => {
|
||||
if (!searchQuery || !visiblePosts.length) return;
|
||||
if (skipAutoZoomRef.current) {
|
||||
skipAutoZoomRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (searchUserMovedRef.current) return;
|
||||
if (searchFitQueryRef.current === searchQuery) return;
|
||||
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
|
|
@ -472,12 +482,29 @@ export default function MapView({
|
|||
duration: 1000
|
||||
}
|
||||
);
|
||||
searchFitQueryRef.current = searchQuery;
|
||||
}
|
||||
}, 300); // Délai pour laisser les markers se créer
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery, visiblePosts, mapRef]);
|
||||
|
||||
useEffect(() => {
|
||||
searchFitQueryRef.current = "";
|
||||
searchUserMovedRef.current = false;
|
||||
}, [searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
if (!searchQuery) return;
|
||||
const handleMove = () => {
|
||||
searchUserMovedRef.current = true;
|
||||
};
|
||||
map.on("movestart", handleMove);
|
||||
return () => map.off("movestart", handleMove);
|
||||
}, [mapRef, searchQuery]);
|
||||
|
||||
const handleProfileVisitIsland = (island) => {
|
||||
console.log('[MapView] Visiting island from profile:', island.username);
|
||||
setSelectedProfile(null); // Close profile modal
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import {
|
|||
createPostComment,
|
||||
fetchPostLikeState,
|
||||
fetchProfile,
|
||||
fetchPostTruth,
|
||||
votePostTruth,
|
||||
} from "../../api/client";
|
||||
import { trackEvent } from "../../utils/analytics";
|
||||
|
||||
|
|
@ -126,6 +128,38 @@ function updateTruth(modalWrap, post) {
|
|||
if (label) label.textContent = `${score}`;
|
||||
}
|
||||
|
||||
function updateTruthVoteUI(modalWrap, stats) {
|
||||
if (!modalWrap || !stats) return;
|
||||
const aiEl = modalWrap.querySelector("[data-truth-ai]");
|
||||
const userEl = modalWrap.querySelector("[data-truth-user]");
|
||||
const countEl = modalWrap.querySelector("[data-truth-count]");
|
||||
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||
const marker = rail ? rail.querySelector(".sw-truth-rail-marker") : null;
|
||||
const label = rail ? rail.querySelector(".sw-truth-rail-score") : null;
|
||||
|
||||
const aiScore = Number.isFinite(stats.ai_score) ? Math.round(stats.ai_score) : stats.AIScore;
|
||||
const userScore = Number.isFinite(stats.user_score) ? Math.round(stats.user_score) : stats.UserScore;
|
||||
const truthScoreValue = Number.isFinite(stats.truth_score) ? Math.round(stats.truth_score) : stats.TruthScore;
|
||||
const totalVotes = Number.isFinite(stats.total_votes) ? stats.total_votes : stats.TotalVotes;
|
||||
|
||||
if (aiEl) aiEl.textContent = Number.isFinite(aiScore) ? String(aiScore) : "—";
|
||||
if (userEl) userEl.textContent = Number.isFinite(userScore) ? String(userScore) : "—";
|
||||
if (countEl) countEl.textContent = `${totalVotes || 0} votes`;
|
||||
if (marker && Number.isFinite(truthScoreValue)) marker.style.top = `${100 - truthScoreValue}%`;
|
||||
if (label && Number.isFinite(truthScoreValue)) label.textContent = `${truthScoreValue}`;
|
||||
|
||||
const vote = (stats.user_vote || stats.UserVote || "").toString();
|
||||
const btns = modalWrap.querySelectorAll(".sw-truth-vote-btn");
|
||||
btns.forEach((btn) => {
|
||||
const v = btn.getAttribute("data-truth-vote");
|
||||
btn.classList.toggle("is-active", vote && v === vote);
|
||||
});
|
||||
const railUp = modalWrap.querySelector(".sw-truth-rail-btn-up");
|
||||
const railDown = modalWrap.querySelector(".sw-truth-rail-btn-down");
|
||||
if (railUp) railUp.classList.toggle("is-active", vote === "true");
|
||||
if (railDown) railDown.classList.toggle("is-active", vote === "false");
|
||||
}
|
||||
|
||||
function isAuthed() {
|
||||
try {
|
||||
const auth = loadAuth();
|
||||
|
|
@ -272,6 +306,34 @@ function mountCard(container, spec, data, theme, post) {
|
|||
)
|
||||
);
|
||||
|
||||
const rawScore = Number(
|
||||
post?.truth_score ??
|
||||
post?.truthScore ??
|
||||
post?.truth_ai ??
|
||||
post?.truthAi ??
|
||||
post?.ai_score ??
|
||||
post?.AIScore
|
||||
);
|
||||
const hasAI = Number.isFinite(
|
||||
Number(post?.truth_ai ?? post?.truthAi ?? post?.ai_score ?? post?.AIScore)
|
||||
);
|
||||
const hasScore = Number.isFinite(rawScore) && !(rawScore === 50 && !hasAI);
|
||||
if (!hasScore && post?.id) {
|
||||
fetchPostTruth(post.id).then((stats) => {
|
||||
if (!stats) return;
|
||||
const nextScore = Number(stats.truth_score ?? stats.TruthScore);
|
||||
if (!Number.isFinite(nextScore)) return;
|
||||
post.truth_score = nextScore;
|
||||
const markerEl = container.querySelector(".sw-truth-mini-marker");
|
||||
const scoreEl = container.querySelector(".sw-truth-mini-score");
|
||||
if (markerEl) markerEl.style.top = `${100 - nextScore}%`;
|
||||
if (scoreEl) {
|
||||
scoreEl.style.background = truthColor(nextScore);
|
||||
scoreEl.textContent = `${Math.round(nextScore)}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
try { root.unmount(); } catch {}
|
||||
};
|
||||
|
|
@ -353,16 +415,18 @@ function getPostTime(post) {
|
|||
}
|
||||
|
||||
function truthScore(post) {
|
||||
const raw = Number(post?.truth_score || post?.truthScore);
|
||||
const raw = Number(
|
||||
post?.truth_score ??
|
||||
post?.truthScore ??
|
||||
post?.truth_ai ??
|
||||
post?.truthAi ??
|
||||
post?.ai_score ??
|
||||
post?.AIScore
|
||||
);
|
||||
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);
|
||||
return 50;
|
||||
}
|
||||
|
||||
function truthColor(score) {
|
||||
|
|
@ -414,6 +478,16 @@ function closeCenteredOverlay(map, opts = {}) {
|
|||
|
||||
map.__swCenteredOverlay = null;
|
||||
|
||||
try {
|
||||
const st = ov.interactionState || {};
|
||||
if (map) {
|
||||
if (st.dragPan) map.dragPan?.enable?.();
|
||||
if (st.scrollZoom) map.scrollZoom?.enable?.();
|
||||
if (st.doubleClickZoom) map.doubleClickZoom?.enable?.();
|
||||
if (st.touchZoomRotate) map.touchZoomRotate?.enable?.();
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (!skipHistory && ov.historyPushed && !ov.historyClosed) {
|
||||
ov.historyClosed = true;
|
||||
try { window.history.back(); } catch {}
|
||||
|
|
@ -426,6 +500,17 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
|
||||
closeCenteredOverlay(map, { force: true, reason: "switch", skipHistory: true });
|
||||
|
||||
const interactionState = {
|
||||
dragPan: map.dragPan && map.dragPan.isEnabled ? map.dragPan.isEnabled() : null,
|
||||
scrollZoom: map.scrollZoom && map.scrollZoom.isEnabled ? map.scrollZoom.isEnabled() : null,
|
||||
doubleClickZoom: map.doubleClickZoom && map.doubleClickZoom.isEnabled ? map.doubleClickZoom.isEnabled() : null,
|
||||
touchZoomRotate: map.touchZoomRotate && map.touchZoomRotate.isEnabled ? map.touchZoomRotate.isEnabled() : null,
|
||||
};
|
||||
try { if (map.dragPan) map.dragPan.disable(); } catch {}
|
||||
try { if (map.scrollZoom) map.scrollZoom.disable(); } catch {}
|
||||
try { if (map.doubleClickZoom) map.doubleClickZoom.disable(); } catch {}
|
||||
try { if (map.touchZoomRotate) map.touchZoomRotate.disable(); } catch {}
|
||||
|
||||
let postData = { ...(post || {}) };
|
||||
const data = adaptPostToTemplateData(postData);
|
||||
const cat = normCatLabel(postData);
|
||||
|
|
@ -494,12 +579,12 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
|
||||
<div class="sw-modal-hero-row">
|
||||
<div class="sw-truth-rail" data-score="${truth}">
|
||||
<div class="sw-truth-rail-top">TRUE</div>
|
||||
<button class="sw-truth-rail-btn sw-truth-rail-btn-up" type="button">TRUE</button>
|
||||
<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>
|
||||
<button class="sw-truth-rail-btn sw-truth-rail-btn-down" type="button">FALSE</button>
|
||||
</div>
|
||||
<div class="sw-modal-hero">
|
||||
${
|
||||
|
|
@ -509,7 +594,6 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div>
|
||||
|
||||
<div class="sw-modal-summary">
|
||||
|
|
@ -612,7 +696,11 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
closeCenteredOverlay(map, { force: true });
|
||||
});
|
||||
// inside click doesn't close
|
||||
modalWrap.addEventListener("click", (e) => e.stopPropagation());
|
||||
const stopPropagation = (e) => e.stopPropagation();
|
||||
modalWrap.addEventListener("click", stopPropagation);
|
||||
modalWrap.addEventListener("pointerdown", stopPropagation);
|
||||
modalWrap.addEventListener("mousedown", stopPropagation);
|
||||
modalWrap.addEventListener("touchstart", stopPropagation, { passive: true });
|
||||
|
||||
// close button
|
||||
const xbtn = modalWrap.querySelector(".sw-modal-x");
|
||||
|
|
@ -668,6 +756,12 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
});
|
||||
}
|
||||
|
||||
if (postID > 0) {
|
||||
fetchPostTruth(postID).then((stats) => {
|
||||
if (stats) updateTruthVoteUI(modalWrap, stats);
|
||||
});
|
||||
}
|
||||
|
||||
let liked = false;
|
||||
const likeBtn = modalWrap.querySelector('[data-action="like"]');
|
||||
const commentInput = modalWrap.querySelector(".sw-livechat-input");
|
||||
|
|
@ -784,6 +878,26 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
});
|
||||
}
|
||||
|
||||
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||
if (rail && postID > 0) {
|
||||
const doVote = async (vote) => {
|
||||
if (!isAuthed()) {
|
||||
requestAuth("Please sign in or create a free account to vote on truth.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
rail.classList.add("sw-truth-vote-disabled");
|
||||
const stats = await votePostTruth(postID, vote);
|
||||
if (stats) updateTruthVoteUI(modalWrap, stats);
|
||||
} catch {}
|
||||
rail.classList.remove("sw-truth-vote-disabled");
|
||||
};
|
||||
const voteUp = rail.querySelector(".sw-truth-rail-btn-up");
|
||||
const voteDown = rail.querySelector(".sw-truth-rail-btn-down");
|
||||
if (voteUp) voteUp.addEventListener("click", () => doVote("true"));
|
||||
if (voteDown) voteDown.addEventListener("click", () => doVote("false"));
|
||||
}
|
||||
|
||||
applyAuthUI();
|
||||
const onAuth = () => {
|
||||
if (postID > 0 && isAuthed()) {
|
||||
|
|
@ -808,6 +922,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
postId: postData?.id,
|
||||
modalWrapRef: modalWrap,
|
||||
backdrop,
|
||||
interactionState,
|
||||
onKey,
|
||||
onPop,
|
||||
onAuth,
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ export function usePostsEngine({
|
|||
);
|
||||
|
||||
const refreshVisibleAndMarkers = useCallback(
|
||||
(tf) => {
|
||||
(tf, force = false) => {
|
||||
const vAll = computeVisible(tf);
|
||||
const map = mapRef.current;
|
||||
let v = (searchQuery || "").trim() ? vAll : filterByBounds(vAll, map);
|
||||
|
|
@ -543,7 +543,10 @@ export function usePostsEngine({
|
|||
}
|
||||
|
||||
// reduce wall blink: don't update if same ids
|
||||
setVisiblePosts((prev) => (sameIdList(prev, v) ? prev : v));
|
||||
setVisiblePosts((prev) => {
|
||||
if (force) return v;
|
||||
return sameIdList(prev, v) ? prev : v;
|
||||
});
|
||||
|
||||
// keep status silent (no annoying bubbles)
|
||||
setStatus("");
|
||||
|
|
@ -843,5 +846,85 @@ export function usePostsEngine({
|
|||
[mainFilter, subFilter, timeFilter, searchQuery, mapRef, markersRef, expandedElRef, theme, refreshVisibleAndMarkers]
|
||||
);
|
||||
|
||||
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost };
|
||||
const handlePostUpdate = useCallback(
|
||||
(payload) => {
|
||||
const postId = payload?.post_id || payload?.postId || payload?.id;
|
||||
if (!postId) return;
|
||||
|
||||
const counts = payload?.counts || null;
|
||||
const truth = payload?.truth || null;
|
||||
const truthScore = Number(
|
||||
truth?.truth_score ??
|
||||
truth?.TruthScore ??
|
||||
payload?.truth_score ??
|
||||
payload?.truthScore
|
||||
);
|
||||
|
||||
const updated = [];
|
||||
let changed = false;
|
||||
for (const p of allPostsRef.current || []) {
|
||||
if ((p?.id || p?.ID) !== postId) {
|
||||
updated.push(p);
|
||||
continue;
|
||||
}
|
||||
const next = { ...p };
|
||||
if (counts) {
|
||||
if (Number.isFinite(counts.view_count)) next.view_count = counts.view_count;
|
||||
if (Number.isFinite(counts.like_count)) next.like_count = counts.like_count;
|
||||
if (Number.isFinite(counts.share_count)) next.share_count = counts.share_count;
|
||||
if (Number.isFinite(counts.comment_count)) next.comment_count = counts.comment_count;
|
||||
}
|
||||
if (Number.isFinite(truthScore)) next.truth_score = truthScore;
|
||||
updated.push(next);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
allPostsRef.current = updated;
|
||||
refreshVisibleAndMarkers(timeFilter, true);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Number.isFinite(truthScore)) {
|
||||
const markers = markersRef.current || [];
|
||||
for (const item of markers) {
|
||||
if (!item || item.id !== postId) continue;
|
||||
const el = item.el;
|
||||
if (!el) continue;
|
||||
const marker = el.querySelector(".sw-truth-mini-marker");
|
||||
const scoreEl = el.querySelector(".sw-truth-mini-score");
|
||||
if (marker) marker.style.top = `${100 - truthScore}%`;
|
||||
if (scoreEl) {
|
||||
scoreEl.style.background = `hsl(${Math.round((truthScore / 100) * 120)} 80% 42%)`;
|
||||
scoreEl.textContent = `${Math.round(truthScore)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const map = mapRef.current;
|
||||
const ov = map?.__swCenteredOverlay;
|
||||
if (ov && ov.postId === postId && ov.modalWrapRef) {
|
||||
const wrap = ov.modalWrapRef;
|
||||
if (counts) {
|
||||
const mapStat = (key, value) => {
|
||||
if (!Number.isFinite(value)) return;
|
||||
const el = wrap.querySelector(`[data-stat="${key}"] .sw-stat-num`);
|
||||
if (el) el.textContent = String(value);
|
||||
};
|
||||
mapStat("views", counts.view_count);
|
||||
mapStat("likes", counts.like_count);
|
||||
mapStat("shares", counts.share_count);
|
||||
mapStat("comments", counts.comment_count);
|
||||
}
|
||||
if (Number.isFinite(truthScore)) {
|
||||
const marker = wrap.querySelector(".sw-truth-rail-marker");
|
||||
const label = wrap.querySelector(".sw-truth-rail-score");
|
||||
if (marker) marker.style.top = `${100 - truthScore}%`;
|
||||
if (label) label.textContent = `${Math.round(truthScore)}`;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
[mapRef, refreshVisibleAndMarkers, timeFilter]
|
||||
);
|
||||
|
||||
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost, handlePostUpdate };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -598,18 +598,22 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
|||
|
||||
.sw-modal-hero-row{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
gap:16px;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.sw-truth-rail{
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
width: 22px;
|
||||
min-width: 22px;
|
||||
height: 180px;
|
||||
min-height: 180px;
|
||||
margin-left: 3px;
|
||||
margin-left: -2px;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
|
|
@ -619,6 +623,8 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
|||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color:#e2e8f0;
|
||||
pointer-events: auto;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.sw-truth-rail-bar{
|
||||
|
|
@ -629,6 +635,7 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
|||
border: 2px solid rgba(0,0,0,0.45);
|
||||
position: relative;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.35);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sw-truth-rail-marker{
|
||||
|
|
@ -651,12 +658,122 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
|||
padding: 4px 6px;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.35);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
.sw-truth-rail-btn{
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background: rgba(56,189,248,0.16);
|
||||
color:#e8f5ff;
|
||||
font-size: 9px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
padding: 6px 2px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
pointer-events: auto;
|
||||
touch-action: manipulation;
|
||||
position: relative;
|
||||
left: -8px;
|
||||
}
|
||||
|
||||
.sw-truth-rail-btn-down{
|
||||
background: rgba(239,68,68,0.18);
|
||||
}
|
||||
|
||||
.sw-truth-rail-btn.is-active{
|
||||
background: rgba(34,197,94,0.35);
|
||||
color: #eafff3;
|
||||
}
|
||||
|
||||
.sw-truth-rail-btn-down.is-active{
|
||||
background: rgba(239,68,68,0.35);
|
||||
color: #ffecec;
|
||||
}
|
||||
|
||||
.sw-truth-vote-disabled{
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sw-modal-hero-row{
|
||||
display:flex;
|
||||
gap:12px;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sw-truth-vote-row{
|
||||
display:flex;
|
||||
gap:12px;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
background: rgba(3, 14, 32, 0.55);
|
||||
border: 1px solid rgba(90, 190, 255, 0.22);
|
||||
border-radius: 14px;
|
||||
padding: 10px 12px;
|
||||
margin: 6px 0 10px;
|
||||
}
|
||||
|
||||
.sw-truth-vote-meta{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:4px;
|
||||
min-width:0;
|
||||
}
|
||||
|
||||
.sw-truth-vote-title{
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: #c7e3ff;
|
||||
}
|
||||
|
||||
.sw-truth-vote-scores{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
font-size: 12px;
|
||||
color:#e8f5ff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sw-truth-vote-count{
|
||||
font-size: 11px;
|
||||
color: rgba(215,233,255,0.7);
|
||||
}
|
||||
|
||||
.sw-truth-vote-actions{
|
||||
display:flex;
|
||||
gap:8px;
|
||||
}
|
||||
|
||||
.sw-truth-vote-btn{
|
||||
background: rgba(56,189,248,0.14);
|
||||
border: 1px solid rgba(56,189,248,0.35);
|
||||
color: #e8f5ff;
|
||||
font-weight: 900;
|
||||
padding: 6px 10px;
|
||||
border-radius: 10px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.sw-truth-vote-btn.is-active{
|
||||
background: rgba(34,197,94,0.25);
|
||||
border-color: rgba(34,197,94,0.6);
|
||||
color:#eafff3;
|
||||
}
|
||||
|
||||
.sw-truth-vote-btn[data-truth-vote="false"].is-active{
|
||||
background: rgba(239,68,68,0.25);
|
||||
border-color: rgba(239,68,68,0.6);
|
||||
color:#ffecec;
|
||||
}
|
||||
|
||||
.sw-card-wrap{
|
||||
|
|
|
|||
Loading…
Reference in New Issue