+
+
-
-
@{zoomedIsland.username}'s Island
-
{zoomedIsland.terrain_type || 'tropical'}
+
+
@{zoomedIsland.username}
+ {zoomedIsland.terrain_type || 'tropical'}
-
-
+
+ )}
- {/* Terrain canvas with posts */}
-
- {terrainLoading ? (
-
-
-
{t('worlds.loadingTerrain')}
-
- ) : (
-
- )}
-
+ {/* Loading overlay */}
+ {loading && (
+
+
+
{t('worlds.loadingIslands')}
)}
);
}
-/**
- * Island Terrain Canvas - renders the island as a large terrain with posts
- */
-function IslandTerrainCanvas({ island, posts, isOwner }) {
- const canvasRef = useRef(null);
- const mapRef = useRef(null);
-
- const terrainColors = {
- 'tropical': { main: '#2ecc71', sand: '#F5DEB3', water: '#1e90ff' },
- 'desert': { main: '#f39c12', sand: '#EDC9AF', water: '#4aa3df' },
- 'arctic': { main: '#3498db', sand: '#E8E8E8', water: '#5fa8d3' },
- 'volcanic': { main: '#e74c3c', sand: '#2C2C2C', water: '#ff6b6b' },
- 'meadow': { main: '#27ae60', sand: '#C2B280', water: '#5dade2' },
- };
-
- const colors = terrainColors[island.terrain_type] || terrainColors['tropical'];
-
- useEffect(() => {
- if (!canvasRef.current) return;
-
- const map = new maplibregl.Map({
- container: canvasRef.current,
- style: {
- version: 8,
- sources: {},
- layers: [
- {
- id: 'background',
- type: 'background',
- paint: { 'background-color': colors.water }
- }
- ]
- },
- center: [0, 0],
- zoom: 3,
- pitch: 45,
- bearing: 0,
- attributionControl: false
- });
-
- mapRef.current = map;
-
- map.on('load', () => {
- // Create island terrain shape
- const seed = island.seed || (island.username || '').charCodeAt(0) || 42;
- const terrainData = generateTerrainGeoJSON(seed, colors.main, colors.sand);
-
- map.addSource('terrain', { type: 'geojson', data: terrainData.island });
- map.addSource('beach', { type: 'geojson', data: terrainData.beach });
-
- // Beach layer
- map.addLayer({
- id: 'beach',
- type: 'fill',
- source: 'beach',
- paint: {
- 'fill-color': colors.sand,
- 'fill-opacity': 0.9
- }
- });
-
- // Island terrain layer
- map.addLayer({
- id: 'terrain',
- type: 'fill',
- source: 'terrain',
- paint: {
- 'fill-color': colors.main,
- 'fill-opacity': 1
- }
- });
-
- // Add terrain details (trees, rocks, etc.)
- const detailsData = generateTerrainDetails(seed);
- map.addSource('details', { type: 'geojson', data: detailsData });
- map.addLayer({
- id: 'details',
- type: 'circle',
- source: 'details',
- paint: {
- 'circle-radius': ['get', 'size'],
- 'circle-color': ['get', 'color'],
- 'circle-opacity': 0.8
- }
- });
-
- // Add posts as markers on the terrain
- posts.forEach((post, idx) => {
- const angle = (idx / Math.max(posts.length, 1)) * Math.PI * 2;
- const radius = 0.01 + Math.random() * 0.02;
- const lng = Math.cos(angle) * radius;
- const lat = Math.sin(angle) * radius;
-
- const el = document.createElement('div');
- el.className = 'terrain-post-marker';
- el.innerHTML = `
-
-
-
-
${(post.title || post.caption || '').slice(0, 30)}...
- `;
- el.addEventListener('click', () => {
- window.dispatchEvent(new CustomEvent('sw:openPostDetail', { detail: { post } }));
- });
-
- new maplibregl.Marker({ element: el, anchor: 'bottom' })
- .setLngLat([lng, lat])
- .addTo(map);
- });
- });
-
- return () => {
- map.remove();
- };
- }, [island, posts, colors]);
-
- return
;
-}
-
-/**
- * Generate terrain GeoJSON for island view
- */
-function generateTerrainGeoJSON(seed, mainColor, sandColor) {
- const rng = seededRandom(seed);
- const numPoints = 24;
- const islandPoints = [];
- const beachPoints = [];
-
- for (let i = 0; i < numPoints; i++) {
- const angle = (i / numPoints) * Math.PI * 2;
- const islandRadius = 0.025 + rng() * 0.015;
- const beachRadius = islandRadius * 1.15;
-
- islandPoints.push([Math.cos(angle) * islandRadius, Math.sin(angle) * islandRadius]);
- beachPoints.push([Math.cos(angle) * beachRadius, Math.sin(angle) * beachRadius]);
- }
-
- // Close the polygons
- islandPoints.push(islandPoints[0]);
- beachPoints.push(beachPoints[0]);
-
- return {
- island: {
- type: 'Feature',
- geometry: { type: 'Polygon', coordinates: [islandPoints] }
- },
- beach: {
- type: 'Feature',
- geometry: { type: 'Polygon', coordinates: [beachPoints] }
- }
- };
-}
-
-/**
- * Generate terrain details (trees, rocks, flowers)
- */
-function generateTerrainDetails(seed) {
- const rng = seededRandom(seed);
- const features = [];
- const detailCount = 30 + Math.floor(rng() * 20);
-
- for (let i = 0; i < detailCount; i++) {
- const angle = rng() * Math.PI * 2;
- const radius = rng() * 0.02;
- const lng = Math.cos(angle) * radius;
- const lat = Math.sin(angle) * radius;
-
- const type = rng();
- let color, size;
- if (type < 0.5) {
- // Tree
- color = '#228B22';
- size = 4 + rng() * 3;
- } else if (type < 0.75) {
- // Rock
- color = '#696969';
- size = 2 + rng() * 2;
- } else {
- // Flower
- color = ['#FF69B4', '#FFD700', '#FF6347', '#9370DB'][Math.floor(rng() * 4)];
- size = 2 + rng();
- }
-
- features.push({
- type: 'Feature',
- geometry: { type: 'Point', coordinates: [lng, lat] },
- properties: { color, size }
- });
- }
-
- return { type: 'FeatureCollection', features };
-}
-
/**
* Create a stylized island marker element
*/
@@ -545,8 +359,6 @@ function createIslandMarker(island, idx, t, currentUsername) {
};
const color = terrainColors[island.terrain_type] || terrainColors['tropical'];
const seed = island.seed || idx;
-
- // Generate unique island shape
const islandSVG = generateIslandSVG(seed, color);
const postCount = island.content_count || 0;
const isOwner = currentUsername && island.username &&
@@ -585,19 +397,14 @@ function generateIslandSVG(seed, color) {
const cy = size / 2;
const numPoints = 10;
- // Generate organic shape
const points = [];
for (let i = 0; i < numPoints; i++) {
const angle = (i / numPoints) * Math.PI * 2;
const radiusVar = 0.55 + rng() * 0.45;
const r = (size / 2.5) * radiusVar;
- points.push({
- x: cx + Math.cos(angle) * r,
- y: cy + Math.sin(angle) * r
- });
+ points.push({ x: cx + Math.cos(angle) * r, y: cy + Math.sin(angle) * r });
}
- // Create smooth path
let path = `M ${points[0].x} ${points[0].y}`;
for (let i = 0; i < numPoints; i++) {
const curr = points[i];
@@ -608,7 +415,6 @@ function generateIslandSVG(seed, color) {
}
path += ' Z';
- // Add decorations
const numTrees = 3 + Math.floor(rng() * 4);
let trees = '';
for (let i = 0; i < numTrees; i++) {
@@ -635,25 +441,16 @@ function generateIslandSVG(seed, color) {
-
-
-
-
+
-
-
-
${trees}
`;
}
-/**
- * Create starfield GeoJSON
- */
function createStarfield(count) {
const features = [];
for (let i = 0; i < count; i++) {
@@ -672,9 +469,6 @@ function createStarfield(count) {
return { type: 'FeatureCollection', features };
}
-/**
- * Create ocean gradient GeoJSON
- */
function createOceanGradient(centerLng, centerLat) {
const features = [];
const rings = 10;
@@ -683,10 +477,7 @@ function createOceanGradient(centerLng, centerLat) {
const coords = [];
for (let j = 0; j <= 64; j++) {
const angle = (j / 64) * Math.PI * 2;
- coords.push([
- centerLng + Math.cos(angle) * radius,
- centerLat + Math.sin(angle) * radius
- ]);
+ coords.push([centerLng + Math.cos(angle) * radius, centerLat + Math.sin(angle) * radius]);
}
features.push({
type: 'Feature',
@@ -697,9 +488,6 @@ function createOceanGradient(centerLng, centerLat) {
return { type: 'FeatureCollection', features };
}
-/**
- * Seeded random number generator
- */
function seededRandom(seed) {
let s = seed;
return function() {
diff --git a/src/components/World/PlanetsWorld.css b/src/components/World/PlanetsWorld.css
index 29ebec0..8aebfb9 100644
--- a/src/components/World/PlanetsWorld.css
+++ b/src/components/World/PlanetsWorld.css
@@ -19,10 +19,10 @@
/* Search bar - fixed below topbar */
.planets-world-search {
position: fixed;
- top: 56px;
+ top: 52px;
left: 50%;
transform: translateX(-50%);
- z-index: 100;
+ z-index: 600;
display: flex;
align-items: center;
gap: 6px;
diff --git a/src/components/World/PlanetsWorld.jsx b/src/components/World/PlanetsWorld.jsx
index d5a326f..e79d6de 100644
--- a/src/components/World/PlanetsWorld.jsx
+++ b/src/components/World/PlanetsWorld.jsx
@@ -2,13 +2,12 @@ import React, { useEffect, useRef, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
-import { fetchPlanets } from '../../api/client';
+import { fetchPlanets, createPlanet } from '../../api/client';
import { useAuth } from '../../auth/AuthContext';
import './PlanetsWorld.css';
/**
* PlanetsWorld: Full-screen metaverse for group spaces (planets)
- * Each planet represents a community/group floating in space
*/
export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
const { t } = useTranslation();
@@ -17,16 +16,23 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
const mapRef = useRef(null);
const markersRef = useRef([]);
const animationRef = useRef(null);
+ const planetPositionsRef = useRef(new Map());
const [planets, setPlanets] = useState([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [hoveredPlanet, setHoveredPlanet] = useState(null);
- // Terrain view state
+ // Zoomed planet state
const [zoomedPlanet, setZoomedPlanet] = useState(null);
- const [planetPosts, setPlanetPosts] = useState([]);
- const [terrainLoading, setTerrainLoading] = useState(false);
+ const [isZooming, setIsZooming] = useState(false);
+
+ // Create planet modal
+ const [showCreateModal, setShowCreateModal] = useState(false);
+ const [newPlanetName, setNewPlanetName] = useState('');
+ const [newPlanetDesc, setNewPlanetDesc] = useState('');
+ const [newPlanetTheme, setNewPlanetTheme] = useState('default');
+ const [creating, setCreating] = useState(false);
// Fetch planets
useEffect(() => {
@@ -56,54 +62,101 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
)
: planets;
- // Load planet posts when zooming into terrain view
- useEffect(() => {
- if (!zoomedPlanet) {
- setPlanetPosts([]);
- return;
+ // Handle create planet
+ const handleCreatePlanet = async () => {
+ if (!newPlanetName.trim() || creating) return;
+ setCreating(true);
+
+ const result = await createPlanet({
+ name: newPlanetName.trim(),
+ description: newPlanetDesc.trim(),
+ theme: newPlanetTheme
+ });
+
+ if (result.ok || result.id) {
+ // Add new planet to list
+ setPlanets(prev => [result, ...prev]);
+ setShowCreateModal(false);
+ setNewPlanetName('');
+ setNewPlanetDesc('');
+ setNewPlanetTheme('default');
+ } else {
+ alert(result.error || 'Failed to create planet');
}
- setTerrainLoading(true);
- // TODO: Add fetchPlanetPosts API function
- // For now, simulate with empty posts
- setTimeout(() => {
- setPlanetPosts([]);
- setTerrainLoading(false);
- }, 500);
- }, [zoomedPlanet]);
+ setCreating(false);
+ };
- // Handle zoom into planet terrain
+ // Handle zoom to planet
const handleZoomToPlanet = useCallback((planet) => {
- setZoomedPlanet(planet);
- }, []);
+ if (!mapRef.current || isZooming) return;
- // Handle back from terrain view
- const handleBackFromTerrain = useCallback(() => {
- setZoomedPlanet(null);
- }, []);
+ const pos = planetPositionsRef.current.get(planet.id || planet.name);
+ if (!pos) return;
+
+ setIsZooming(true);
+ setZoomedPlanet(planet);
+
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ animationRef.current = null;
+ }
+
+ mapRef.current.flyTo({
+ center: [pos.lng, pos.lat],
+ zoom: 4,
+ pitch: 60,
+ bearing: 0,
+ duration: 1500,
+ essential: true
+ });
+
+ mapRef.current.once('moveend', () => {
+ setIsZooming(false);
+ });
+ }, [isZooming]);
+
+ // Handle zoom out
+ const handleZoomOut = useCallback(() => {
+ if (!mapRef.current || isZooming) return;
+
+ setIsZooming(true);
+
+ mapRef.current.flyTo({
+ center: [0, 0],
+ zoom: 0.8,
+ pitch: 45,
+ bearing: 0,
+ duration: 1500,
+ essential: true
+ });
+
+ mapRef.current.once('moveend', () => {
+ setZoomedPlanet(null);
+ setIsZooming(false);
+
+ let bearing = 0;
+ function animate() {
+ bearing += 0.03;
+ if (bearing >= 360) bearing = 0;
+ mapRef.current?.rotateTo(bearing, { duration: 100 });
+ animationRef.current = requestAnimationFrame(animate);
+ }
+ animate();
+ });
+ }, [isZooming]);
// Initialize MapLibre
useEffect(() => {
if (!containerRef.current || loading) return;
- const centerLng = 0;
- const centerLat = 0;
-
const map = new maplibregl.Map({
container: containerRef.current,
style: {
version: 8,
sources: {},
- layers: [
- {
- id: 'background',
- type: 'background',
- paint: {
- 'background-color': '#050510' // Deep space black
- }
- }
- ]
+ layers: [{ id: 'background', type: 'background', paint: { 'background-color': '#050510' } }]
},
- center: [centerLng, centerLat],
+ center: [0, 0],
zoom: 0.8,
pitch: 45,
bearing: 0,
@@ -114,26 +167,19 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
mapRef.current = map;
map.on('load', () => {
- // Add nebula effect
+ // Nebula effect
const nebulaData = createNebulaEffect();
map.addSource('nebula', { type: 'geojson', data: nebulaData });
map.addLayer({
- id: 'nebula',
- type: 'fill',
- source: 'nebula',
- paint: {
- 'fill-color': ['get', 'color'],
- 'fill-opacity': ['get', 'opacity']
- }
+ id: 'nebula', type: 'fill', source: 'nebula',
+ paint: { 'fill-color': ['get', 'color'], 'fill-opacity': ['get', 'opacity'] }
});
- // Add starfield
+ // Starfield
const starsData = createStarfield(800);
map.addSource('stars', { type: 'geojson', data: starsData });
map.addLayer({
- id: 'stars',
- type: 'circle',
- source: 'stars',
+ id: 'stars', type: 'circle', source: 'stars',
paint: {
'circle-radius': ['interpolate', ['linear'], ['zoom'], 0, 0.8, 5, 2.5],
'circle-color': ['get', 'color'],
@@ -142,20 +188,19 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
}
});
- // Create planet markers in orbital rings
+ // Planet markers
const markers = [];
-
filteredPlanets.forEach((planet, idx) => {
const ring = Math.floor(idx / 6);
const posInRing = idx % 6;
const baseRadius = 25 + ring * 30;
const angle = (posInRing / 6) * Math.PI * 2 + (ring * 0.5);
+ const lng = Math.cos(angle) * baseRadius;
+ const lat = Math.sin(angle) * baseRadius;
- const lng = centerLng + Math.cos(angle) * baseRadius;
- const lat = centerLat + Math.sin(angle) * baseRadius;
-
- const el = createPlanetMarker(planet, idx, t);
+ planetPositionsRef.current.set(planet.id || planet.name, { lng, lat });
+ const el = createPlanetMarker(planet, idx);
el.addEventListener('mouseenter', () => setHoveredPlanet(planet));
el.addEventListener('mouseleave', () => setHoveredPlanet(null));
el.addEventListener('click', (e) => {
@@ -163,10 +208,7 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
handleZoomToPlanet(planet);
});
- const marker = new maplibregl.Marker({
- element: el,
- anchor: 'center'
- })
+ const marker = new maplibregl.Marker({ element: el, anchor: 'center' })
.setLngLat([lng, lat])
.addTo(map);
@@ -175,7 +217,7 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
markersRef.current = markers;
- // Slow cosmic rotation
+ // Rotation
let bearing = 0;
function animate() {
bearing += 0.03;
@@ -190,13 +232,14 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
if (animationRef.current) cancelAnimationFrame(animationRef.current);
markersRef.current.forEach(({ marker }) => marker.remove());
markersRef.current = [];
+ planetPositionsRef.current.clear();
map.remove();
};
- }, [filteredPlanets, loading, t, handleZoomToPlanet]);
+ }, [filteredPlanets, loading, handleZoomToPlanet]);
return (
- {/* Search bar with icon and world nav */}
+ {/* Search bar */}
@@ -222,17 +265,17 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
- {/* Main canvas */}
+ {/* Canvas */}
- {/* Hovered planet tooltip */}
+ {/* Tooltip */}
{hoveredPlanet && !zoomedPlanet && (
-
{hoveredPlanet.name || 'Unknown Planet'}
+
{hoveredPlanet.name || 'Planet'}
{hoveredPlanet.description || t('worlds.clickToExplore')}
{hoveredPlanet.member_count || 0} {t('worlds.members')} • {hoveredPlanet.theme || 'default'}
@@ -241,243 +284,98 @@ export default function PlanetsWorld({ theme, onOpenMap, onOpenIslands }) {
)}
- {/* Loading overlay */}
- {loading && (
-
-
-
{t('worlds.loadingPlanets')}
-
- )}
-
- {/* Planet Terrain View - Full screen when zoomed */}
- {zoomedPlanet && (
-
-
-
+ {/* Zoomed planet overlay */}
+ {zoomedPlanet && !isZooming && (
+
+
+
{t('worlds.backToPlanets')}
-
-
{zoomedPlanet.name || 'Planet'}
-
{zoomedPlanet.theme || 'default'}
+
+
{zoomedPlanet.name}
+ {zoomedPlanet.theme || 'default'}
-
-
+
+
{zoomedPlanet.member_count || 0}
{authenticated && (
-
+
{t('worlds.postHere')}
)}
+
+ )}
- {/* Terrain canvas with posts */}
-
- {terrainLoading ? (
-
-
-
{t('worlds.loadingTerrain')}
-
- ) : (
-
setShowCreateModal(true)}>
+
+ {t('worlds.createPlanet')}
+
+ )}
+
+ {/* Create Planet Modal */}
+ {showCreateModal && (
+ setShowCreateModal(false)}>
+
e.stopPropagation()}>
+
setShowCreateModal(false)}>
+
+
+
{t('worlds.createPlanet')}
+
+ setNewPlanetName(e.target.value)}
+ maxLength={50}
/>
- )}
+
)}
+
+ {/* Loading */}
+ {loading && (
+
+
+
{t('worlds.loadingPlanets')}
+
+ )}
);
}
-/**
- * Planet Terrain Canvas - renders the planet as a large terrain with posts
- */
-function PlanetTerrainCanvas({ planet, posts }) {
- const canvasRef = useRef(null);
- const mapRef = useRef(null);
-
- const themeColors = {
- 'default': { main: '#9b59b6', accent: '#8e44ad', bg: '#1a0a2e' },
- 'tech': { main: '#3498db', accent: '#2980b9', bg: '#0a1a2e' },
- 'gaming': { main: '#e74c3c', accent: '#c0392b', bg: '#2e0a0a' },
- 'music': { main: '#1abc9c', accent: '#16a085', bg: '#0a2e2a' },
- 'art': { main: '#e91e63', accent: '#c2185b', bg: '#2e0a1a' },
- 'science': { main: '#00bcd4', accent: '#0097a7', bg: '#0a2e2e' },
- 'sports': { main: '#ff9800', accent: '#f57c00', bg: '#2e1a0a' },
- };
-
- const colors = themeColors[planet.theme] || themeColors['default'];
-
- useEffect(() => {
- if (!canvasRef.current) return;
-
- const map = new maplibregl.Map({
- container: canvasRef.current,
- style: {
- version: 8,
- sources: {},
- layers: [
- {
- id: 'background',
- type: 'background',
- paint: { 'background-color': colors.bg }
- }
- ]
- },
- center: [0, 0],
- zoom: 3,
- pitch: 45,
- bearing: 0,
- attributionControl: false
- });
-
- mapRef.current = map;
-
- map.on('load', () => {
- // Create planet surface with craters/features
- const seed = planet.id || (planet.name || '').charCodeAt(0) || 42;
- const surfaceData = generatePlanetSurface(seed, colors.main, colors.accent);
-
- map.addSource('surface', { type: 'geojson', data: surfaceData.terrain });
- map.addSource('features', { type: 'geojson', data: surfaceData.features });
-
- // Planet surface
- map.addLayer({
- id: 'surface',
- type: 'fill',
- source: 'surface',
- paint: {
- 'fill-color': colors.main,
- 'fill-opacity': 1
- }
- });
-
- // Surface features (craters, mountains)
- map.addLayer({
- id: 'features',
- type: 'circle',
- source: 'features',
- paint: {
- 'circle-radius': ['get', 'size'],
- 'circle-color': ['get', 'color'],
- 'circle-opacity': ['get', 'opacity']
- }
- });
-
- // Add posts as markers on the terrain
- posts.forEach((post, idx) => {
- const angle = (idx / Math.max(posts.length, 1)) * Math.PI * 2;
- const radius = 0.01 + Math.random() * 0.02;
- const lng = Math.cos(angle) * radius;
- const lat = Math.sin(angle) * radius;
-
- const el = document.createElement('div');
- el.className = 'terrain-post-marker planet-post-marker';
- el.innerHTML = `
-
-
-
-
${(post.title || post.caption || '').slice(0, 30)}...
- `;
- el.addEventListener('click', () => {
- window.dispatchEvent(new CustomEvent('sw:openPostDetail', { detail: { post } }));
- });
-
- new maplibregl.Marker({ element: el, anchor: 'bottom' })
- .setLngLat([lng, lat])
- .addTo(map);
- });
- });
-
- return () => {
- map.remove();
- };
- }, [planet, posts, colors]);
-
- return
;
-}
-
-/**
- * Generate planet surface GeoJSON
- */
-function generatePlanetSurface(seed, mainColor, accentColor) {
- const rng = seededRandom(seed);
- const numPoints = 32;
- const surfacePoints = [];
-
- for (let i = 0; i < numPoints; i++) {
- const angle = (i / numPoints) * Math.PI * 2;
- const radius = 0.03 + rng() * 0.01;
- surfacePoints.push([Math.cos(angle) * radius, Math.sin(angle) * radius]);
- }
- surfacePoints.push(surfacePoints[0]);
-
- // Generate craters and features
- const features = [];
- const featureCount = 20 + Math.floor(rng() * 15);
-
- for (let i = 0; i < featureCount; i++) {
- const angle = rng() * Math.PI * 2;
- const radius = rng() * 0.025;
- const lng = Math.cos(angle) * radius;
- const lat = Math.sin(angle) * radius;
-
- const type = rng();
- let color, size, opacity;
- if (type < 0.4) {
- // Crater
- color = accentColor;
- size = 3 + rng() * 4;
- opacity = 0.6;
- } else if (type < 0.7) {
- // Mountain/ridge
- color = mainColor;
- size = 2 + rng() * 3;
- opacity = 0.8;
- } else {
- // Glowing spot
- color = '#ffffff';
- size = 1 + rng() * 2;
- opacity = 0.4;
- }
-
- features.push({
- type: 'Feature',
- geometry: { type: 'Point', coordinates: [lng, lat] },
- properties: { color, size, opacity }
- });
- }
-
- return {
- terrain: {
- type: 'Feature',
- geometry: { type: 'Polygon', coordinates: [surfacePoints] }
- },
- features: { type: 'FeatureCollection', features }
- };
-}
-
-/**
- * Seeded random number generator
- */
-function seededRandom(seed) {
- let s = seed;
- return function() {
- s = (s * 9301 + 49297) % 233280;
- return s / 233280;
- };
-}
-
-/**
- * Create a stylized planet marker element
- */
-function createPlanetMarker(planet, idx, t) {
+function createPlanetMarker(planet, idx) {
const el = document.createElement('div');
el.className = 'planet-marker';
@@ -491,7 +389,6 @@ function createPlanetMarker(planet, idx, t) {
'sports': ['#ff9800', '#f57c00'],
};
const colors = themeColors[planet.theme] || themeColors['default'];
- const memberCount = planet.member_count || 0;
el.innerHTML = `
@@ -503,32 +400,22 @@ function createPlanetMarker(planet, idx, t) {
-
-
-
-
+
-
-
-
${planet.name || 'Planet'}
- ${memberCount} members
+ ${planet.member_count || 0} members
`;
-
return el;
}
-/**
- * Get planet gradient for backgrounds
- */
function getPlanetGradient(theme) {
const gradients = {
'default': 'linear-gradient(135deg, #9b59b6, #8e44ad)',
@@ -542,9 +429,6 @@ function getPlanetGradient(theme) {
return gradients[theme] || gradients['default'];
}
-/**
- * Create starfield GeoJSON
- */
function createStarfield(count) {
const features = [];
for (let i = 0; i < count; i++) {
@@ -563,9 +447,6 @@ function createStarfield(count) {
return { type: 'FeatureCollection', features };
}
-/**
- * Create nebula effect GeoJSON
- */
function createNebulaEffect() {
const features = [];
const nebulaColors = [
@@ -575,16 +456,12 @@ function createNebulaEffect() {
{ color: 'rgba(26, 188, 156, 0.08)', x: 80, y: 50 },
];
- nebulaColors.forEach((nebula, i) => {
+ nebulaColors.forEach((nebula) => {
const coords = [];
- const numPoints = 32;
- for (let j = 0; j <= numPoints; j++) {
- const angle = (j / numPoints) * Math.PI * 2;
+ for (let j = 0; j <= 32; j++) {
+ const angle = (j / 32) * Math.PI * 2;
const radius = 60 + Math.random() * 30;
- coords.push([
- nebula.x + Math.cos(angle) * radius,
- nebula.y + Math.sin(angle) * radius
- ]);
+ coords.push([nebula.x + Math.cos(angle) * radius, nebula.y + Math.sin(angle) * radius]);
}
features.push({
type: 'Feature',