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