Last active
October 22, 2025 09:04
-
-
Save opencoca/8b9c17194850e881cc7c0ca39a0a145b to your computer and use it in GitHub Desktop.
Sage 3D Globe Dashboard News example.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Sage 3D News Globe</title> | |
| <link href="https://startr.style/style.css" rel="stylesheet" /> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| } | |
| /* LEFT SIDEBAR */ | |
| .logo { | |
| font-size: 24px; | |
| font-weight: bold; | |
| text-align: center; | |
| margin-bottom: 30px; | |
| color: #00d9ff; | |
| } | |
| .nav-item { | |
| padding: 12px 16px; | |
| margin: 4px 0; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| } | |
| .nav-item:hover { | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| .nav-item.active { | |
| background: #00d9ff; | |
| color: #1a1a2e; | |
| } | |
| /* CENTER - GLOBE */ | |
| .globe-container { | |
| background: #0a0a0f; | |
| } | |
| #globeViz { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .globe-controls { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 20px; | |
| background: rgba(0, 0, 0, 0.8); | |
| padding: 15px; | |
| border-radius: 8px; | |
| color: white; | |
| } | |
| .toggle-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| cursor: pointer; | |
| } | |
| input[type="checkbox"] { | |
| width: 18px; | |
| height: 18px; | |
| cursor: pointer; | |
| } | |
| /* RIGHT SIDEBAR */ | |
| .detail-panel h2 { | |
| font-size: 24px; | |
| margin-bottom: 20px; | |
| color: #af1aae; | |
| } | |
| .metric { | |
| background: white; | |
| padding: 16px; | |
| margin: 12px 0; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .metric-label { | |
| font-size: 12px; | |
| color: #666; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .metric-value { | |
| font-size: 32px; | |
| font-weight: bold; | |
| color: #00d9ff; | |
| margin-top: 8px; | |
| } | |
| .chart-placeholder { | |
| background: white; | |
| height: 200px; | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: #999; | |
| margin-top: 20px; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .empty-state { | |
| color: #999; | |
| text-align: center; | |
| padding: 40px 20px; | |
| } | |
| </style> | |
| </head> | |
| <body style="--d:flex; --jc: space-between; pointer-events: none; "> | |
| <!-- LEFT SIDEBAR --> | |
| <div class="sidebar-left" style="--w:16rem; --p:1em; pointer-events: auto; "> | |
| <div class="logo">Sage.is News</div> | |
| <div class="nav-item active">Dash 🌍</div> | |
| <div class="nav-item">Analytics</div> | |
| <div class="nav-item">Locations</div> | |
| <div class="nav-item">Reports</div> | |
| <div class="nav-item">Settings</div> | |
| </div> | |
| <!-- CENTER - GLOBE --> | |
| <div class="globe-container" style="--pos:fixed; --z:-1; --w:100%; pointer-events: auto; "> | |
| <div id="globeViz"></div> | |
| <div class="globe-controls"> | |
| <label class="toggle-label"> | |
| <input type="checkbox" id="showConnections"> | |
| <span>Hide Connections</span> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- RIGHT SIDEBAR --> | |
| <div class="sidebar-right; pointer-events: auto; "> | |
| <div id="detailPanel" class="empty-state"> | |
| Click on a location marker to view details | |
| </div> | |
| </div> | |
| <!-- Globe.GL Library --> | |
| <script src="https://unpkg.com/globe.gl"></script> | |
| <script> | |
| const locations = [ | |
| { | |
| id: 1, | |
| name: "Kyiv", | |
| lat: 50.4501, | |
| lng: 30.5234, | |
| metricA: 85, | |
| metricB: 1000000, | |
| description: "Targeted by massive Russian air strikes causing widespread power outages and residential fires." | |
| }, | |
| { | |
| id: 2, | |
| name: "Brovary", | |
| lat: 50.6822, | |
| lng: 30.8036, | |
| metricA: 70, | |
| metricB: 20000, | |
| description: "Civilian areas struck, killing 6 including children in fires from debris." | |
| }, | |
| { | |
| id: 3, | |
| name: "Odesa", | |
| lat: 46.4825, | |
| lng: 30.7233, | |
| metricA: 75, | |
| metricB: 500000, | |
| description: "Energy infrastructure hit, exacerbating national power crisis." | |
| }, | |
| { | |
| id: 4, | |
| name: "Moscow", | |
| lat: 55.7558, | |
| lng: 37.6173, | |
| metricA: 90, | |
| metricB: 0, | |
| description: "Launched over 100 missiles and drones in escalation against Ukraine." | |
| }, | |
| { | |
| id: 5, | |
| name: "Paris", | |
| lat: 48.8566, | |
| lng: 2.3522, | |
| metricA: 80, | |
| metricB: 88, | |
| description: "Louvre reopens after daylight heist of crown jewels valued at €88M." | |
| }, | |
| { | |
| id: 6, | |
| name: "Washington DC", | |
| lat: 38.9072, | |
| lng: -77.0369, | |
| metricA: 65, | |
| metricB: 1, | |
| description: "Vance heads to Jerusalem; Trump shelves Putin summit amid Ukraine crisis." | |
| }, | |
| { | |
| id: 7, | |
| name: "Jerusalem", | |
| lat: 31.7683, | |
| lng: 35.2137, | |
| metricA: 75, | |
| metricB: 2000000, | |
| description: "Netanyahu to discuss Gaza with US VP Vance as aid remains blocked." | |
| }, | |
| { | |
| id: 8, | |
| name: "Gaza City", | |
| lat: 31.5017, | |
| lng: 34.4668, | |
| metricA: 95, | |
| metricB: 500, | |
| description: "Ceasefire fails to deliver aid; UN reports critical shortages and ongoing suffering." | |
| }, | |
| { | |
| id: 9, | |
| name: "Rabat", | |
| lat: 34.0209, | |
| lng: -6.8416, | |
| metricA: 60, | |
| metricB: 5000, | |
| description: "Gen Z protests demand education and anti-corruption reforms; met with arrests." | |
| }, | |
| { | |
| id: 10, | |
| name: "Casablanca", | |
| lat: 33.5731, | |
| lng: -7.5898, | |
| metricA: 55, | |
| metricB: 10000, | |
| description: "Youth demonstrations spread, calling for healthcare improvements and accountability." | |
| }, | |
| { | |
| id: 11, | |
| name: "Munich", | |
| lat: 48.1351, | |
| lng: 11.5820, | |
| metricA: 40, | |
| metricB: 6000000, | |
| description: "Oktoberfest concludes with losses from earlier bomb threat shutdown." | |
| }, | |
| { | |
| id: 12, | |
| name: "Kigali", | |
| lat: -1.9493, | |
| lng: 30.0589, | |
| metricA: 30, | |
| metricB: 10000, | |
| description: "MWC25 launches, spotlighting Africa's push for digital connectivity and innovation." | |
| }, | |
| { | |
| id: 13, | |
| name: "New York", | |
| lat: 40.7128, | |
| lng: -74.0060, | |
| metricA: 70, | |
| metricB: 10000000, | |
| description: "AWS outage disrupts services, highlighting global reliance on cloud infrastructure." | |
| }, | |
| { | |
| id: 14, | |
| name: "Seattle", | |
| lat: 47.6062, | |
| lng: -122.3321, | |
| metricA: 85, | |
| metricB: 1000000000, | |
| description: "AWS US-EAST-1 failure triggers worldwide internet disruptions affecting billions." | |
| }, | |
| { | |
| id: 15, | |
| name: "London", | |
| lat: 51.5074, | |
| lng: -0.1278, | |
| metricA: 60, | |
| metricB: 5000000, | |
| description: "Europe grapples with outage fallout, impacting businesses and daily communications." | |
| } | |
| ]; | |
| const connections = [ | |
| { from: 4, to: 1 }, | |
| { from: 4, to: 2 }, | |
| { from: 4, to: 3 }, | |
| { from: 6, to: 4 }, | |
| { from: 6, to: 7 }, | |
| { from: 7, to: 8 }, | |
| { from: 5, to: 11 }, | |
| { from: 5, to: 15 }, | |
| { from: 9, to: 10 }, | |
| { from: 14, to: 13 }, | |
| { from: 14, to: 15 }, | |
| { from: 14, to: 5 }, | |
| { from: 14, to: 7 }, | |
| { from: 11, to: 5 } | |
| ]; | |
| function getArcsData() { | |
| return connections.map(conn => { | |
| const from = locations.find(l => l.id === conn.from); | |
| const to = locations.find(l => l.id === conn.to); | |
| return { | |
| startLat: from.lat, | |
| startLng: from.lng, | |
| endLat: to.lat, | |
| endLng: to.lng, | |
| color: from.id % 2 === 0 ? 'rgba(0, 217, 255, 0.5)' : 'rgba(255, 71, 200, 0.5)' | |
| }; | |
| }); | |
| } | |
| // Initialize Globe | |
| const globe = Globe() | |
| (document.getElementById('globeViz')) | |
| .globeImageUrl('https://upload.wikimedia.org/wikipedia/commons/b/ba/The_earth_at_night.jpg') | |
| .bumpImageUrl('https://unpkg.com/three-globe/example/img/earth-topology.png') | |
| .backgroundImageUrl('https://unpkg.com/three-globe/example/img/night-sky.png') | |
| .pointsData(locations) | |
| .pointLat('lat') | |
| .pointLng('lng') | |
| .pointColor(() => '#ff47FFBB') | |
| .pointAltitude(0.02) | |
| .pointRadius(0.6) | |
| .pointLabel(d => `<b>${d.name}</b><br/>Impact: ${d.metricA}<br/>Affected: ${d.metricB}`) | |
| .onPointClick(point => { | |
| showDetails(point); | |
| globe.pointOfView({ | |
| lat: point.lat, | |
| lng: point.lng, | |
| altitude: 2 | |
| }, 1000); | |
| }) | |
| .arcsData(getArcsData()) | |
| .arcColor('color') | |
| .arcStroke(0.5) | |
| .arcDashLength(0.4) | |
| .arcDashGap(0.2) | |
| .arcDashAnimateTime(2000); | |
| // Controls | |
| globe.controls().autoRotate = true; | |
| globe.controls().autoRotateSpeed = 0.06; | |
| // Show/hide connections | |
| document.getElementById('showConnections').addEventListener('change', (e) => { | |
| if (!e.target.checked) { | |
| globe.arcsData(getArcsData()); | |
| globe.arcDashGap(0.2); | |
| } else { | |
| globe.arcsData([]); | |
| } | |
| }); | |
| // Show location details | |
| function showDetails(location) { | |
| const panel = document.getElementById('detailPanel'); | |
| panel.innerHTML = ` | |
| <div class="detail-panel"> | |
| <h2>${location.name}</h2> | |
| <p style="color: #666; margin-bottom: 20px;">${location.description}</p> | |
| <div class="metric"> | |
| <div class="metric-label">Impact Score</div> | |
| <div class="metric-value">${location.metricA}</div> | |
| </div> | |
| <div class="metric"> | |
| <div class="metric-label">Affected</div> | |
| <div class="metric-value">${location.metricB}</div> | |
| </div> | |
| <div class="chart-placeholder"> | |
| 📊 News Impact Chart | |
| </div> | |
| </div> | |
| `; | |
| } | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| globe.width(document.getElementById('globeViz').offsetWidth); | |
| globe.height(document.getElementById('globeViz').offsetHeight); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <link href="https://startr.style/style.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment