/* =================================================== JAESWIFT.XYZ — 3D Globe (Globe.gl) eDEX-UI inspired rotating globe with server location =================================================== */ (function () { 'use strict'; const container = document.getElementById('globeViz'); if (!container || typeof Globe === 'undefined') return; // Manchester coordinates const SERVER_LAT = 53.48; const SERVER_LNG = -2.24; // Wait for container to be visible and sized function initGlobe() { const rect = container.parentElement.getBoundingClientRect(); const size = Math.min(rect.width, 340); if (size < 50) { setTimeout(initGlobe, 200); return; } const globe = Globe() .globeImageUrl('') .backgroundColor('rgba(0,0,0,0)') .width(size) .height(size) .showAtmosphere(true) .atmosphereColor('#00cc33') .atmosphereAltitude(0.15) // Hex polygons for land masses .hexPolygonsData([]) // Points - server location .pointsData([{ lat: SERVER_LAT, lng: SERVER_LNG, size: 0.6, color: '#00cc33', label: 'MANCHESTER' }]) .pointAltitude('size') .pointColor('color') .pointRadius(0.4) .pointsMerge(false) // Rings - pulse effect .ringsData([{ lat: SERVER_LAT, lng: SERVER_LNG, maxR: 3, propagationSpeed: 1.5, repeatPeriod: 1200 }]) .ringColor(function () { return '#00cc3380'; }) .ringMaxRadius('maxR') .ringPropagationSpeed('propagationSpeed') .ringRepeatPeriod('repeatPeriod') // Labels .labelsData([{ lat: SERVER_LAT, lng: SERVER_LNG, text: 'MCR // 53.48°N', color: '#00cc33', size: 0.7 }]) .labelColor('color') .labelSize('size') .labelDotRadius(0.3) .labelAltitude(0.01) .labelText('text') .labelResolution(2) // Custom globe material .onGlobeReady(function () { // Style the globe mesh var globeMesh = globe.scene().children.find(function (c) { return c.type === 'Mesh' && c.geometry && c.geometry.type === 'SphereGeometry'; }); if (globeMesh && globeMesh.material) { globeMesh.material.color.setHex(0x050505); globeMesh.material.emissive.setHex(0x001a00); globeMesh.material.emissiveIntensity = 0.15; } }) (container); // Auto rotate globe.controls().autoRotate = true; globe.controls().autoRotateSpeed = 0.4; globe.controls().enableZoom = false; globe.controls().enablePan = false; globe.controls().minPolarAngle = Math.PI * 0.35; globe.controls().maxPolarAngle = Math.PI * 0.65; // Point camera at Manchester globe.pointOfView({ lat: SERVER_LAT, lng: SERVER_LNG, altitude: 2.2 }, 1500); // Fetch country polygons for land outlines fetch('https://unpkg.com/world-atlas@2/countries-110m.json') .then(function (r) { return r.json(); }) .then(function (worldData) { var countries = topojsonFeature(worldData, worldData.objects.countries).features; globe .hexPolygonsData(countries) .hexPolygonResolution(3) .hexPolygonMargin(0.4) .hexPolygonColor(function () { return 'rgba(0, 204, 51, 0.12)'; }) .hexPolygonAltitude(0.005); }) .catch(function () { // Fallback: no country data, globe still works }); // Simulated visitor arcs (random locations connecting to Manchester) function generateRandomArcs() { var cities = [ { lat: 40.71, lng: -74.01 }, // New York { lat: 35.68, lng: 139.69 }, // Tokyo { lat: 48.86, lng: 2.35 }, // Paris { lat: -33.87, lng: 151.21 }, // Sydney { lat: 55.76, lng: 37.62 }, // Moscow { lat: 1.35, lng: 103.82 }, // Singapore { lat: 37.57, lng: 126.98 }, // Seoul { lat: 19.43, lng: -99.13 }, // Mexico City { lat: 52.52, lng: 13.41 }, // Berlin { lat: -23.55, lng: -46.63 }, // São Paulo { lat: 28.61, lng: 77.21 }, // Delhi { lat: 34.05, lng: -118.24 }, // LA ]; var arcs = []; var count = 2 + Math.floor(Math.random() * 3); for (var i = 0; i < count; i++) { var city = cities[Math.floor(Math.random() * cities.length)]; arcs.push({ startLat: city.lat, startLng: city.lng, endLat: SERVER_LAT, endLng: SERVER_LNG, color: ['rgba(0, 204, 51, 0.6)', 'rgba(0, 204, 51, 0.05)'] }); } return arcs; } globe .arcsData(generateRandomArcs()) .arcColor('color') .arcDashLength(0.5) .arcDashGap(0.2) .arcDashAnimateTime(2000) .arcStroke(0.4) .arcAltitudeAutoScale(0.3); // Refresh arcs periodically setInterval(function () { globe.arcsData(generateRandomArcs()); }, 5000); // Handle resize var resizeTimer; window.addEventListener('resize', function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { var newRect = container.parentElement.getBoundingClientRect(); var newSize = Math.min(newRect.width, 340); globe.width(newSize).height(newSize); }, 250); }); } // Topojson helper function topojsonFeature(topology, obj) { if (!obj || !obj.geometries) return { type: 'FeatureCollection', features: [] }; return { type: 'FeatureCollection', features: obj.geometries.map(function (geom) { return { type: 'Feature', geometry: topojsonGeometry(topology, geom), properties: geom.properties || {}, id: geom.id }; }) }; } function topojsonGeometry(topology, obj) { var arcs = topology.arcs; var transform = topology.transform; function decodeArc(arcIdx) { var arc = arcs[arcIdx < 0 ? ~arcIdx : arcIdx]; var coords = []; var x = 0, y = 0; for (var i = 0; i < arc.length; i++) { x += arc[i][0]; y += arc[i][1]; var lon = x, lat = y; if (transform) { lon = lon * transform.scale[0] + transform.translate[0]; lat = lat * transform.scale[1] + transform.translate[1]; } coords.push([lon, lat]); } if (arcIdx < 0) coords.reverse(); return coords; } function decodeRing(arcIndices) { var coords = []; for (var i = 0; i < arcIndices.length; i++) { var arcCoords = decodeArc(arcIndices[i]); if (i > 0) arcCoords.shift(); coords = coords.concat(arcCoords); } return coords; } var type = obj.type; if (type === 'Polygon') { return { type: 'Polygon', coordinates: obj.arcs.map(decodeRing) }; } else if (type === 'MultiPolygon') { return { type: 'MultiPolygon', coordinates: obj.arcs.map(function (polygon) { return polygon.map(decodeRing); }) }; } else if (type === 'GeometryCollection') { return { type: 'GeometryCollection', geometries: (obj.geometries || []).map(function (g) { return topojsonGeometry(topology, g); }) }; } return { type: type, coordinates: [] }; } // Init when DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(initGlobe, 300); }); } else { setTimeout(initGlobe, 300); } })();