mirror of
https://github.com/permissionlesstech/georelays.git
synced 2026-07-24 23:45:17 +00:00
feat: integrate 3D WebGL cinematic globe with dynamic theme colors and smooth camera glides
This commit is contained in:
+3
-3
@@ -541,7 +541,7 @@ relay.rkus.se,59.3666,16.5077,0
|
||||
btc.klendazu.com,41.2861,1.24993,0
|
||||
relay.noderunners.network,52.2633,21.0283,0
|
||||
nostr.babyshark.win,43.6532,-79.3832,0
|
||||
nsite.run,45.4642,9.18998,0
|
||||
nsite.run,42.6977,23.3219,0
|
||||
relay.sdbitcoiners.com,43.6532,-79.3832,0
|
||||
relay.bramm.xyz,45.5029,-73.5723,0
|
||||
relay.nostr.nu,50.1109,8.68213,0
|
||||
@@ -842,7 +842,7 @@ relay.nostraddress.com,43.6532,-79.3832,0
|
||||
haven.nostrfreedom.net,43.6532,-79.3832,0
|
||||
inbox.cyphernomad.com,43.6532,-79.3832,0
|
||||
relay.fizx.uk,51.9194,19.1451,0
|
||||
nostr.dler.com,25.0367,121.524,0
|
||||
nostr.dler.com,25.0501,121.565,0
|
||||
u2p.anhkagi.net,50.6924,3.20113,0
|
||||
search.nostrarchives.com,49.4543,11.0746,0
|
||||
nip85.nostr1.com,40.7057,-74.0136,0
|
||||
@@ -914,7 +914,7 @@ rrr.pupupu.monster,43.6532,-79.3832,0
|
||||
relay.blkstr.io:443,43.6532,-79.3832,0
|
||||
wot.relayted.de,52.52,13.405,0
|
||||
relay.denver.space,39.5481,-104.974,0
|
||||
bitcoinostr.duckdns.org,38.9504,-0.14007,0
|
||||
bitcoinostr.duckdns.org,43.3434,-3.99532,0
|
||||
relay.bnos.space:443,43.6532,-79.3832,0
|
||||
relay.caramboo.com,50.1109,8.68213,0
|
||||
relay.thebluepulse.com,49.4521,11.0767,0
|
||||
|
||||
|
+139
-14
@@ -669,6 +669,9 @@
|
||||
<li class="channel-item active" data-tab="map-tab">
|
||||
<span class="channel-hash">#</span>bitchat-map
|
||||
</li>
|
||||
<li class="channel-item" data-tab="globe-tab">
|
||||
<span class="channel-hash">#</span>bitchat-3d
|
||||
</li>
|
||||
<li class="channel-item" data-tab="charts-tab">
|
||||
<span class="channel-hash">#</span>bitchat-charts
|
||||
</li>
|
||||
@@ -719,6 +722,11 @@
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 1.5: 3D Globe View -->
|
||||
<div class="tab-content" id="globe-tab">
|
||||
<div id="globe-3d-container" style="width: 100%; height: 100%; position: relative; background-color: var(--bg-darker);"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Charts View -->
|
||||
<div class="tab-content" id="charts-tab">
|
||||
<div class="charts-container">
|
||||
@@ -816,6 +824,9 @@
|
||||
<!-- ChartJS JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<!-- Globe.gl WebGL JS for the 3D globe -->
|
||||
<script src="https://unpkg.com/globe.gl"></script>
|
||||
|
||||
<script>
|
||||
// Global App State
|
||||
const state = {
|
||||
@@ -1013,6 +1024,24 @@
|
||||
// Leaflet needs an invalidateSize call when container shifts visibility
|
||||
setTimeout(() => state.map.invalidateSize(), 50);
|
||||
}
|
||||
} else if (tabId === 'globe-tab') {
|
||||
titleEl.textContent = '#bitchat-3d';
|
||||
descEl.textContent = '3D Cinematic holographic terminal tracking geolocated relays';
|
||||
|
||||
// Lazy initialize the 3D globe to optimize startup performance
|
||||
if (!state.globe) {
|
||||
logSystemMessage("Booting WebGL 3D Globe Engine... Loading targeting mesh.");
|
||||
setTimeout(() => {
|
||||
init3DGlobe();
|
||||
logSystemMessage("3D Holographic target grid initialized. Auto-rotate ENABLED.");
|
||||
}, 50);
|
||||
} else {
|
||||
// Trigger a resize/refresh in case size of container shifted
|
||||
const container = document.getElementById('globe-3d-container');
|
||||
if (container) {
|
||||
setTimeout(() => state.globe.width(container.clientWidth).height(container.clientHeight), 50);
|
||||
}
|
||||
}
|
||||
} else if (tabId === 'charts-tab') {
|
||||
titleEl.textContent = '#bitchat-charts';
|
||||
descEl.textContent = 'Interactive trend analysis over custom date ranges';
|
||||
@@ -1039,6 +1068,79 @@
|
||||
}).addTo(state.map);
|
||||
}
|
||||
|
||||
// WebGL 3D Globe Setup
|
||||
function init3DGlobe() {
|
||||
const container = document.getElementById('globe-3d-container');
|
||||
if (!container || state.globe) return;
|
||||
|
||||
const styles = getComputedStyle(document.documentElement);
|
||||
const primaryColor = styles.getPropertyValue('--text-primary').trim();
|
||||
const accentColor = styles.getPropertyValue('--accent-color').trim();
|
||||
|
||||
// Instantiate Globe.gl
|
||||
state.globe = Globe()(container)
|
||||
.backgroundColor('rgba(0,0,0,0)') // Transparent context to let the CSS background bleed through
|
||||
.showAtmosphere(true)
|
||||
.atmosphereColor(primaryColor) // glowing atmosphere matching current terminal theme
|
||||
.atmosphereAltitude(0.18)
|
||||
.showGraticules(true) // latitude/longitude wireframe grid
|
||||
|
||||
// Use an empty globe texture to make it a transparent vector outline ball
|
||||
.globeImageUrl('')
|
||||
|
||||
// Bind Geolocation coordinates
|
||||
.pointsData(state.relays)
|
||||
.pointLat(d => d.lat)
|
||||
.pointLng(d => d.lon)
|
||||
.pointColor(d => d.isBitChat === 1 ? primaryColor : accentColor)
|
||||
.pointRadius(d => d.isBitChat === 1 ? 0.6 : 0.3)
|
||||
.pointAltitude(d => d.isBitChat === 1 ? 0.08 : 0.03) // Taller towers for voice/BitChat relays!
|
||||
.pointResolution(8)
|
||||
|
||||
// Styled popup tooltip
|
||||
.pointLabel(d => `
|
||||
<div style="background-color: var(--bg-darker); border: 1px solid ${d.isBitChat === 1 ? 'var(--text-primary)' : 'var(--accent-color)'}; color: var(--text-primary); font-family: 'Fira Code', monospace; font-size: 0.8rem; padding: 8px; border-radius: 4px; box-shadow: var(--border-glow);">
|
||||
<strong style="color: var(--accent-color);">wss://${d.url}</strong><br>
|
||||
Scope: ${d.isBitChat === 1 ? 'BitChat (Kind 20000)' : 'Standard Relay'}<br>
|
||||
Coords: ${d.lat.toFixed(4)}, ${d.lon.toFixed(4)}
|
||||
</div>
|
||||
`)
|
||||
|
||||
// Clicking points triggers terminal WHOIS report
|
||||
.onPointClick(d => {
|
||||
triggerWhois(d);
|
||||
});
|
||||
|
||||
// Enable slowly spinning globe controls
|
||||
state.globe.controls().autoRotate = true;
|
||||
state.globe.controls().autoRotateSpeed = 0.6;
|
||||
state.globe.controls().enableZoom = true;
|
||||
|
||||
// Adjust camera distance for global viewing
|
||||
state.globe.pointOfView({ lat: 20, lng: 0, altitude: 2.2 });
|
||||
}
|
||||
|
||||
function updateGlobeColors() {
|
||||
if (!state.globe) return;
|
||||
const styles = getComputedStyle(document.documentElement);
|
||||
const primaryColor = styles.getPropertyValue('--text-primary').trim();
|
||||
const accentColor = styles.getPropertyValue('--accent-color').trim();
|
||||
|
||||
state.globe
|
||||
.atmosphereColor(primaryColor)
|
||||
.pointColor(d => d.isBitChat === 1 ? primaryColor : accentColor);
|
||||
}
|
||||
|
||||
// Window Resize Handler to update WebGL aspect ratios
|
||||
window.addEventListener('resize', () => {
|
||||
if (state.globe) {
|
||||
const container = document.getElementById('globe-3d-container');
|
||||
if (container) {
|
||||
state.globe.width(container.clientWidth).height(container.clientHeight);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Load and Parse Relay CSV
|
||||
function loadRelayData() {
|
||||
fetch('all_relays_geo.csv')
|
||||
@@ -1208,15 +1310,19 @@
|
||||
li.appendChild(name);
|
||||
|
||||
li.addEventListener('click', () => {
|
||||
// Zoom to location on map
|
||||
switchTab('map-tab');
|
||||
state.map.setView([r.lat, r.lon], 7);
|
||||
|
||||
// Find marker and open popup
|
||||
// Since markers are filtered and match the filtered index or we find it by URL
|
||||
const markerIndex = state.relays.filter(el => !showBitChatOnly || el.isBitChat === 1).indexOf(r);
|
||||
if (markerIndex !== -1 && state.markers[markerIndex]) {
|
||||
state.markers[markerIndex].openPopup();
|
||||
if (state.currentTab === 'globe-tab' && state.globe) {
|
||||
// Smoothly glide 3D camera in space to target node (altitude 1.2, 1.2s transition)
|
||||
state.globe.pointOfView({ lat: r.lat, lng: r.lon, altitude: 1.2 }, 1200);
|
||||
} else {
|
||||
// Zoom to location on 2D map
|
||||
switchTab('map-tab');
|
||||
state.map.setView([r.lat, r.lon], 7);
|
||||
|
||||
// Find marker and open popup
|
||||
const markerIndex = state.relays.filter(el => !showBitChatOnly || el.isBitChat === 1).indexOf(r);
|
||||
if (markerIndex !== -1 && state.markers[markerIndex]) {
|
||||
state.markers[markerIndex].openPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// Output WHOIS to system logs
|
||||
@@ -1230,6 +1336,13 @@
|
||||
function toggleBitChatFilter() {
|
||||
populateNicklist();
|
||||
plotRelayMarkers();
|
||||
|
||||
// Instantly update the 3D Globe data if it's active
|
||||
if (state.globe) {
|
||||
const showBitChatOnly = document.getElementById('bitchat-filter-checkbox') ? document.getElementById('bitchat-filter-checkbox').checked : true;
|
||||
const filteredPoints = state.relays.filter(r => !showBitChatOnly || r.isBitChat === 1);
|
||||
state.globe.pointsData(filteredPoints);
|
||||
}
|
||||
}
|
||||
|
||||
function onNickSearchInput(val) {
|
||||
@@ -1588,6 +1701,7 @@
|
||||
logSystemMessage("=================================================");
|
||||
logSystemMessage("/help - Displays this instruction panel");
|
||||
logSystemMessage("/map - Toggle view to interactive geographic node map");
|
||||
logSystemMessage("/globe /3d - Toggle view to interactive 3D WebGL globe");
|
||||
logSystemMessage("/charts - Toggle view to metrics & historical graphs");
|
||||
logSystemMessage("/log - Toggle view to system diagnostic console");
|
||||
logSystemMessage("/whois <relay> - Query specific relay metadata & locate");
|
||||
@@ -1603,6 +1717,12 @@
|
||||
logSystemMessage("Mainframe display routed to #bitchat-map.");
|
||||
break;
|
||||
|
||||
case '/globe':
|
||||
case '/3d':
|
||||
switchTab('globe-tab');
|
||||
logSystemMessage("Mainframe display routed to #bitchat-3d.");
|
||||
break;
|
||||
|
||||
case '/charts':
|
||||
switchTab('charts-tab');
|
||||
logSystemMessage("Mainframe display routed to #bitchat-charts.");
|
||||
@@ -1659,6 +1779,7 @@
|
||||
// Re-render markers and charts with new CSS variables
|
||||
setTimeout(() => {
|
||||
plotRelayMarkers();
|
||||
updateGlobeColors();
|
||||
if (state.history) {
|
||||
const startVal = document.getElementById('chart-start-date').value;
|
||||
const endVal = document.getElementById('chart-end-date').value;
|
||||
@@ -1699,11 +1820,15 @@
|
||||
const sub = args[0].toLowerCase();
|
||||
const match = state.relays.find(r => r.url.toLowerCase().includes(sub));
|
||||
if (match) {
|
||||
switchTab('map-tab');
|
||||
state.map.setView([match.lat, match.lon], 7);
|
||||
const index = state.relays.indexOf(match);
|
||||
if (index !== -1 && state.markers[index]) {
|
||||
state.markers[index].openPopup();
|
||||
if (state.currentTab === 'globe-tab' && state.globe) {
|
||||
state.globe.pointOfView({ lat: match.lat, lng: match.lon, altitude: 1.2 }, 1200);
|
||||
} else {
|
||||
switchTab('map-tab');
|
||||
state.map.setView([match.lat, match.lon], 7);
|
||||
const index = state.relays.indexOf(match);
|
||||
if (index !== -1 && state.markers[index]) {
|
||||
state.markers[index].openPopup();
|
||||
}
|
||||
}
|
||||
triggerWhois(match);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user