Improve map UX, theming, and privacy page

This commit is contained in:
Your Name 2025-12-27 14:50:18 -05:00
parent 55ecfbd34e
commit 909715c2d7
11 changed files with 937 additions and 265 deletions

View File

@ -17,6 +17,7 @@
<meta property="og:image" content="https://sociowire.com/icons/og-image.png" /> <meta property="og:image" content="https://sociowire.com/icons/og-image.png" />
<meta property="og:url" content="https://sociowire.com" /> <meta property="og:url" content="https://sociowire.com" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta name="facebook-domain-verification" content="o4iph92mh9fgqx58sn1b77ia08h8cp" />
<!-- Twitter Card --> <!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />

113
public/privacy.html Normal file
View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SocioWire Privacy Policy</title>
<style>
:root {
color-scheme: light;
}
body {
margin: 0;
font-family: "Georgia", "Times New Roman", serif;
background: #f8fafc;
color: #0f172a;
}
main {
max-width: 820px;
margin: 0 auto;
padding: 48px 24px 72px;
}
h1 {
font-size: 34px;
margin: 0 0 8px;
}
h2 {
font-size: 18px;
margin: 28px 0 8px;
}
p, li {
line-height: 1.6;
font-size: 16px;
}
ul {
padding-left: 20px;
}
.meta {
font-size: 14px;
color: #475569;
margin-bottom: 24px;
}
.card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 14px;
padding: 20px 22px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
}
a {
color: #1d4ed8;
}
</style>
</head>
<body>
<main>
<div class="card">
<h1>Privacy Policy</h1>
<div class="meta">Last updated: 2025-12-27</div>
<p>
SocioWire respects your privacy. This policy explains what data we collect, how we use it,
and your choices.
</p>
<h2>What we collect</h2>
<ul>
<li>Account data you provide (username, email, profile image).</li>
<li>Content you create (posts, comments, media, location selections).</li>
<li>Usage data (views, likes, shares, and basic analytics).</li>
<li>Device and browser data for security and performance.</li>
</ul>
<h2>How we use data</h2>
<ul>
<li>To operate the platform and deliver core features.</li>
<li>To personalize your feed and recommendations.</li>
<li>To improve safety, moderation, and service reliability.</li>
<li>To provide analytics for performance and growth.</li>
</ul>
<h2>Sharing</h2>
<p>
We do not sell personal data. We share data only with service providers required to run
SocioWire (hosting, analytics, storage) or when required by law.
</p>
<h2>Public vs private content</h2>
<p>
Content marked as public is visible to anyone. Private, group, or custom content is limited
to the audience you select.
</p>
<h2>Data retention</h2>
<p>
We keep data as long as necessary to provide the service. You can request deletion of your
account or content.
</p>
<h2>Your choices</h2>
<ul>
<li>Update your profile or privacy settings at any time.</li>
<li>Control who can see your posts and media.</li>
<li>Request deletion by contacting us.</li>
</ul>
<h2>Contact</h2>
<p>
Email: <a href="mailto:support@sociowire.com">support@sociowire.com</a>
</p>
</div>
</main>
</body>
</html>

View File

