253 lines
8.8 KiB
JavaScript
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(0x1a1a2e);
|
|
globeMesh.material.emissive.setHex(0x003a00);
|
|
globeMesh.material.emissiveIntensity = 0.4;
|
|
}
|
|
})
|
|
(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);
|
|
}
|
|
|
|
})();
|