jaeswift-website/js/globe.js

253 lines
8.8 KiB
JavaScript

/* ===================================================
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);
}
})();