From e1956b1383ea592f11434787d16a5c124111c41f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 24 Jan 2026 23:01:29 +0000 Subject: [PATCH] v113: Enable map navigation + full-screen island terrain view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed auto-rotation, enabled drag/pan/zoom navigation - Added navigation controls (zoom buttons) - Islands placed in a grid with jitter (25° spacing) - Clicking island opens full-screen overlay with island terrain - Island shape rendered as SVG background (ocean, sand, grass, trees) - Posts displayed scattered on the island terrain Co-Authored-By: Claude Opus 4.5 --- package.json | 2 +- src/components/World/IslandsWorld.css | 27 ++-- src/components/World/IslandsWorld.jsx | 212 +++++++++++++++++--------- 3 files changed, 156 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index a58134c..9ea8c8a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sociowire-frontend", "private": true, - "version": "0.0.112", + "version": "0.0.113", "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", diff --git a/src/components/World/IslandsWorld.css b/src/components/World/IslandsWorld.css index 7c77e58..f51d760 100644 --- a/src/components/World/IslandsWorld.css +++ b/src/components/World/IslandsWorld.css @@ -366,21 +366,27 @@ } } -/* Island Terrain Overlay - Full screen when zoomed */ -.island-terrain-overlay { +/* Island Full-screen Overlay */ +.island-fullscreen-overlay { position: fixed; inset: 0; - z-index: 500; - pointer-events: none; - transition: opacity 0.3s ease; + z-index: 700; + background: #0a1628; + animation: islandFadeIn 0.4s ease; } -.island-terrain-overlay.zooming { - opacity: 0.5; +@keyframes islandFadeIn { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } } -.island-terrain-overlay.ready { - opacity: 1; +/* Full-screen island terrain SVG background */ +.island-terrain-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; } .island-terrain-header { @@ -471,9 +477,10 @@ } /* Posts container - overlay for posts on island */ -.island-terrain-posts { +.island-terrain-content { position: absolute; inset: 0; + top: 80px; pointer-events: none; } diff --git a/src/components/World/IslandsWorld.jsx b/src/components/World/IslandsWorld.jsx index 7f27494..657d0b1 100644 --- a/src/components/World/IslandsWorld.jsx +++ b/src/components/World/IslandsWorld.jsx @@ -58,33 +58,14 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl ) : islands; - // Handle zoom to island - fills the screen with the island terrain + // Handle zoom to island - shows full-screen island terrain overlay const handleZoomToIsland = useCallback(async (island) => { - if (!mapRef.current || isZooming) return; - - const pos = islandPositionsRef.current.get(island.username || island.id); - if (!pos) return; + if (isZooming) return; setIsZooming(true); setZoomedIsland(island); setLoadingPosts(true); - // Stop the rotation animation - if (animationRef.current) { - cancelAnimationFrame(animationRef.current); - animationRef.current = null; - } - - // Fly to island - zoom high enough so island fills screen - mapRef.current.flyTo({ - center: [pos.lng, pos.lat], - zoom: 16, - pitch: 0, - bearing: 0, - duration: 2000, - essential: true - }); - // Fetch posts for this island try { const posts = await fetchPosts({ username: island.username, limit: 50 }); @@ -94,42 +75,16 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl setIslandPosts([]); } setLoadingPosts(false); - - mapRef.current.once('moveend', () => { - setIsZooming(false); - }); + setIsZooming(false); }, [isZooming]); // Handle zoom out (back to world view) const handleZoomOut = useCallback(() => { - if (!mapRef.current || isZooming) return; + if (isZooming) return; - setIsZooming(true); + setZoomedIsland(null); setIslandPosts([]); - - mapRef.current.flyTo({ - center: [0, 0], - zoom: 1, - pitch: 50, - bearing: 0, - duration: 1500, - essential: true - }); - - mapRef.current.once('moveend', () => { - setZoomedIsland(null); - setIsZooming(false); - - // Restart rotation animation - let bearing = 0; - function animate() { - bearing += 0.05; - if (bearing >= 360) bearing = 0; - mapRef.current?.rotateTo(bearing, { duration: 100 }); - animationRef.current = requestAnimationFrame(animate); - } - animate(); - }); + setIsZooming(false); }, [isZooming]); // Initialize MapLibre @@ -155,15 +110,21 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl ] }, center: [centerLng, centerLat], - zoom: 1, - pitch: 50, + zoom: 2, + minZoom: 1, + maxZoom: 18, + pitch: 0, bearing: 0, antialias: true, - attributionControl: false + attributionControl: false, + dragRotate: false // Disable rotation, keep simple 2D navigation }); mapRef.current = map; + // Add navigation controls + map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'bottom-right'); + map.on('load', () => { // Add ocean gradient layer const oceanGradientData = createOceanGradient(centerLng, centerLat); @@ -195,16 +156,22 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl } }); - // Create island markers - scattered randomly across a large area + // Create island markers - spread in a grid with some randomization const markers = []; - const worldSize = 300; // Large world area + const numIslands = filteredIslands.length; + const gridSize = Math.ceil(Math.sqrt(numIslands)); + const spacing = 25; // Degrees between islands filteredIslands.forEach((island, idx) => { - // Use seeded random for consistent positions per island - const rng = seededRandom((island.username || island.id || '').charCodeAt(0) * 1000 + idx); - // Random position across the entire world - const lng = (rng() - 0.5) * worldSize; - const lat = (rng() - 0.5) * worldSize; + // Grid position with jitter + const gridX = idx % gridSize; + const gridY = Math.floor(idx / gridSize); + const rng = seededRandom((island.username || '').charCodeAt(0) || idx); + const jitterX = (rng() - 0.5) * spacing * 0.6; + const jitterY = (rng() - 0.5) * spacing * 0.6; + + const lng = (gridX - gridSize / 2) * spacing + jitterX; + const lat = (gridY - gridSize / 2) * spacing + jitterY; // Store position for zoom islandPositionsRef.current.set(island.username || island.id, { lng, lat }); @@ -239,16 +206,6 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl }); markersRef.current = markers; - - // Gentle rotation (only when not zoomed) - let bearing = 0; - function animate() { - bearing += 0.05; - if (bearing >= 360) bearing = 0; - map.rotateTo(bearing, { duration: 100 }); - animationRef.current = requestAnimationFrame(animate); - } - animate(); }); return () => { @@ -313,7 +270,10 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl {/* Zoomed island - full screen terrain view */} {zoomedIsland && ( -
+
+ {/* Full-screen island terrain background */} + + {/* Header bar */}
{/* Posts on the island terrain */} -
+
{loadingPosts && (
@@ -355,7 +315,7 @@ export default function IslandsWorld({ theme, onIslandClick, onOpenMap, onOpenPl )} {!loadingPosts && islandPosts.length === 0 && (
- +

{t('worlds.emptyIsland')}

{username && username.toLowerCase() === (zoomedIsland.username || '').toLowerCase() && (