From 28a96948b4db6e8e2dcd6add4862ddebf16de807 Mon Sep 17 00:00:00 2001 From: SocioWire Date: Sat, 10 Jan 2026 23:54:40 +0000 Subject: [PATCH] Facebook-style sociowall redesign + day/night terminator default on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Redesign sociowall to be centered (max 680px) with full-width cards - Add sw-wall-body and sw-wall-footer structure to PostCard - Update posts.css with Facebook-style card layout (rounded avatars, proper spacing) - Enable day/night terminator by default with reduced opacity (0.15) - Keep weather widget at top of map πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/components/Map/MapView.jsx | 145 +++++++++++++++++++- src/components/Posts/PostCard.jsx | 22 +-- src/styles/posts.css | 214 +++++++++++++++--------------- 3 files changed, 261 insertions(+), 120 deletions(-) diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index ff963dd..70bc69c 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -5,8 +5,9 @@ import "../../styles/mapMarkers.css"; import "../../styles/posts.css"; import "../../styles/filters.css"; -import { createPost, fetchPostPreview, resolveAssetRef } from "../../api/client"; +import { createPost, fetchPostPreview, resolveAssetRef, fetchWeatherPosts } from "../../api/client"; import { LiveKitBroadcast, LiveKitViewerModal } from "../Live"; +import { WeatherModal } from "../Weather"; import { FILTER_MAIN_CATEGORIES, FILTER_CATEGORY_MAP, @@ -147,7 +148,6 @@ export default function MapView({ const [weatherViewEnabled, setWeatherViewEnabled] = useState(() => { try { const saved = localStorage.getItem(WEATHER_VIEW_KEY); - // Default to true if not set return saved === null ? true : saved === "true"; } catch { return true; @@ -197,6 +197,11 @@ export default function MapView({ const [showLiveBroadcast, setShowLiveBroadcast] = useState(false); const [activeLivePost, setActiveLivePost] = useState(null); const [liveCoords, setLiveCoords] = useState(null); + // Weather modal state + const [activeWeatherPost, setActiveWeatherPost] = useState(null); + // Weather cities widget (top left under search) + const [weatherCities, setWeatherCities] = useState([]); + const [nearestWeather, setNearestWeather] = useState(null); const openedPostRef = useRef(null); const queryPostRef = useRef(false); const overlayTimerRef = useRef(null); @@ -1206,6 +1211,82 @@ export default function MapView({ return () => map.off("movestart", handleMove); }, [mapRef, searchQuery]); + // Fetch weather cities on mount + useEffect(() => { + const loadWeatherCities = async () => { + try { + const posts = await fetchWeatherPosts(); + if (Array.isArray(posts) && posts.length > 0) { + // Parse weather data from posts + const cities = posts.map(p => { + const title = p.title || ''; + const tempMatch = title.match(/:\s*(-?\d+)Β°/); + const cityMatch = title.match(/^[^\w]*([A-Za-zΓ€-ΓΏ\s]+):/); + const temp = tempMatch ? parseInt(tempMatch[1], 10) : null; + const city = cityMatch ? cityMatch[1].trim() : ''; + + // Detect weather icon from emoji in title + let icon = '🌑️'; + if (title.includes('β˜€οΈ')) icon = 'β˜€οΈ'; + else if (title.includes('πŸŒ™')) icon = 'πŸŒ™'; + else if (title.includes('β›…')) icon = 'β›…'; + else if (title.includes('☁️')) icon = '☁️'; + else if (title.includes('🌫️')) icon = '🌫️'; + else if (title.includes('🌧️')) icon = '🌧️'; + else if (title.includes('❄️')) icon = '❄️'; + else if (title.includes('🌦️')) icon = '🌦️'; + else if (title.includes('🌨️')) icon = '🌨️'; + else if (title.includes('β›ˆοΈ')) icon = 'β›ˆοΈ'; + + return { ...p, city, temp, icon }; + }).filter(c => c.city && c.temp !== null); + + setWeatherCities(cities); + } + } catch (err) { + console.warn('[Weather] Failed to load cities:', err); + } + }; + + loadWeatherCities(); + // Refresh every 10 minutes + const interval = setInterval(loadWeatherCities, 10 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + // Find nearest weather city when map moves + useEffect(() => { + const map = mapRef.current; + if (!map || weatherCities.length === 0) return; + + const updateNearest = () => { + try { + const center = map.getCenter(); + if (!center) return; + + let nearest = null; + let minDist = Infinity; + + for (const w of weatherCities) { + if (w.lat == null || w.lon == null) continue; + const dist = Math.sqrt( + Math.pow(center.lat - w.lat, 2) + Math.pow(center.lng - w.lon, 2) + ); + if (dist < minDist) { + minDist = dist; + nearest = w; + } + } + + if (nearest) setNearestWeather(nearest); + } catch {} + }; + + updateNearest(); + map.on("idle", updateNearest); + return () => map.off("idle", updateNearest); + }, [mapRef, weatherCities]); + const handleProfileVisitIsland = (island) => { console.log('[MapView] Visiting island from profile:', island.username); setSelectedProfile(null); // Close profile modal @@ -1805,9 +1886,60 @@ export default function MapView({ > + + {/* Weather widget - single nearest city - BELOW search bar */} + {nearestWeather && (() => { + const w = nearestWeather; + const isSunny = w.icon === 'β˜€οΈ' || w.icon === 'β›…'; + const isNight = w.icon === 'πŸŒ™'; + const isRainy = w.icon === '🌧️' || w.icon === '🌦️' || w.icon === 'β›ˆοΈ'; + const isSnow = w.icon === '❄️' || w.icon === '🌨️'; + const bg = isNight + ? 'linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.9))' + : isSunny + ? 'linear-gradient(135deg, rgba(56, 189, 248, 0.85), rgba(14, 165, 233, 0.85))' + : isRainy + ? 'linear-gradient(135deg, rgba(71, 85, 105, 0.9), rgba(51, 65, 85, 0.9))' + : isSnow + ? 'linear-gradient(135deg, rgba(148, 163, 184, 0.9), rgba(203, 213, 225, 0.9))' + : 'linear-gradient(135deg, rgba(100, 116, 139, 0.85), rgba(71, 85, 105, 0.85))'; + + return ( +
setActiveWeatherPost(w)} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: '5px', + marginTop: '8px', + padding: '5px 12px', + borderRadius: '16px', + background: bg, + border: '1px solid rgba(255,255,255,0.25)', + color: isSnow ? '#1e293b' : '#fff', + fontSize: '13px', + fontWeight: '600', + cursor: 'pointer', + backdropFilter: 'blur(8px)', + boxShadow: '0 2px 8px rgba(0,0,0,0.2)', + }} + > + {w.icon} + {w.city}: + {w.temp}Β° +
+ ); + })()}
@@ -2104,6 +2236,15 @@ export default function MapView({ /> )} + {activeWeatherPost && ( + setActiveWeatherPost(null)} + /> + )}
); } diff --git a/src/components/Posts/PostCard.jsx b/src/components/Posts/PostCard.jsx index 5b5b3b3..1b299e9 100644 --- a/src/components/Posts/PostCard.jsx +++ b/src/components/Posts/PostCard.jsx @@ -398,17 +398,20 @@ export default function PostCard({ post, selected, onSelect, onPostDeleted, onPo )} -
{title}
- {snippet ?
{snippet}
: null} - {metaLine ?
{metaLine}
: null} - -
- {formatCount(counts.like)} - {formatCount(counts.comment)} - {formatCount(counts.share)} +
+
{title}
+ {snippet ?
{snippet}
: null} + {metaLine ?
{metaLine}
: null}
-
+
+
+ {formatCount(counts.like)} + {formatCount(counts.comment)} + {formatCount(counts.share)} +
+ +
{isAuthor && showPrivacy ? ( diff --git a/src/styles/posts.css b/src/styles/posts.css index 3bbf59e..c96a50c 100644 --- a/src/styles/posts.css +++ b/src/styles/posts.css @@ -1,88 +1,92 @@ -/* Sociowall + Chat BELOW the map */ +/* Sociowall BELOW the map - Full width Facebook style */ .below-row{ display:flex; - gap:.6rem; + gap:0; align-items:stretch; min-width:0; - flex-wrap: nowrap; /* βœ… keep side-by-side */ -} - -/* Small top nudge down (as requested) */ -.sociowall-panel, -.chat-panel{ - max-width: 32vw; - min-width: 160px; - flex: 1 1 0; /* βœ… 1/4 */ - margin-top: 5px; /* βœ… descend 5px */ + flex-wrap: nowrap; } .sociowall-panel{ - flex: 3 1 0; /* βœ… 3/4 */ - min-width:0; /* IMPORTANT: prevents pushing chat */ - min-height: 160px; - background: rgba(15, 23, 42, 0.96); - border-radius: 14px; - border: 1px solid rgba(148, 163, 184, 0.9); - box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45); - padding: .35rem .45rem; + flex: 1 1 100%; + width: 100%; + max-width: 680px; + margin: 0 auto; + min-height: 200px; + background: transparent; + border-radius: 0; + border: none; + box-shadow: none; + padding: 1rem 0.5rem; display:flex; flex-direction:column; - transition: max-height 220ms ease; + gap: 1rem; } .sociowall-header{ - font-size: .78rem; - font-weight: 700; - letter-spacing: .06em; + font-size: 1.1rem; + font-weight: 800; + letter-spacing: .02em; text-transform: uppercase; - color:#bfdbfe; - margin-bottom:.25rem; + color: #e2e8f0; + padding: 0 0.5rem; + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + padding-bottom: 0.75rem; } /* list scroll inside wall */ .post-list{ flex:1; - overflow-y:auto; - padding-right:.15rem; - max-height: 44vh; - transition: max-height 220ms ease; + overflow-y: visible; + display: flex; + flex-direction: column; + gap: 1rem; } -/* prevent long content from widening layout */ +/* Facebook-style post card */ .post-card{ overflow:hidden; min-width:0; - border-radius:12px; - border:1px solid rgba(51, 65, 85, 0.9); - background: rgba(15, 23, 42, 0.92); - padding:.6rem .7rem; - margin-bottom:.5rem; + border-radius: 12px; + border: none; + background: rgba(30, 41, 59, 0.95); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 0 1px rgba(148, 163, 184, 0.3); + padding: 0; cursor:pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.post-card:hover{ + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4), 0 0 1px rgba(148, 163, 184, 0.4); } .sw-wall-card{ display:flex; flex-direction:column; - gap:.4rem; + gap: 0; } .sw-wall-head{ display:flex; align-items:center; - gap:.5rem; + gap: 0.75rem; + padding: 0.85rem 1rem; } .sw-wall-avatar{ - width:34px; - height:34px; - border-radius:10px; - background: rgba(56,189,248,0.22); - border:1px solid rgba(56,189,248,0.55); - color:#e2f2ff; + width: 42px; + height: 42px; + border-radius: 50%; + background: linear-gradient(135deg, #3b82f6, #6366f1); + border: none; + color:#fff; font-weight:800; + font-size: 1rem; display:flex; align-items:center; justify-content:center; overflow: hidden; + flex-shrink: 0; } .sw-wall-avatar img{ width: 100%; @@ -92,32 +96,38 @@ } .sw-wall-meta{ flex:1; min-width:0; } .sw-wall-author{ - font-size:.85rem; - font-weight:800; - color:#e5e7eb; + font-size: 0.95rem; + font-weight: 700; + color: #f1f5f9; } .sw-wall-time{ - font-size:.72rem; + font-size: 0.8rem; color:#94a3b8; } .sw-wall-badge{ - font-size:.65rem; - font-weight:800; - padding:.2rem .5rem; - border-radius:999px; - border:1px solid rgba(56,189,248,0.5); - color:#dbeafe; - background: rgba(56,189,248,0.18); + font-size: 0.7rem; + font-weight: 700; + padding: 0.25rem 0.6rem; + border-radius: 6px; + border: none; + color: #e0f2fe; + background: rgba(14, 165, 233, 0.25); text-transform: uppercase; - letter-spacing:.06em; + letter-spacing: .04em; +} +.sw-wall-gallery{ + width: 100%; } .sw-wall-image{ width:100%; - height: 180px; - border-radius:12px; + height: 320px; + max-height: 50vh; + border-radius: 0; overflow:hidden; - border:1px solid rgba(148,163,184,0.25); - background: rgba(15,23,42,0.85); + border: none; + border-top: 1px solid rgba(148,163,184,0.1); + border-bottom: 1px solid rgba(148,163,184,0.1); + background: rgba(15,23,42,0.5); position:relative; display:flex; align-items:center; @@ -220,30 +230,43 @@ body[data-theme="light"] .sw-wall-image-fallback{ object-fit:cover; display:block; } +.sw-wall-body{ + padding: 0.85rem 1rem; +} .sw-wall-title{ - font-size: 1rem; - font-weight: 900; + font-size: 1.05rem; + font-weight: 700; color:#f8fafc; + margin-bottom: 0.35rem; + line-height: 1.35; } .sw-wall-snippet{ - font-size:.86rem; - color:#d7e3ff; - line-height:1.3; + font-size: 0.92rem; + color: #cbd5e1; + line-height: 1.45; } .sw-wall-meta-line{ - font-size:.74rem; - color:#94a3b8; + font-size: 0.8rem; + color: #64748b; + margin-top: 0.5rem; +} +.sw-wall-footer{ + padding: 0.6rem 1rem; + border-top: 1px solid rgba(148, 163, 184, 0.15); + display: flex; + align-items: center; + justify-content: space-between; } .sw-wall-stats{ display:flex; - gap:.75rem; - font-size:.75rem; - color:#cbd5f5; + gap: 1rem; + font-size: 0.85rem; + color: #94a3b8; } -.sw-wall-stats i{ margin-right:.25rem; } +.sw-wall-stats i{ margin-right: 0.35rem; } .sw-wall-actions{ display:flex; - gap:.4rem; + gap: 0.5rem; flex-wrap:wrap; } .sw-wall-privacy{ @@ -420,51 +443,24 @@ body[data-theme="light"] .sw-wall-image-fallback{ .post-list{ max-height: 46vh; } } -/* SW_LAYOUT_RATIO_FIX:BEGIN */ +/* Full width Facebook-style wall */ .below-row{ flex-wrap: nowrap !important; } .sociowall-panel{ - flex: 3 1 0 !important; /* βœ… 3/4 */ - width: auto !important; - max-width: none !important; - min-width: 0 !important; + flex: 1 1 100% !important; + width: 100% !important; + max-width: 680px !important; + margin: 0 auto !important; } -.chat-panel{ - flex: 1 1 0 !important; /* βœ… 1/4 */ - width: auto !important; - max-width: none !important; - min-width: 140px !important; -} -/* SW_LAYOUT_RATIO_FIX:END */ - -/* SW_DESKTOP_WIDE_LAYOUT:BEGIN */ -/* Big browser: Sociowall 75% / Chat 25% + centered container */ +/* Desktop: centered single column */ @media (min-width: 900px){ .below-stage{ - max-width: 1320px; - margin-left: auto; - margin-right: auto; - padding-left: 18px; - padding-right: 18px; + max-width: 100%; + padding: 1rem; } - .below-row{ gap: 0.9rem; } - - .sociowall-panel{ flex: 0 0 74%; } - .chat-panel{ - flex: 0 0 26%; - width: auto; - max-width: none; - min-width: 260px; + .sociowall-panel{ + max-width: 680px; } - - .sociowall-panel{ max-height: 22vh; } - .post-list{ max-height: 16vh; } - .chat-users{ max-height: 16vh; } - - body.sw-wall-expanded .sociowall-panel{ max-height: 70vh; } - body.sw-wall-expanded .post-list{ max-height: 62vh; } - body.sw-wall-expanded .chat-users{ max-height: 62vh; } } -/* SW_DESKTOP_WIDE_LAYOUT:END */