@ -565,11 +565,14 @@ export default function TopBar({ theme = "dark", onChangeTheme, onIslandsClick,
<button <button
type="button" type="button"
className="btn-oauth btn-oauth-facebook" className="btn-oauth btn-oauth-facebook"
disabled={true} disabled={loading}
title="Facebook SSO coming soon" onClick={() => {
trackEvent("login_provider_click", { provider: "facebook" });
loginWithProvider && loginWithProvider("facebook");
}}
> >
<i className="fa-brands fa-facebook-f"></i> <i className="fa-brands fa-facebook-f"></i>
<span>Facebook (soon)</span> <span>Continue with Facebook</span>
</button> </button>
</div> </div>
</> </>

View File

@ -134,6 +134,7 @@ export default function MapView({
const [uploadingImage, setUploadingImage] = useState(false); const [uploadingImage, setUploadingImage] = useState(false);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState(""); const [saveError, setSaveError] = useState("");
const [createStep, setCreateStep] = useState(0);
const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"]; const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"];
@ -559,6 +560,7 @@ export default function MapView({
} }
setIsCreating(true); setIsCreating(true);
setCreateStep(0);
setDraftTitle(""); setDraftTitle("");
setDraftBody(""); setDraftBody("");
setDraftImage(""); setDraftImage("");
@ -866,157 +868,189 @@ export default function MapView({
</button> </button>
</div> </div>
<span className="crosshair-label"> <div className="create-stepper">
Move the map, aim with the crosshair your wire will be pinned {["Pin", "Details", "Share"].map((label, idx) => (
there. <div
</span> key={label}
className={
<div className="create-row"> "create-step" +
<select (idx === createStep ? " create-step-current" : "") +
value={draftCategory} (idx <= createStep ? " create-step-active" : "")
onChange={(e) => { }
const cat = e.target.value; >
setDraftCategory(cat); <div className="create-step-dot">{idx + 1}</div>
setDraftSubCategory( <div className="create-step-label">{label}</div>
(CREATE_CATEGORY_MAP[cat] || CREATE_CATEGORY_MAP.News)[0] </div>
); ))}
</div>
<div className="create-step-progress">
<span
style={{
width: `${(createStep / 2) * 100}%`,
}} }}
>
{["News", "Friends", "Events", "Market"].map((cat) => (
<option key={cat}>{cat}</option>
))}
</select>
<select
value={draftSubCategory}
onChange={(e) => setDraftSubCategory(e.target.value)}
>
{(CREATE_CATEGORY_MAP[draftCategory] ||
CREATE_CATEGORY_MAP.News
).map((sub) => (
<option key={sub}>{sub}</option>
))}
</select>
</div>
<input
className="create-input"
placeholder="Short title..."
value={draftTitle}
onChange={(e) => setDraftTitle(e.target.value)}
/>
<textarea
className="create-textarea"
rows={2}
placeholder="Context (optional)..."
value={draftBody}
onChange={(e) => setDraftBody(e.target.value)}
/>
<div className="create-privacy-row">
<label>Post visibility</label>
<select
value={postVisibility}
onChange={(e) => {
const next = e.target.value;
setPostVisibility(next);
if (next !== "group") setPostGroupId("");
if (next !== "custom") setPostUsers("");
}}
>
<option value="public">Public</option>
<option value="private">Private</option>
<option value="group">Group</option>
<option value="custom">Custom</option>
</select>
</div>
{postVisibility === "group" ? (
<input
className="create-input"
placeholder="Group ID"
value={postGroupId}
onChange={(e) => setPostGroupId(e.target.value)}
/> />
) : null}
{postVisibility === "custom" ? (
<input
className="create-input"
placeholder="Usernames (comma separated)"
value={postUsers}
onChange={(e) => setPostUsers(e.target.value)}
/>
) : null}
<div className="create-image-option">
<label style={{ display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" }}>
<input
type="checkbox"
checked={useCustomImage}
onChange={(e) => setUseCustomImage(e.target.checked)}
/>
<span>Use custom image instead of category icon</span>
</label>
</div> </div>
{useCustomImage && ( {createStep === 0 && (
<div className="create-image-panel"> <div className="create-step-body">
<div style={{ display: "flex", gap: "8px" }}> <span className="crosshair-label">
<input Move the map, aim with the crosshair your wire will be pinned
type="file" there.
accept="image/*" </span>
onChange={handleImageFileChange}
disabled={uploadingImage} <div className="create-row">
style={{ flex: 1 }} <select
/> value={draftCategory}
{uploadingImage && <span>Uploading...</span>} onChange={(e) => {
const cat = e.target.value;
setDraftCategory(cat);
setDraftSubCategory(
(CREATE_CATEGORY_MAP[cat] || CREATE_CATEGORY_MAP.News)[0]
);
}}
>
{["News", "Friends", "Events", "Market"].map((cat) => (
<option key={cat}>{cat}</option>
))}
</select>
<select
value={draftSubCategory}
onChange={(e) => setDraftSubCategory(e.target.value)}
>
{(CREATE_CATEGORY_MAP[draftCategory] ||
CREATE_CATEGORY_MAP.News
).map((sub) => (
<option key={sub}>{sub}</option>
))}
</select>
</div> </div>
{draftImagePreview && (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<img
src={draftImagePreview}
alt="Preview"
style={{ width: "40px", height: "40px", objectFit: "cover", borderRadius: "6px" }}
/>
<button
type="button"
onClick={() => {
setDraftImageFile(null);
setDraftImagePreview("");
setDraftImage("");
}}
style={{ padding: "4px 8px", fontSize: "11px" }}
>
Remove
</button>
</div>
)}
<div style={{ fontSize: "12px", color: "#888" }}>
Or paste an image URL:
</div>
<input <input
className="create-input" className="create-input"
placeholder="https://example.com/image.jpg" placeholder="Short title..."
value={draftImageFile ? "" : draftImage} value={draftTitle}
onChange={(e) => { onChange={(e) => setDraftTitle(e.target.value)}
setDraftImage(e.target.value); />
setDraftImageFile(null); <div className="create-hint">Give it a clear, punchy headline.</div>
setDraftImagePreview(""); </div>
}} )}
disabled={!!draftImageFile}
{createStep === 1 && (
<div className="create-step-body">
<textarea
className="create-textarea"
rows={3}
placeholder="Context (optional)..."
value={draftBody}
onChange={(e) => setDraftBody(e.target.value)}
/> />
<div className="create-image-option">
<label className="create-checkbox">
<input
type="checkbox"
checked={useCustomImage}
onChange={(e) => setUseCustomImage(e.target.checked)}
/>
<span>Use a custom image instead of the category icon</span>
</label>
</div>
{useCustomImage && (
<div className="create-image-panel">
<div className="create-image-row">
<input
type="file"
accept="image/*"
onChange={handleImageFileChange}
disabled={uploadingImage}
/>
{uploadingImage && (
<span className="create-uploading">Uploading...</span>
)}
</div>
{draftImagePreview && (
<div className="create-image-preview-row">
<img src={draftImagePreview} alt="Preview" />
<button
type="button"
onClick={() => {
setDraftImageFile(null);
setDraftImagePreview("");
setDraftImage("");
}}
>
Remove
</button>
</div>
)}
<div className="create-muted">
Or paste an image URL:
</div>
<input
className="create-input"
placeholder="https://example.com/image.jpg"
value={draftImageFile ? "" : draftImage}
onChange={(e) => {
setDraftImage(e.target.value);
setDraftImageFile(null);
setDraftImagePreview("");
}}
disabled={!!draftImageFile}
/>
<div className="create-privacy-row">
<label>Image visibility</label>
<select
value={imageVisibility}
onChange={(e) => {
const next = e.target.value;
setImageVisibility(next);
if (next !== "group") setImageGroupId("");
if (next !== "custom") setImageUsers("");
}}
>
<option value="public">Public</option>
<option value="private">Private</option>
<option value="group">Group</option>
<option value="custom">Custom</option>
</select>
</div>
{imageVisibility === "group" ? (
<input
className="create-input"
placeholder="Group ID"
value={imageGroupId}
onChange={(e) => setImageGroupId(e.target.value)}
/>
) : null}
{imageVisibility === "custom" ? (
<input
className="create-input"
placeholder="Usernames (comma separated)"
value={imageUsers}
onChange={(e) => setImageUsers(e.target.value)}
/>
) : null}
</div>
)}
</div>
)}
{createStep === 2 && (
<div className="create-step-body">
<div className="create-privacy-row"> <div className="create-privacy-row">
<label>Image visibility</label> <label>Post visibility</label>
<select <select
value={imageVisibility} value={postVisibility}
onChange={(e) => { onChange={(e) => {
const next = e.target.value; const next = e.target.value;
setImageVisibility(next); setPostVisibility(next);
if (next !== "group") setImageGroupId(""); if (next !== "group") setPostGroupId("");
if (next !== "custom") setImageUsers(""); if (next !== "custom") setPostUsers("");
}} }}
> >
<option value="public">Public</option> <option value="public">Public</option>
@ -1025,22 +1059,48 @@ export default function MapView({
<option value="custom">Custom</option> <option value="custom">Custom</option>
</select> </select>
</div> </div>
{imageVisibility === "group" ? ( {postVisibility === "group" ? (
<input <input
className="create-input" className="create-input"
placeholder="Group ID" placeholder="Group ID"
value={imageGroupId} value={postGroupId}
onChange={(e) => setImageGroupId(e.target.value)} onChange={(e) => setPostGroupId(e.target.value)}
/> />
) : null} ) : null}
{imageVisibility === "custom" ? ( {postVisibility === "custom" ? (
<input <input
className="create-input" className="create-input"
placeholder="Usernames (comma separated)" placeholder="Usernames (comma separated)"
value={imageUsers} value={postUsers}
onChange={(e) => setImageUsers(e.target.value)} onChange={(e) => setPostUsers(e.target.value)}
/> />
) : null} ) : null}
<div className="create-review-card">
<div className="create-review-media">
{useCustomImage && (draftImagePreview || draftImage) ? (
<img src={draftImagePreview || draftImage} alt="Preview" />
) : (
<div className="create-review-icon">
<i className={getSubcatIcon(draftCategory, draftSubCategory)} />
</div>
)}
</div>
<div className="create-review-meta">
<div className="create-review-title">
{draftTitle || "Untitled wire"}
</div>
<div className="create-review-sub">
{draftCategory} · {draftSubCategory}
</div>
<div className="create-review-body">
{draftBody || "Add a quick line of context if you want."}
</div>
<div className="create-review-visibility">
Visibility: {postVisibility}
</div>
</div>
</div>
</div> </div>
)} )}
@ -1048,12 +1108,33 @@ export default function MapView({
<div className="create-actions"> <div className="create-actions">
<button <button
className="chip-pill" type="button"
onClick={handleSubmitPost} className="create-back"
disabled={isSaving} onClick={() => setCreateStep((s) => Math.max(0, s - 1))}
disabled={createStep === 0}
> >
{isSaving ? "Posting..." : "Post"} Back
</button> </button>
<div className="create-actions-right">
{createStep < 2 ? (
<button
type="button"
className="chip-pill"
onClick={() => setCreateStep((s) => Math.min(2, s + 1))}
disabled={!draftTitle.trim() || uploadingImage}
>
Next
</button>
) : (
<button
className="chip-pill"
onClick={handleSubmitPost}
disabled={isSaving}
>
{isSaving ? "Posting..." : "Post"}
</button>
)}
</div>
</div> </div>
</div> </div>
)} )}

View File

@ -6,7 +6,7 @@ const SW_ANIM_MS = 840;
import CardRenderer from "../Cards/CardRenderer"; import CardRenderer from "../Cards/CardRenderer";
import { cardTokens } from "../../theme/cardTokens"; import { cardTokens } from "../../theme/cardTokens";
import { getTemplateSpecForPost, adaptPostToTemplateData } from "./templateSpecs"; import { getTemplateSpecForPost, getTemplateKeyForPost, adaptPostToTemplateData } from "./templateSpecs";
import { getMarkerColorForCategory } from "./postPriority"; import { getMarkerColorForCategory } from "./postPriority";
import { loadAuth } from "../../auth/authStorage"; import { loadAuth } from "../../auth/authStorage";
import { import {
@ -40,6 +40,44 @@ function applyAvatar(el, username, url) {
} }
} }
function resolvePostImage(post) {
return normalizeImageUrl(
post?.image_small ||
post?.image ||
post?.image_large ||
post?.media_url ||
post?.image_url ||
""
);
}
function refreshMarkerFromPost(map, postId, fresh) {
if (!map || !fresh) return;
const ref = map.__swMarkersRef;
const list = ref?.current || [];
for (const item of list) {
if (!item || item.id !== postId) continue;
const nextPost = { ...(item.post || {}), ...(fresh || {}) };
item.post = nextPost;
if (!item.el) continue;
item.el.__post = nextPost;
const nextKey = getTemplateKeyForPost(nextPost);
const nextImg = resolvePostImage(nextPost);
const nextTitle = (nextPost?.title || "").toString();
if (
item.el.__lastTemplateKey !== nextKey ||
item.el.__lastImage !== nextImg ||
item.el.__lastTitle !== nextTitle
) {
if (item.type === "simple" && item.el.__renderSimple) {
try { item.el.__renderSimple(); } catch {}
} else if (item.el.__renderCompact) {
try { item.el.__renderCompact(); } catch {}
}
}
}
}
async function loadAvatar(username) { async function loadAvatar(username) {
const key = (username || "").toLowerCase().trim(); const key = (username || "").toLowerCase().trim();
if (!key) return ""; if (!key) return "";
@ -514,6 +552,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
let postData = { ...(post || {}) }; let postData = { ...(post || {}) };
const data = adaptPostToTemplateData(postData); const data = adaptPostToTemplateData(postData);
const cat = normCatLabel(postData); const cat = normCatLabel(postData);
const templateKey = getTemplateKeyForPost(postData);
const relTime = formatRelativeTime(getPostTime(postData)); const relTime = formatRelativeTime(getPostTime(postData));
const author = (postData?.author || postData?.Author || "").toString().trim(); const author = (postData?.author || postData?.Author || "").toString().trim();
const sub = (postData?.sub_category || postData?.subCategory || "").toString().trim(); const sub = (postData?.sub_category || postData?.subCategory || "").toString().trim();
@ -561,7 +600,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
// Keep size behavior from your CSS (max-width/min(92vw,...), max-height etc.) // Keep size behavior from your CSS (max-width/min(92vw,...), max-height etc.)
modalWrap.innerHTML = ` modalWrap.innerHTML = `
<div class="post-card sw-expanded-shell sw-modal"> <div class="post-card sw-expanded-shell sw-modal" data-template="${escapeHtml(templateKey)}" data-category="${escapeHtml((postData?.category || "").toString().toUpperCase())}">
<div class="sw-modal-toprow"> <div class="sw-modal-toprow">
<div class="sw-modal-left"> <div class="sw-modal-left">
<div class="sw-modal-badge">${escapeHtml(cat)}</div> <div class="sw-modal-badge">${escapeHtml(cat)}</div>
@ -830,6 +869,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
if (!fresh) return; if (!fresh) return;
postData = { ...postData, ...fresh }; postData = { ...postData, ...fresh };
applyPostDetails(modalWrap, postData); applyPostDetails(modalWrap, postData);
refreshMarkerFromPost(map, postID, postData);
}); });
} }
@ -1027,6 +1067,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
requestAnimationFrame(() => root.classList.add("sw-appear-in")); requestAnimationFrame(() => root.classList.add("sw-appear-in"));
root.className = "post-pin post-pin--compact"; root.className = "post-pin post-pin--compact";
root.style.zIndex = "1"; root.style.zIndex = "1";
root.__post = post;
function unmountIfAny() { function unmountIfAny() {
if (root.__swUnmount) { if (root.__swUnmount) {
@ -1053,9 +1094,13 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
const wrap = root.querySelector(".sw-template-mini-wrap"); const wrap = root.querySelector(".sw-template-mini-wrap");
if (wrap) { if (wrap) {
const spec = getTemplateSpecForPost(post, "mini"); const activePost = root.__post || post;
const data = adaptPostToTemplateData(post); const spec = getTemplateSpecForPost(activePost, "mini");
root.__swUnmount = mountCard(wrap, spec, data, theme, post); const data = adaptPostToTemplateData(activePost);
root.__lastTemplateKey = getTemplateKeyForPost(activePost);
root.__lastImage = data?.image || "";
root.__lastTitle = data?.headline || "";
root.__swUnmount = mountCard(wrap, spec, data, theme, activePost);
} }
clearOcclusion(markersRef); clearOcclusion(markersRef);
@ -1116,11 +1161,6 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe
const lngLat = [lon, lat]; const lngLat = [lon, lat];
// Pas d'offset pour les marqueurs simples (ils sont petits) // Pas d'offset pour les marqueurs simples (ils sont petits)
const color = getMarkerColorForCategory(post?.category);
const img = normalizeImageUrl(
post?.image_small || post?.image || post?.image_large || post?.media_url || "",
);
const root = document.createElement("div"); const root = document.createElement("div");
root.classList.add("sw-appear"); root.classList.add("sw-appear");
requestAnimationFrame(() => root.classList.add("sw-appear-in")); requestAnimationFrame(() => root.classList.add("sw-appear-in"));
@ -1131,73 +1171,88 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe
root.style.display = "flex"; root.style.display = "flex";
root.style.flexDirection = "column"; root.style.flexDirection = "column";
root.style.alignItems = "center"; root.style.alignItems = "center";
root.__post = post;
// Marqueur pin avec image en haut et pointe en bas function renderSimple() {
root.innerHTML = ` const activePost = root.__post || post;
<div class="sw-simple-marker" style=" const color = getMarkerColorForCategory(activePost?.category);
width: 32px; const img = normalizeImageUrl(
height: 32px; activePost?.image_small || activePost?.image || activePost?.image_large || activePost?.media_url || "",
border-radius: 50%; );
background: ${color}; root.__lastTemplateKey = getTemplateKeyForPost(activePost);
border: 2px solid white; root.__lastImage = img || "";
box-shadow: 0 2px 8px rgba(0,0,0,0.3); root.__lastTitle = (activePost?.title || "").toString();
display: flex;
align-items: center; // Marqueur pin avec image en haut et pointe en bas
justify-content: center; root.innerHTML = `
overflow: hidden; <div class="sw-simple-marker" style="
transition: transform 0.2s ease, box-shadow 0.2s ease; width: 32px;
z-index: 2; height: 32px;
border-radius: 50%;
background: ${color};
border: 2px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
z-index: 2;
">
${img ? `<img src="${img}" alt="" style="width:100%;height:100%;object-fit:cover;" />` : `
<div style="
width: 12px;
height: 12px;
border-radius: 50%;
background: white;
opacity: 0.9;
"></div>
`}
</div>
<div class="sw-simple-pin-point" style="
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 16px solid ${color};
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
margin-top: -2px;
z-index: 1;
"></div>
<div class="sw-simple-hover-card" style="
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(-6px) scale(0.9);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 1000;
min-width: 200px;
max-width: 280px;
background: rgba(8, 20, 40, 0.97);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 10px 12px;
border: 1px solid rgba(90, 190, 255, 0.7);
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
white-space: normal;
"> ">
${img ? `<img src="${img}" alt="" style="width:100%;height:100%;object-fit:cover;" />` : ` <div style="font-weight: 900; font-size: 12px; color: #e8f5ff; margin-bottom: 4px;">
<div style=" ${escapeHtml(activePost?.title || "Untitled")}
width: 12px; </div>
height: 12px; <div style="font-size: 10px; color: rgba(232, 245, 255, 0.8);">
border-radius: 50%; ${escapeHtml((activePost?.snippet || "").substring(0, 80))}${(activePost?.snippet || "").length > 80 ? "..." : ""}
background: white; </div>
opacity: 0.9; <div style="margin-top: 6px; font-size: 9px; color: rgba(232, 245, 255, 0.6);">
"></div> ${normCatLabel(activePost)} ${formatRelativeTime(activePost?.created_at || activePost?.CreatedAt)}
`} </div>
</div> </div>
<div class="sw-simple-pin-point" style=" `;
width: 0; }
height: 0;
border-left: 8px solid transparent; renderSimple();
border-right: 8px solid transparent; root.__renderSimple = renderSimple;
border-top: 16px solid ${color};
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
margin-top: -2px;
z-index: 1;
"></div>
<div class="sw-simple-hover-card" style="
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(-6px) scale(0.9);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 1000;
min-width: 200px;
max-width: 280px;
background: rgba(8, 20, 40, 0.97);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 10px 12px;
border: 1px solid rgba(90, 190, 255, 0.7);
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
white-space: normal;
">
<div style="font-weight: 900; font-size: 12px; color: #e8f5ff; margin-bottom: 4px;">
${escapeHtml(post?.title || "Untitled")}
</div>
<div style="font-size: 10px; color: rgba(232, 245, 255, 0.8);">
${escapeHtml((post?.snippet || "").substring(0, 80))}${(post?.snippet || "").length > 80 ? "..." : ""}
</div>
<div style="margin-top: 6px; font-size: 9px; color: rgba(232, 245, 255, 0.6);">
${normCatLabel(post)} ${formatRelativeTime(post?.created_at || post?.CreatedAt)}
</div>
</div>
`;
const markerEl = root.querySelector(".sw-simple-marker"); const markerEl = root.querySelector(".sw-simple-marker");
const hoverCard = root.querySelector(".sw-simple-hover-card"); const hoverCard = root.querySelector(".sw-simple-hover-card");

View File

@ -3,6 +3,7 @@ import { fetchPosts, fetchSmartFeed, fetchUnifiedSearch } from "../../api/client
import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter"; import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter";
import { getCoords, getViewFromMap, haversineKm } from "./mapGeo"; import { getCoords, getViewFromMap, haversineKm } from "./mapGeo";
import { createMarkerForPost, createSimpleMarkerForPost } from "./markerManager"; import { createMarkerForPost, createSimpleMarkerForPost } from "./markerManager";
import { getTemplateKeyForPost } from "./templateSpecs";
import { prioritizePosts } from "./postPriority"; import { prioritizePosts } from "./postPriority";
import { trackEvent } from "../../utils/analytics"; import { trackEvent } from "../../utils/analytics";
@ -38,6 +39,21 @@ function asText(v) {
return String(v); return String(v);
} }
function getTemplateKey(post) {
return getTemplateKeyForPost(post);
}
function resolvePostImage(post) {
return (
post?.image_small ||
post?.image ||
post?.image_large ||
post?.media_url ||
post?.image_url ||
""
).toString();
}
function postHaystack(p) { function postHaystack(p) {
const title = asText(p?.title || p?.text || p?.body || ""); const title = asText(p?.title || p?.text || p?.body || "");
const snippet = asText(p?.snippet || p?.summary || p?.content || ""); const snippet = asText(p?.snippet || p?.summary || p?.content || "");
@ -439,6 +455,7 @@ export function usePostsEngine({
(visible) => { (visible) => {
const map = mapRef.current; const map = mapRef.current;
if (!map) return; if (!map) return;
map.__swMarkersRef = markersRef;
// If user is reading an expanded overlay, don't touch markers // If user is reading an expanded overlay, don't touch markers
if (expandedElRef?.current) return; if (expandedElRef?.current) return;
@ -491,6 +508,27 @@ export function usePostsEngine({
try { item?.marker?.remove?.(); } catch {} try { item?.marker?.remove?.(); } catch {}
}, 840); }, 840);
} else { } else {
const entry = wanted.get(id);
if (entry?.post) {
item.post = entry.post;
if (item.el) {
item.el.__post = entry.post;
const nextKey = getTemplateKey(entry.post);
const nextImg = resolvePostImage(entry.post);
const nextTitle = (entry.post?.title || "").toString();
if (
item.el.__lastTemplateKey !== nextKey ||
item.el.__lastImage !== nextImg ||
item.el.__lastTitle !== nextTitle
) {
if (item.type === "simple" && item.el.__renderSimple) {
try { item.el.__renderSimple(); } catch {}
} else if (item.el.__renderCompact) {
try { item.el.__renderCompact(); } catch {}
}
}
}
}
next.push(item); next.push(item);
} }
} }
@ -695,7 +733,13 @@ export function usePostsEngine({
if (Array.isArray(newPosts)) { if (Array.isArray(newPosts)) {
for (const p of newPosts) { for (const p of newPosts) {
const key = getPostKey(p); const key = getPostKey(p);
if (key && !byId.has(key)) byId.set(key, p); if (!key) continue;
const prior = byId.get(key);
if (prior) {
byId.set(key, { ...prior, ...p });
} else {
byId.set(key, p);
}
} }
} }
@ -853,6 +897,12 @@ export function usePostsEngine({
const counts = payload?.counts || null; const counts = payload?.counts || null;
const truth = payload?.truth || null; const truth = payload?.truth || null;
const postPatch =
payload?.post ||
payload?.post_data ||
payload?.postData ||
payload?.data ||
null;
const truthScore = Number( const truthScore = Number(
truth?.truth_score ?? truth?.truth_score ??
truth?.TruthScore ?? truth?.TruthScore ??
@ -867,7 +917,7 @@ export function usePostsEngine({
updated.push(p); updated.push(p);
continue; continue;
} }
const next = { ...p }; const next = { ...p, ...(postPatch || {}) };
if (counts) { if (counts) {
if (Number.isFinite(counts.view_count)) next.view_count = counts.view_count; 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.like_count)) next.like_count = counts.like_count;

View File

@ -64,6 +64,14 @@
border: 1px solid rgba(56, 189, 248, 0.2); border: 1px solid rgba(56, 189, 248, 0.2);
} }
body[data-theme="blue"] .island-viewer-modal{
background: linear-gradient(135deg, rgba(3, 12, 40, 0.95) 0%, rgba(2, 8, 28, 0.98) 100%);
}
body[data-theme="blue"] .island-viewer-container{
background: linear-gradient(135deg, #0b1b3b 0%, #0f224d 60%, #163264 100%);
border-color: rgba(96, 165, 250, 0.35);
}
@keyframes slideUp { @keyframes slideUp {
from { from {
transform: translateY(20px); transform: translateY(20px);

View File

@ -145,8 +145,8 @@
bottom:8%; bottom:8%;
transform:translateX(-50%); transform:translateX(-50%);
width:92%; width:92%;
max-width:520px; max-width:560px;
max-height:45vh; max-height:62vh;
overflow-y:auto; overflow-y:auto;
background: rgba(15, 23, 42, 0.96); background: rgba(15, 23, 42, 0.96);
border-radius:16px; border-radius:16px;
@ -170,6 +170,60 @@
padding:.1rem .3rem; padding:.1rem .3rem;
cursor:pointer; cursor:pointer;
} }
.create-stepper{
display:flex;
justify-content:space-between;
align-items:center;
gap:.4rem;
margin:.3rem 0 .35rem;
}
.create-step{
display:flex;
align-items:center;
gap:.35rem;
color:#64748b;
font-size:.68rem;
text-transform:uppercase;
letter-spacing:.08em;
}
.create-step-active{
color:#e2e8f0;
}
.create-step-dot{
width:18px;
height:18px;
border-radius:50%;
display:flex;
align-items:center;
justify-content:center;
font-size:.65rem;
background:#0f172a;
border:1px solid #334155;
color:#cbd5f5;
}
.create-step-current .create-step-dot{
background:linear-gradient(120deg, #38bdf8, #22c55e);
border-color:transparent;
color:#0f172a;
font-weight:700;
}
.create-step-label{
font-weight:600;
}
.create-step-progress{
height:4px;
background:rgba(148, 163, 184, 0.15);
border-radius:999px;
overflow:hidden;
}
.create-step-progress span{
display:block;
height:100%;
background:linear-gradient(90deg, #38bdf8, #22c55e);
}
.create-step-body{
margin-top:.5rem;
}
.create-row{ .create-row{
display:flex; display:flex;
align-items:center; align-items:center;
@ -180,6 +234,11 @@
font-size:.75rem; font-size:.75rem;
color:#cbd5f5; color:#cbd5f5;
} }
.create-hint{
margin-top:.2rem;
font-size:.7rem;
color:#94a3b8;
}
.create-row select{ .create-row select{
flex:1; flex:1;
@ -210,12 +269,60 @@
resize:none; resize:none;
font-size:.78rem; font-size:.78rem;
} }
.create-image-option{
margin-top:.4rem;
}
.create-checkbox{
display:flex;
align-items:center;
gap:.5rem;
font-size:.72rem;
color:#e5e7eb;
cursor:pointer;
}
.create-image-panel{ .create-image-panel{
display:flex; display:flex;
flex-direction:column; flex-direction:column;
gap:.4rem; gap:.4rem;
margin-top:.35rem; margin-top:.35rem;
} }
.create-image-row{
display:flex;
gap:.5rem;
align-items:center;
}
.create-image-row input[type="file"]{
flex:1;
color:#e2e8f0;
}
.create-uploading{
font-size:.7rem;
color:#38bdf8;
}
.create-image-preview-row{
display:flex;
align-items:center;
gap:.5rem;
}
.create-image-preview-row img{
width:44px;
height:44px;
border-radius:8px;
object-fit:cover;
border:1px solid rgba(148, 163, 184, 0.5);
}
.create-image-preview-row button{
background:transparent;
border:1px solid rgba(148, 163, 184, 0.4);
color:#e2e8f0;
padding:.2rem .45rem;
border-radius:999px;
font-size:.68rem;
}
.create-muted{
font-size:.7rem;
color:#94a3b8;
}
.create-privacy-row{ .create-privacy-row{
display:flex; display:flex;
align-items:center; align-items:center;
@ -242,9 +349,169 @@
color:#f97373; color:#f97373;
} }
.create-actions{ .create-actions{
margin-top:.4rem; margin-top:.6rem;
display:flex; display:flex;
justify-content:flex-end; justify-content:space-between;
align-items:center;
gap:.5rem;
}
.create-actions-right{
display:flex;
gap:.45rem;
align-items:center;
}
.create-back{
background:transparent;
border:1px solid rgba(148, 163, 184, 0.4);
color:#cbd5f5;
padding:.35rem .8rem;
border-radius:999px;
font-size:.72rem;
cursor:pointer;
}
.create-back:disabled{
opacity:.45;
cursor:not-allowed;
}
.create-review-card{
display:grid;
grid-template-columns:72px 1fr;
gap:.7rem;
padding:.6rem;
margin-top:.5rem;
border-radius:12px;
border:1px solid rgba(148, 163, 184, 0.35);
background:rgba(2, 6, 23, 0.7);
}
.create-review-media{
width:72px;
height:72px;
border-radius:12px;
overflow:hidden;
background:rgba(30, 41, 59, 0.85);
display:flex;
align-items:center;
justify-content:center;
}
.create-review-media img{
width:100%;
height:100%;
object-fit:cover;
}
.create-review-icon{
width:46px;
height:46px;
border-radius:14px;
background:rgba(56, 189, 248, 0.2);
display:flex;
align-items:center;
justify-content:center;
color:#38bdf8;
font-size:1.1rem;
}
.create-review-meta{
display:flex;
flex-direction:column;
gap:.2rem;
}
.create-review-title{
font-size:.85rem;
color:#e2e8f0;
font-weight:600;
}
.create-review-sub{
font-size:.7rem;
color:#94a3b8;
}
.create-review-body{
font-size:.74rem;
color:#cbd5f5;
max-height:3.8em;
overflow:hidden;
}
.create-review-visibility{
font-size:.68rem;
color:#38bdf8;
}
body[data-theme="blue"] .create-post-panel{
background: rgba(12, 20, 60, 0.96);
border-color: rgba(96, 165, 250, 0.95);
box-shadow: 0 16px 40px rgba(2,8,26,.85), 0 0 24px rgba(56,189,248,.5);
}
body[data-theme="blue"] .create-row select,
body[data-theme="blue"] .create-input,
body[data-theme="blue"] .create-textarea,
body[data-theme="blue"] .create-privacy-row select{
background: #0b1b3b;
border-color: rgba(96, 165, 250, 0.5);
color:#e2e8f0;
}
body[data-theme="blue"] .create-step{
color:#94a3b8;
}
body[data-theme="blue"] .create-step-active{
color:#e2e8f0;
}
body[data-theme="blue"] .create-step-dot{
background:#0b1b3b;
border-color: rgba(96, 165, 250, 0.6);
color:#e2e8f0;
}
body[data-theme="blue"] .create-back{
border-color: rgba(96, 165, 250, 0.55);
color:#e2e8f0;
}
body[data-theme="blue"] .create-review-card{
background: rgba(3, 10, 28, 0.7);
border-color: rgba(96, 165, 250, 0.4);
}
body[data-theme="light"] .create-post-panel{
background: rgba(248, 250, 252, 0.98);
border-color: rgba(148, 163, 184, 0.9);
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.15);
color:#0f172a;
}
body[data-theme="light"] .create-row select,
body[data-theme="light"] .create-input,
body[data-theme="light"] .create-textarea,
body[data-theme="light"] .create-privacy-row select{
background:#ffffff;
border-color: rgba(148, 163, 184, 0.7);
color:#0f172a;
}
body[data-theme="light"] .create-step{
color:#64748b;
}
body[data-theme="light"] .create-step-active{
color:#0f172a;
}
body[data-theme="light"] .create-step-dot{
background:#ffffff;
border-color: rgba(148, 163, 184, 0.7);
color:#334155;
}
body[data-theme="light"] .create-step-current .create-step-dot{
color:#0f172a;
}
body[data-theme="light"] .create-hint,
body[data-theme="light"] .create-muted{
color:#64748b;
}
body[data-theme="light"] .create-back{
border-color: rgba(148, 163, 184, 0.7);
color:#0f172a;
}
body[data-theme="light"] .create-review-card{
background: rgba(241, 245, 249, 0.95);
border-color: rgba(148, 163, 184, 0.6);
}
body[data-theme="light"] .create-review-title{
color:#0f172a;
}
body[data-theme="light"] .create-review-body{
color:#334155;
} }
.chip-pill{ .chip-pill{

View File

@ -57,7 +57,7 @@
.sw-centered-modal .post-card.sw-expanded-shell { .sw-centered-modal .post-card.sw-expanded-shell {
background: rgba(15, 23, 42, 0.98) !important; /* transparence intacte */ background: rgba(15, 23, 42, 0.98) !important; /* transparence intacte */
border: 3px solid #000 !important; /* contour plus épais */ border: 3px solid #000 !important; /* contour plus épais */
box-shadow: 0 12px 30px rgba(0,0,0,0.7), 0 0 16px rgba(56,189,248,0.5) !important; box-shadow: 0 12px 30px rgba(0,0,0,0.7), 0 0 16px var(--sw-modal-accent-glow, rgba(56,189,248,0.5)) !important;
padding: 12px 14px; padding: 12px 14px;
color: #e5e7eb !important; /* texte blanc clair */ color: #e5e7eb !important; /* texte blanc clair */
width: min(94vw, 460px) !important; width: min(94vw, 460px) !important;
@ -70,36 +70,59 @@
gap: 8px; gap: 8px;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
--sw-modal-accent: #38bdf8;
--sw-modal-accent-soft: rgba(56,189,248,0.28);
--sw-modal-accent-glow: rgba(56,189,248,0.5);
--sw-modal-accent-text: #D7F3FF;
--sw-modal-text: #e5e7eb;
--sw-modal-meta: #9eb7ff;
--sw-modal-pill-text: #c7e3ff;
} }
/* Couleur badge par catégorie dans grosse carte */ /* Couleur principale par catégorie/template pour grosse carte */
.sw-centered-modal [data-category="NEWS"] .sw-modal-badge { .sw-centered-modal [data-template="news"]{
background: rgba(56,189,248,0.35) !important; --sw-modal-accent: #38bdf8;
border-color: #38bdf8 !important; --sw-modal-accent-soft: rgba(56,189,248,0.28);
color: #D7F3FF !important; --sw-modal-accent-glow: rgba(56,189,248,0.5);
--sw-modal-accent-text: #D7F3FF;
} }
.sw-centered-modal [data-category="EVENT"] .sw-modal-badge { .sw-centered-modal [data-template="breaking"]{
background: rgba(168,85,247,0.35) !important; --sw-modal-accent: #f43f5e;
border-color: #a855f7 !important; --sw-modal-accent-soft: rgba(244,63,94,0.26);
color: #F3E8FF !important; --sw-modal-accent-glow: rgba(244,63,94,0.5);
--sw-modal-accent-text: #FFE4E6;
} }
.sw-centered-modal [data-category="FRIENDS"] .sw-modal-badge { .sw-centered-modal [data-template="finance"]{
background: rgba(34,197,94,0.35) !important; --sw-modal-accent: #14b8a6;
border-color: #22c55e !important; --sw-modal-accent-soft: rgba(20,184,166,0.26);
color: #D1FAE5 !important; --sw-modal-accent-glow: rgba(20,184,166,0.5);
--sw-modal-accent-text: #CCFBF1;
} }
.sw-centered-modal [data-category="MARKET"] .sw-modal-badge { .sw-centered-modal [data-template="market"]{
background: rgba(250,204,21,0.35) !important; --sw-modal-accent: #facc15;
border-color: #facc15 !important; --sw-modal-accent-soft: rgba(250,204,21,0.26);
color: #FEFCE8 !important; --sw-modal-accent-glow: rgba(250,204,21,0.5);
--sw-modal-accent-text: #FEF9C3;
}
.sw-centered-modal [data-template="friends"]{
--sw-modal-accent: #22c55e;
--sw-modal-accent-soft: rgba(34,197,94,0.26);
--sw-modal-accent-glow: rgba(34,197,94,0.5);
--sw-modal-accent-text: #D1FAE5;
}
.sw-centered-modal [data-template="events"]{
--sw-modal-accent: #a855f7;
--sw-modal-accent-soft: rgba(168,85,247,0.26);
--sw-modal-accent-glow: rgba(168,85,247,0.5);
--sw-modal-accent-text: #EDE9FE;
} }
/* Hero (image en haut) + titre + stats : lisibles */ /* Hero (image en haut) + titre + stats : lisibles */
.sw-modal-hero { .sw-modal-hero {
background: rgba(15, 23, 42, 0.98) !important; /* transparence intacte */ background: radial-gradient(circle at 30% 30%, var(--sw-modal-accent-soft, rgba(56,189,248,0.18)), rgba(3,14,32,0.98)) !important;
border: 3px solid #000 !important; /* contour plus épais */ border: 3px solid #000 !important; /* contour plus épais */
box-shadow: 0 12px 30px rgba(0,0,0,0.7), 0 0 16px rgba(56,189,248,0.5) !important; box-shadow: 0 12px 30px rgba(0,0,0,0.7), 0 0 16px var(--sw-modal-accent-glow, rgba(56,189,248,0.5)) !important;
} }
.sw-centered-modal.sw-modal-fullscreen{ .sw-centered-modal.sw-modal-fullscreen{
@ -119,19 +142,21 @@
} }
.sw-modal-title, .sw-modal-title,
.sw-modal-summary, .sw-modal-summary,
.sw-stat-pill,
.sw-modal-meta,
.sw-livechat-title, .sw-livechat-title,
.sw-chat-line, .sw-chat-line,
.sw-livechat-input { .sw-livechat-input {
color: #e5e7eb !important; /* texte blanc partout */ color: var(--sw-modal-text, #e5e7eb) !important; /* texte blanc partout */
}
.sw-modal-meta{
color: var(--sw-modal-meta, #9eb7ff) !important;
} }
.sw-stat-pill { .sw-stat-pill {
color: var(--sw-modal-pill-text, #c7e3ff) !important;
background: rgba(148, 163, 184, 0.25) !important; background: rgba(148, 163, 184, 0.25) !important;
border: 1px solid rgba(148, 163, 184, 0.5) !important; border: 1px solid rgba(148, 163, 184, 0.5) !important;
} }
.sw-cta-primary { .sw-cta-primary {
background: rgba(56,189,248,0.35) !important; background: var(--sw-modal-accent-soft, rgba(56,189,248,0.35)) !important;
color: #ffffff !important; color: #ffffff !important;
} }
.sw-livechat-input { .sw-livechat-input {
@ -474,8 +499,8 @@ body[data-theme="light"] .sw-chat-bubble::before{
font-weight: 900; font-weight: 900;
font-size: 12px; font-size: 12px;
letter-spacing: .04em; letter-spacing: .04em;
color: #d7f3ff; color: var(--sw-modal-accent-text, #d7f3ff);
background: rgba(56,189,248,0.16); background: var(--sw-modal-accent-soft, rgba(56,189,248,0.16));
border: 3px solid #000 !important; /* contour plus épais */ border: 3px solid #000 !important; /* contour plus épais */
margin-bottom: 10px; margin-bottom: 10px;
} }
@ -506,8 +531,8 @@ body[data-theme="light"] .sw-chat-bubble::before{
} }
.post-card-btn{ .post-card-btn{
background: rgba(56,189,248,0.14); background: var(--sw-modal-accent-soft, rgba(56,189,248,0.14));
border: 1px solid rgba(56,189,248,0.35); border: 1px solid var(--sw-modal-accent, rgba(56,189,248,0.35));
color: #e8f5ff; color: #e8f5ff;
font-weight: 900; font-weight: 900;
padding: 6px 12px; padding: 6px 12px;
@ -539,9 +564,9 @@ body[data-theme="light"] .sw-chat-bubble::before{
font-size: 11px; font-size: 11px;
font-weight: 900; font-weight: 900;
letter-spacing: .06em; letter-spacing: .06em;
background: rgba(56,189,248,0.16); background: var(--sw-modal-accent-soft, rgba(56,189,248,0.16));
border: 3px solid #000 !important; /* contour plus épais */ border: 3px solid #000 !important; /* contour plus épais */
color:#D7F3FF; color: var(--sw-modal-accent-text, #D7F3FF);
text-transform: uppercase; text-transform: uppercase;
} }
@ -591,11 +616,46 @@ body[data-theme="light"] .sw-chat-bubble::before{
border-radius: 16px; border-radius: 16px;
overflow:hidden; overflow:hidden;
border: 3px solid #000 !important; /* contour plus épais */ border: 3px solid #000 !important; /* contour plus épais */
background: radial-gradient(circle at 30% 30%, rgba(56,189,248,0.18), rgba(3,14,32,0.98)); background: radial-gradient(circle at 30% 30%, var(--sw-modal-accent-soft, rgba(56,189,248,0.18)), rgba(3,14,32,0.98));
box-shadow: 0 10px 24px rgba(0,0,0,0.35); box-shadow: 0 10px 24px rgba(0,0,0,0.35), 0 0 18px var(--sw-modal-accent-glow, rgba(56,189,248,0.45));
margin-bottom: 10px; margin-bottom: 10px;
} }
body[data-theme="light"] .sw-centered-modal .post-card.sw-expanded-shell{
--sw-modal-text: #0f172a;
--sw-modal-meta: #64748b;
--sw-modal-pill-text: #0f172a;
--sw-modal-accent-text: #0f172a;
background: rgba(248, 250, 252, 0.98) !important;
border-color: rgba(148, 163, 184, 0.8) !important;
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12), 0 0 14px rgba(148, 163, 184, 0.5) !important;
}
body[data-theme="light"] .sw-modal-title{
color:#0f172a;
}
body[data-theme="light"] .sw-modal-summary{
color:#334155;
}
body[data-theme="light"] .sw-modal-meta{
color:#64748b;
}
body[data-theme="light"] .sw-modal-hero-fallback{
color:#0f172a;
}
body[data-theme="light"] .sw-modal-hero-fallback-inner{
border-color: rgba(148, 163, 184, 0.5);
color:#0f172a;
}
body[data-theme="light"] .sw-centered-modal .post-card.sw-expanded-shell .sw-stat-pill{
background: rgba(148, 163, 184, 0.18) !important;
border-color: rgba(148, 163, 184, 0.4) !important;
}
body[data-theme="blue"] .sw-centered-modal .post-card.sw-expanded-shell{
--sw-modal-meta: #b3ccff;
}
.sw-modal-hero-row{ .sw-modal-hero-row{
display:flex; display:flex;
gap:16px; gap:16px;
@ -881,9 +941,9 @@ body[data-theme="light"] .sw-modal-hero-fallback{
.sw-stat-pill{ .sw-stat-pill{
font-size: 11px; font-size: 11px;
color:#c7e3ff; color: var(--sw-modal-pill-text, #c7e3ff);
background: rgba(3, 14, 32, 0.58); background: rgba(3, 14, 32, 0.58);
border: 1px solid rgba(90, 190, 255, 0.22); border: 1px solid var(--sw-modal-accent, rgba(90, 190, 255, 0.22));
padding: 6px 10px; padding: 6px 10px;
border-radius: 999px; border-radius: 999px;
} }
@ -904,7 +964,7 @@ body[data-theme="light"] .sw-modal-hero-fallback{
font-size: 14px; font-size: 14px;
color:#e8f5ff; color:#e8f5ff;
cursor:pointer; cursor:pointer;
background: rgba(56,189,248,0.22); background: var(--sw-modal-accent-soft, rgba(56,189,248,0.22));
box-shadow: 0 10px 18px rgba(0,0,0,0.35); box-shadow: 0 10px 18px rgba(0,0,0,0.35);
} }
@ -972,6 +1032,20 @@ body[data-theme="light"] .sw-modal-hero-fallback{
opacity: 1.0; opacity: 1.0;
} }
body[data-theme="blue"] .sw-centered-backdrop{
background: rgba(6, 16, 46, 0.45);
}
body[data-theme="light"] .sw-centered-backdrop{
background: rgba(226, 232, 240, 0.75);
}
body[data-theme="light"] .sw-modal-x{
background: rgba(226, 232, 240, 0.9);
color:#0f172a;
border-color: rgba(148, 163, 184, 0.5);
}
/* SW_MINI_SMOOTH_MOVE */ /* SW_MINI_SMOOTH_MOVE */
.post-pin{ .post-pin{
will-change: transform; will-change: transform;

View File

@ -23,6 +23,14 @@
animation: slideUp 0.3s ease; animation: slideUp 0.3s ease;
} }
body[data-theme="blue"] .user-profile-modal{
background: rgba(3, 10, 32, 0.86);
}
body[data-theme="blue"] .user-profile-container{
background: linear-gradient(135deg, #0b1b3b 0%, #0f224d 100%);
border: 1px solid rgba(96, 165, 250, 0.35);
}
.user-profile-close { .user-profile-close {
position: absolute; position: absolute;
top: 0.75rem; top: 0.75rem;

View File

@ -30,6 +30,18 @@
border: 1px solid rgba(148, 163, 184, 0.1); border: 1px solid rgba(148, 163, 184, 0.1);
} }
body[data-theme="blue"] .island-universe-modal{
background: linear-gradient(135deg, rgba(3, 10, 32, 0.96) 0%, rgba(6, 12, 36, 0.98) 100%);
}
body[data-theme="blue"] .island-universe-container{
background: linear-gradient(135deg, #0b1b3b 0%, #0f224d 45%, #162f5c 100%);
border-color: rgba(96, 165, 250, 0.35);
box-shadow:
0 30px 100px rgba(2, 8, 26, 0.85),
0 0 0 1px rgba(96, 165, 250, 0.2),
inset 0 1px 0 0 rgba(255, 255, 255, 0.04);
}
.island-universe-close { .island-universe-close {
position: absolute; position: absolute;
top: 1rem; top: 1rem;