MoShow's picture
Initial DeepSite commit
2f4fe32 verified
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MoStar Industries — Phantom POE Map | 3D Intelligence View</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- CesiumJS -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
mono: ['JetBrains Mono', 'monospace'],
sans: ['Inter', 'sans-serif'],
},
colors: {
'risk-critical': '#ef4444',
'risk-high': '#f97316',
'risk-medium': '#eab308',
'lane-live': '#00CC66',
}
}
}
}
</script>
<style>
body {
margin: 0;
overflow: hidden;
background: #0f172a;
font-family: 'Inter', sans-serif;
}
#cesiumContainer {
width: 100%;
height: 100vh;
}
.glass-panel {
background: rgba(15, 23, 42, 0.9);
backdrop-filter: blur(12px);
border: 1px solid rgba(51, 65, 85, 0.8);
}
.cesium-viewer-toolbar {
display: none !important;
}
.cesium-viewer-bottom {
display: none !important;
}
.cesium-viewer-fullscreenContainer {
display: none !important;
}
/* Custom scrollbar for panels */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #1e293b;
}
::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 3px;
}
</style>
</head>
<body class="text-slate-200">
<!-- Header Overlay -->
<div class="absolute top-0 left-0 right-0 z-50 glass-panel border-b border-slate-700 h-16 flex items-center justify-between px-4">
<div class="flex items-center gap-4">
<a href="index.html" class="flex items-center gap-2 hover:opacity-80 transition-opacity">
<span class="text-2xl font-bold tracking-tighter text-white">◉⟁⬡</span>
<div>
<h1 class="font-bold text-lg leading-tight text-white">MoStar Industries</h1>
<p class="text-xs text-slate-400 font-mono">Phantom POE 3D Map v2.1</p>
</div>
</a>
<div class="h-8 w-px bg-slate-700 mx-2"></div>
<div class="flex items-center gap-2">
<span class="px-3 py-1 rounded-full bg-lane-live/20 text-lane-live border border-lane-live/30 text-xs font-bold flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-lane-live animate-pulse"></span>
LIVE MODE
</span>
<span class="text-xs text-slate-500 font-mono">14 Corridors | 91 Nodes | 157 Signals</span>
</div>
</div>
<div class="flex items-center gap-3">
<button onclick="resetView()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded-lg text-xs font-medium transition-colors flex items-center gap-2 border border-slate-600">
<i data-lucide="globe" class="w-4 h-4"></i>
Africa Overview
</button>
<button onclick="toggleCascadeMenu()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded-lg text-xs font-medium transition-colors flex items-center gap-2 border border-slate-600">
<i data-lucide="play-circle" class="w-4 h-4 text-emerald-400"></i>
Cascade Animation
</button>
<button onclick="clearCascade()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded-lg text-xs font-medium transition-colors flex items-center gap-2 border border-slate-600">
<i data-lucide="square" class="w-4 h-4 text-red-400"></i>
Stop
</button>
<a href="index.html" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded-lg text-xs font-medium transition-colors flex items-center gap-2 border border-slate-600">
<i data-lucide="layout-dashboard" class="w-4 h-4"></i>
Dashboard
</a>
</div>
</div>
<!-- Cascade Menu (Hidden by default) -->
<div id="cascade-menu" class="hidden absolute top-20 left-4 z-40 glass-panel rounded-xl border border-slate-700 w-64 max-h-[70vh] overflow-y-auto">
<div class="p-3 border-b border-slate-700 bg-slate-800/50">
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Select Corridor</h3>
</div>
<div class="p-2 space-y-1">
<button onclick="startCascade('C-ET-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-critical">
<div class="font-bold text-white">Gambela → Malakal</div>
<div class="text-slate-500">280km | CRITICAL</div>
</button>
<button onclick="startCascade('C-NG-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-critical">
<div class="font-bold text-white">Baga → Diffa</div>
<div class="text-slate-500">147km | CRITICAL</div>
</button>
<button onclick="startCascade('C-CD-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-critical">
<div class="font-bold text-white">Mbandaka → Impfondo</div>
<div class="text-slate-500">190km | CRITICAL</div>
</button>
<button onclick="startCascade('C-CF-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-critical">
<div class="font-bold text-white">Birao → Nyala</div>
<div class="text-slate-500">300km | CRITICAL</div>
</button>
<button onclick="startCascade('C-SS-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-critical">
<div class="font-bold text-white">Yambio → Bangui</div>
<div class="text-slate-500">527km | CRITICAL</div>
</button>
<button onclick="startCascade('C-CD-002')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-high">
<div class="font-bold text-white">Gbadolite → Bangui</div>
<div class="text-slate-500">280km | HIGH</div>
</button>
<button onclick="startCascade('C-ET-002')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-high">
<div class="font-bold text-white">Dolo Ado → Baidoa</div>
<div class="text-slate-500">210km | HIGH</div>
</button>
<button onclick="startCascade('C-SS-002')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-high">
<div class="font-bold text-white">Renk → Ed Damazin</div>
<div class="text-slate-500">175km | HIGH</div>
</button>
<button onclick="startCascade('C-ET-003')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-high">
<div class="font-bold text-white">Humera → Kassala</div>
<div class="text-slate-500">155km | HIGH</div>
</button>
<button onclick="startCascade('C-NG-002')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-high">
<div class="font-bold text-white">Gwoza → Mora</div>
<div class="text-slate-500">52km | HIGH</div>
</button>
<button onclick="startCascade('C-SD-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-high">
<div class="font-bold text-white">Metema → Gallabat</div>
<div class="text-slate-500">8km | HIGH</div>
</button>
<button onclick="startCascade('C-SS-003')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-medium">
<div class="font-bold text-white">Kapoeta → Lodwar</div>
<div class="text-slate-500">245km | MEDIUM</div>
</button>
<button onclick="startCascade('C-NG-003')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-medium">
<div class="font-bold text-white">Banki → Amchide</div>
<div class="text-slate-500">73km | MEDIUM</div>
</button>
<button onclick="startCascade('C-GL-001')" class="w-full text-left px-3 py-2 rounded-lg hover:bg-slate-800 text-xs transition-colors border-l-2 border-risk-critical">
<div class="font-bold text-white">Goma → Gisenyi</div>
<div class="text-slate-500">4km | CRITICAL</div>
</button>
</div>
</div>
<!-- Legend -->
<div class="absolute bottom-6 left-6 z-40 glass-panel rounded-xl border border-slate-700 p-4 max-w-xs pointer-events-none">
<div class="font-bold text-sm mb-2 text-white flex items-center gap-2">
<i data-lucide="map" class="w-4 h-4"></i>
PHANTOM POE ENGINE
</div>
<div class="text-xs text-slate-400 mb-3">Hidden & Undocumented Border Crossings</div>
<div class="space-y-2 mb-3">
<div class="flex items-center gap-2 text-xs">
<div class="w-3 h-1 bg-risk-critical rounded"></div>
<span class="text-slate-300">CRITICAL Risk</span>
</div>
<div class="flex items-center gap-2 text-xs">
<div class="w-3 h-1 bg-risk-high rounded"></div>
<span class="text-slate-300">HIGH Risk</span>
</div>
<div class="flex items-center gap-2 text-xs">
<div class="w-3 h-1 bg-risk-medium rounded"></div>
<span class="text-slate-300">MEDIUM Risk</span>
</div>
</div>
<div class="border-t border-slate-700 pt-2 space-y-1.5 mb-3">
<div class="flex items-center gap-2 text-xs text-slate-400">
<span class="w-2 h-2 rounded-full bg-lime-500"></span>
<span>Start Node</span>
</div>
<div class="flex items-center gap-2 text-xs text-slate-400">
<span class="w-2 h-2 rounded-full bg-red-500"></span>
<span>End Node</span>
</div>
<div class="flex items-center gap-2 text-xs text-slate-400">
<span class="w-2 h-2 rounded-full bg-amber-400 border border-amber-600"></span>
<span>Phantom POE ⚡</span>
</div>
<div class="flex items-center gap-2 text-xs text-slate-400">
<span class="w-2 h-2 rounded-full bg-orange-500"></span>
<span>Border Zone</span>
</div>
</div>
<div class="border-t border-slate-700 pt-2 space-y-1.5">
<div class="flex items-center gap-2 text-xs">
<span class="w-2 h-2 rounded-full bg-red-500"></span>
<span class="text-slate-400">ACLED Conflict</span>
</div>
<div class="flex items-center gap-2 text-xs">
<span class="w-2 h-2 rounded-full bg-blue-500"></span>
<span class="text-slate-400">IOM-DTM Displacement</span>
</div>
<div class="flex items-center gap-2 text-xs">
<span class="w-2 h-2 rounded-full bg-emerald-500"></span>
<span class="text-slate-400">DHIS2 Health</span>
</div>
<div class="flex items-center gap-2 text-xs">
<span class="w-2 h-2 rounded-full bg-yellow-500"></span>
<span class="text-slate-400">AFRO-Sentinel</span>
</div>
</div>
</div>
<!-- Active Cascade Info -->
<div id="cascade-info" class="hidden absolute bottom-6 right-6 z-40 glass-panel rounded-xl border border-slate-700 p-4 w-64">
<div class="flex items-center justify-between mb-2">
<h3 class="text-xs font-bold text-slate-400 uppercase">Cascade Animation</h3>
<div class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></div>
</div>
<div id="cascade-corridor-name" class="font-bold text-white text-sm mb-1">--</div>
<div class="flex justify-between text-xs mb-2">
<span class="text-slate-400">Day:</span>
<span id="cascade-day" class="text-white font-mono">0/7</span>
</div>
<div class="flex justify-between text-xs mb-2">
<span class="text-slate-400">Score:</span>
<span id="cascade-score" class="text-emerald-400 font-mono">0.000</span>
</div>
<div class="w-full bg-slate-800 rounded-full h-1.5">
<div id="cascade-progress" class="bg-emerald-500 h-1.5 rounded-full transition-all duration-500" style="width: 0%"></div>
</div>
</div>
<!-- Cesium Container -->
<div id="cesiumContainer"></div>
<script>
// Initialize Lucide icons
lucide.createIcons();
// Cesium Ion token (replace with your own for production)
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJlYWEzNmJkOC1iMzQ2LTRmOTYtODVlNy1iZGU4M2M2YjE1MTYiLCJpZCI6MjQ0MTU4LCJpYXQiOjE3MzA4MjY1NzB9.ZH9mWGt46kZ1RGbK8R6K7ZvC7P9R8Q5F3M2N1L0K9J8';
// Initialize viewer
const viewer = new Cesium.Viewer("cesiumContainer", {
timeline: false,
animation: false,
sceneModePicker: false,
baseLayerPicker: false,
geocoder: false,
homeButton: false,
fullscreenButton: false,
navigationHelpButton: false,
skyAtmosphere: true,
globe: false // Google Tiles include terrain
});
// Enable sky
viewer.scene.skyAtmosphere.show = true;
// Add Google Photorealistic 3D Tiles
let googleTileset;
async function loadGoogleTiles() {
try {
googleTileset = await Cesium.createGooglePhotorealistic3DTileset({
onlyUsingWithGoogleGeocoder: false
});
viewer.scene.primitives.add(googleTileset);
} catch (error) {
console.log('Error loading Google Tiles:', error);
// Fallback to default terrain
viewer.globe = true;
}
}
loadGoogleTiles();
// Source Colors for Signals
const SOURCE_COLORS = {
'ACLED': Cesium.Color.fromCssColorString('#EF4444'),
'IOM-DTM': Cesium.Color.fromCssColorString('#3B82F6'),
'DHIS2': Cesium.Color.fromCssColorString('#22C55E'),
'AFRO-SENTINEL': Cesium.Color.fromCssColorString('#EAB308'),
'MANUAL': Cesium.Color.fromCssColorString('#A855F7'),
'MOCK': Cesium.Color.fromCssColorString('#64748B')
};
// Risk Colors
const RISK_COLORS = {
'CRITICAL': Cesium.Color.fromCssColorString('#EF4444'),
'HIGH': Cesium.Color.fromCssColorString('#F97316'),
'MEDIUM': Cesium.Color.fromCssColorString('#EAB308'),
'LOW': Cesium.Color.fromCssColorString('#22C55E')
};
// Corridor Definitions
const corridors = [
{ id: 'C-ET-001', name: 'Gambela → Malakal', risk: 'CRITICAL', distance: 280, mode: 'foot', color: '#EF4444', width: 6, glow: 0.6 },
{ id: 'C-NG-001', name: 'Baga → Diffa', risk: 'CRITICAL', distance: 147, mode: 'canoe', color: '#EF4444', width: 6, glow: 0.6 },
{ id: 'C-CD-001', name: 'Mbandaka → Impfondo', risk: 'CRITICAL', distance: 190, mode: 'canoe', color: '#EF4444', width: 6, glow: 0.6 },
{ id: 'C-CF-001', name: 'Birao → Nyala', risk: 'CRITICAL', distance: 300, mode: 'foot', color: '#EF4444', width: 6, glow: 0.6 },
{ id: 'C-SS-001', name: 'Yambio → Bangui', risk: 'CRITICAL', distance: 527, mode: 'foot', color: '#EF4444', width: 6, glow: 0.6 },
{ id: 'C-GL-001', name: 'Goma → Gisenyi', risk: 'CRITICAL', distance: 4, mode: 'foot', color: '#EF4444', width: 6, glow: 0.6 },
{ id: 'C-CD-002', name: 'Gbadolite → Bangui', risk: 'HIGH', distance: 280, mode: 'canoe', color: '#F97316', width: 4, glow: 0.4 },
{ id: 'C-ET-002', name: 'Dolo Ado → Baidoa', risk: 'HIGH', distance: 210, mode: 'foot', color: '#F97316', width: 4, glow: 0.4 },
{ id: 'C-SS-002', name: 'Renk → Ed Damazin', risk: 'HIGH', distance: 175, mode: 'foot', color: '#F97316', width: 4, glow: 0.4 },
{ id: 'C-ET-003', name: 'Humera → Kassala', risk: 'HIGH', distance: 155, mode: 'foot', color: '#F97316', width: 4, glow: 0.4 },
{ id: 'C-NG-002', name: 'Gwoza → Mora', risk: 'HIGH', distance: 52, mode: 'foot', color: '#F97316', width: 4, glow: 0.4 },
{ id: 'C-SD-001', name: 'Metema → Gallabat', risk: 'HIGH', distance: 8, mode: 'foot', color: '#F97316', width: 4, glow: 0.4 },
{ id: 'C-SS-003', name: 'Kapoeta → Lodwar', risk: 'MEDIUM', distance: 245, mode: 'livestock', color: '#EAB308', width: 3, glow: 0.2 },
{ id: 'C-NG-003', name: 'Banki → Amchide', risk: 'MEDIUM', distance: 73, mode: 'foot', color: '#EAB308', width: 3, glow: 0.2 }
];
// Path coordinates for corridors
const corridorPaths = {
'C-ET-001': [34.5833, 8.25, 50, 34.2081, 8.4008, 50, 33.8703, 8.5677, 50, 33.4757, 8.7387, 50, 33.1118, 8.9070, 50, 32.7688, 9.0444, 50, 32.3657, 9.2253, 50, 32.0143, 9.3893, 50, 31.65, 9.5333, 50],
'C-NG-001': [13.7833, 12.8167, 50, 13.4679, 12.9490, 50, 13.2232, 13.0500, 50, 12.8940, 13.2123, 50, 12.6167, 13.3167, 50],
'C-CD-001': [18.2558, -0.0478, 50, 18.2230, 0.3079, 50, 18.1575, 0.6325, 50, 18.1304, 0.9764, 50, 18.0988, 1.2734, 50, 18.0594, 1.6306, 50],
'C-CF-001': [22.7833, 10.2833, 50, 23.0229, 10.5124, 50, 23.2891, 10.7105, 50, 23.5850, 10.9526, 50, 23.8500, 11.1632, 50, 24.1049, 11.3950, 50, 24.3412, 11.6140, 50, 24.6381, 11.8336, 50, 24.8833, 12.05, 50],
'C-SS-001': [28.3833, 4.5667, 50, 27.7003, 4.5390, 50, 26.9809, 4.5535, 50, 26.2815, 4.5090, 50, 25.5847, 4.5050, 50, 24.8779, 4.5011, 50, 24.1815, 4.4978, 50, 23.4633, 4.4523, 50, 22.7698, 4.4584, 50, 22.0954, 4.4280, 50, 21.4034, 4.4410, 50, 20.7043, 4.3847, 50, 19.9889, 4.4152, 50, 19.2866, 4.3599, 50, 18.5833, 4.3667, 50],
'C-GL-001': [29.2333, -1.6833, 50, 29.2697, -1.6903, 50, 29.2667, -1.7, 50],
'C-CD-002': [21.0167, 4.2833, 50, 20.7319, 4.3157, 50, 20.3842, 4.2801, 50, 20.1108, 4.3306, 50, 19.7991, 4.3292, 50, 19.5079, 4.3163, 50, 19.1945, 4.3542, 50, 18.8895, 4.3354, 50, 18.5833, 4.3667, 50],
'C-ET-002': [42.0667, 4.1833, 50, 42.3439, 3.9934, 50, 42.6012, 3.8294, 50, 42.8797, 3.6443, 50, 43.1219, 3.4561, 50, 43.3886, 3.2831, 50, 43.65, 3.1167, 50],
'C-SS-002': [32.7833, 11.7667, 50, 33.0720, 11.7647, 50, 33.3905, 11.7466, 50, 33.7326, 11.7421, 50, 34.0386, 11.7580, 50, 34.35, 11.7667, 50],
'C-ET-003': [36.6333, 14.3, 50, 36.5867, 14.5750, 50, 36.5130, 14.8646, 50, 36.4728, 15.1642, 50, 36.4, 15.45, 50],
'C-NG-002': [13.6833, 11.0833, 50, 13.8960, 11.0299, 50, 14.1333, 11.0167, 50],
'C-SD-001': [36.2, 12.95, 50, 36.1677, 12.9741, 50, 36.15, 12.9667, 50],
'C-SS-003': [33.5833, 4.7667, 50, 33.8537, 4.5274, 50, 34.1758, 4.3103, 50, 34.4249, 4.0643, 50, 34.7470, 3.8198, 50, 35.0036, 3.6073, 50, 35.3191, 3.3284, 50, 35.6, 3.1167, 50],
'C-NG-003': [13.5833, 11.05, 50, 13.9212, 10.8955, 50, 14.2167, 10.75, 50]
};
// Evidence/Simulated Signal Data
const EVIDENCE = [
// C-ET-001: Gambela → Malakal
{id:"C-ET-001-E0-0",cid:"C-ET-001",day:0,lat:8.25,lng:34.5833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Gambela",loc:"Gambela",score:0.391,precision:"PRECISE"},
{id:"C-ET-001-E0-1",cid:"C-ET-001",day:0,lat:8.4008,lng:34.2081,km:35,src:"IOM-DTM",type:"DISPLACEMENT",tag:"DISPLACEMENT: Border Zone",loc:"Border Zone ET/SS",score:0.361,precision:"SETTLEMENT"},
{id:"C-ET-001-E2-2",cid:"C-ET-001",day:2,lat:8.9070,lng:33.1118,km:140,src:"IOM-DTM",type:"DISPLACEMENT",tag:"DISPLACEMENT: Phantom Crossing",loc:"Phantom Crossing C-ET-001",score:0.169,precision:"INFERRED"},
{id:"C-ET-001-E5-2",cid:"C-ET-001",day:5,lat:9.5333,lng:31.65,km:280,src:"IOM-DTM",type:"DISPLACEMENT",tag:"DISPLACEMENT: Malakal",loc:"Malakal",score:0.367,precision:"PRECISE"},
// C-NG-001: Baga → Diffa
{id:"C-NG-001-E0-0",cid:"C-NG-001",day:0,lat:12.8167,lng:13.7833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Baga",loc:"Baga",score:0.443,precision:"PRECISE"},
{id:"C-NG-001-E3-0",cid:"C-NG-001",day:3,lat:13.0500,lng:13.2232,km:74,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Phantom Crossing",loc:"Phantom Crossing C-NG-001",score:0.453,precision:"INFERRED"},
{id:"C-NG-001-E6-1",cid:"C-NG-001",day:6,lat:13.3167,lng:12.6167,km:147,src:"IOM-DTM",type:"DISPLACEMENT",tag:"DISPLACEMENT: Diffa",loc:"Diffa",score:0.391,precision:"PRECISE"},
// C-CD-001: Mbandaka → Impfondo
{id:"C-CD-001-E0-0",cid:"C-CD-001",day:0,lat:-0.0478,lng:18.2558,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Mbandaka",loc:"Mbandaka",score:0.466,precision:"PRECISE"},
{id:"C-CD-001-E0-2",cid:"C-CD-001",day:0,lat:0.6325,lng:18.1575,km:76,src:"DHIS2",type:"HEALTH",tag:"HEALTH: Phantom Crossing",loc:"Phantom Crossing C-CD-001",score:0.378,precision:"INFERRED"},
{id:"C-CD-001-E5-1",cid:"C-CD-001",day:5,lat:1.6306,lng:18.0594,km:190,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Impfondo",loc:"Impfondo",score:0.491,precision:"PRECISE"},
// C-CF-001: Birao → Nyala
{id:"C-CF-001-E0-0",cid:"C-CF-001",day:0,lat:10.2833,lng:22.7833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Birao",loc:"Birao",score:0.368,precision:"PRECISE"},
{id:"C-CF-001-E4-2",cid:"C-CF-001",day:4,lat:11.8336,lng:24.6381,km:263,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Waypoint",loc:"Waypoint C-CF-001-7",score:0.474,precision:"SETTLEMENT"},
{id:"C-CF-001-E6-1",cid:"C-CF-001",day:6,lat:12.05,lng:24.8833,km:300,src:"IOM-DTM",type:"DISPLACEMENT",tag:"DISPLACEMENT: Nyala",loc:"Nyala",score:0.344,precision:"PRECISE"},
// C-GL-001: Goma → Gisenyi
{id:"C-GL-001-E0-0",cid:"C-GL-001",day:0,lat:-1.6833,lng:29.2333,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Goma",loc:"Goma",score:0.386,precision:"PRECISE"},
{id:"C-GL-001-E3-0",cid:"C-GL-001",day:3,lat:-1.6903,lng:29.2697,km:2,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Phantom Crossing",loc:"Phantom Crossing C-GL-001",score:0.478,precision:"INFERRED"},
// Additional signals for other corridors
{id:"C-CD-002-E0-0",cid:"C-CD-002",day:0,lat:4.2833,lng:21.0167,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Gbadolite",loc:"Gbadolite",score:0.371,precision:"PRECISE"},
{id:"C-ET-002-E0-0",cid:"C-ET-002",day:0,lat:4.1833,lng:42.0667,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Dolo Ado",loc:"Dolo Ado",score:0.203,precision:"PRECISE"},
{id:"C-SS-002-E0-0",cid:"C-SS-002",day:0,lat:11.7667,lng:32.7833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Renk",loc:"Renk",score:0.195,precision:"PRECISE"},
{id:"C-ET-003-E0-0",cid:"C-ET-003",day:0,lat:14.3,lng:36.6333,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Humera",loc:"Humera",score:0.368,precision:"PRECISE"},
{id:"C-NG-002-E0-0",cid:"C-NG-002",day:0,lat:11.0833,lng:13.6833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Gwoza",loc:"Gwoza",score:0.449,precision:"PRECISE"},
{id:"C-SD-001-E0-0",cid:"C-SD-001",day:0,lat:12.95,lng:36.2,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Metema",loc:"Metema",score:0.468,precision:"PRECISE"},
{id:"C-SS-003-E0-0",cid:"C-SS-003",day:0,lat:4.7667,lng:33.5833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Kapoeta",loc:"Kapoeta",score:0.255,precision:"PRECISE"},
{id:"C-NG-003-E0-0",cid:"C-NG-003",day:0,lat:11.05,lng:13.5833,km:0,src:"ACLED",type:"CONFLICT",tag:"CONFLICT: Banki",loc:"Banki",score:0.208,precision:"PRECISE"}
];
// Create Corridor Ribbons
function createCorridors() {
corridors.forEach(c => {
const coords = corridorPaths[c.id];
if (!coords) return;
// Create polyline
viewer.entities.add({
id: c.id,
name: c.name,
description: `<h3>${c.name}</h3>
<p><b>Risk:</b> ${c.risk} | <b>Distance:</b> ${c.distance}km | <b>Mode:</b> ${c.mode}</p>
<p><b>Corridor ID:</b> ${c.id}</p>
<p><b>Gap:</b> ${c.distance}km with 0% formal coverage</p>
<p><b>Canoe Required:</b> ${c.mode === 'canoe' ? 'Yes' : 'No'}</p>`,
polyline: {
positions: Cesium.Cartesian3.fromDegreesArrayHeights(coords),
width: c.width,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: c.glow,
color: RISK_COLORS[c.risk]
}),
clampToGround: false
}
});
});
}
// Create Nodes for all corridors
function createNodes() {
// Node data: [lat, lng, alt, type, name, corridorId, km, country, precision]
const nodes = [
// C-ET-001: Gambela → Malakal
[8.25, 34.5833, 50, 'START', 'Gambela', 'C-ET-001', 0, 'ET', 'PRECISE'],
[8.4008, 34.2081, 50, 'BORDER', 'Border Zone ET/SS', 'C-ET-001', 35, 'ET', 'SETTLEMENT'],
[8.5677, 33.8703, 50, 'WAYPOINT', 'Waypoint C-ET-001-2', 'C-ET-001', 70, 'ET', 'SETTLEMENT'],
[8.7387, 33.4757, 50, 'WAYPOINT', 'Waypoint C-ET-001-3', 'C-ET-001', 105, 'ET', 'SETTLEMENT'],
[8.9070, 33.1118, 50, 'PHANTOM', 'Phantom Crossing C-ET-001', 'C-ET-001', 140, 'SS', 'INFERRED'],
[9.0444, 32.7688, 50, 'WAYPOINT', 'Waypoint C-ET-001-5', 'C-ET-001', 175, 'SS', 'SETTLEMENT'],
[9.2253, 32.3657, 50, 'WAYPOINT', 'Waypoint C-ET-001-6', 'C-ET-001', 210, 'SS', 'SETTLEMENT'],
[9.3893, 32.0143, 50, 'WAYPOINT', 'Waypoint C-ET-001-7', 'C-ET-001', 245, 'SS', 'SETTLEMENT'],
[9.5333, 31.65, 50, 'END', 'Malakal', 'C-ET-001', 280, 'SS', 'PRECISE'],
// C-NG-001: Baga → Diffa
[12.8167, 13.7833, 50, 'START', 'Baga', 'C-NG-001', 0, 'NG', 'PRECISE'],
[12.9490, 13.4679, 50, 'BORDER', 'Border Zone NG/NE', 'C-NG-001', 37, 'NG', 'SETTLEMENT'],
[13.0500, 13.2232, 50, 'PHANTOM', 'Phantom Crossing C-NG-001', 'C-NG-001', 74, 'NE', 'INFERRED'],
[13.2123, 12.8940, 50, 'WAYPOINT', 'Waypoint C-NG-001-3', 'C-NG-001', 110, 'NE', 'SETTLEMENT'],
[13.3167, 12.6167, 50, 'END', 'Diffa', 'C-NG-001', 147, 'NE', 'PRECISE'],
// C-CD-001: Mbandaka → Impfondo
[-0.0478, 18.2558, 50, 'START', 'Mbandaka', 'C-CD-001', 0, 'CD', 'PRECISE'],
[0.3079, 18.2230, 50, 'BORDER', 'Border Zone CD/CG', 'C-CD-001', 38, 'CD', 'SETTLEMENT'],
[0.6325, 18.1575, 50, 'PHANTOM', 'Phantom Crossing C-CD-001', 'C-CD-001', 76, 'CD', 'INFERRED'],
[0.9764, 18.1304, 50, 'WAYPOINT', 'Waypoint C-CD-001-3', 'C-CD-001', 114, 'CG', 'SETTLEMENT'],
[1.2734, 18.0988, 50, 'WAYPOINT', 'Waypoint C-CD-001-4', 'C-CD-001', 152, 'CG', 'SETTLEMENT'],
[1.6306, 18.0594, 50, 'END', 'Impfondo', 'C-CD-001', 190, 'CG', 'PRECISE'],
// C-CF-001: Birao → Nyala
[10.2833, 22.7833, 50, 'START', 'Birao', 'C-CF-001', 0, 'CF', 'PRECISE'],
[10.5124, 23.0229, 50, 'BORDER', 'Border Zone CF/SD', 'C-CF-001', 38, 'CF', 'SETTLEMENT'],
[10.7105, 23.2891, 50, 'WAYPOINT', 'Waypoint C-CF-001-2', 'C-CF-001', 75, 'CF', 'SETTLEMENT'],
[10.9526, 23.5850, 50, 'WAYPOINT', 'Waypoint C-CF-001-3', 'C-CF-001', 113, 'CF', 'SETTLEMENT'],
[11.1632, 23.8500, 50, 'PHANTOM', 'Phantom Crossing C-CF-001', 'C-CF-001', 150, 'SD', 'INFERRED'],
[11.3950, 24.1049, 50, 'WAYPOINT', 'Waypoint C-CF-001-5', 'C-CF-001', 188, 'SD', 'SETTLEMENT'],
[11.6140, 24.3412, 50, 'WAYPOINT', 'Waypoint C-CF-001-6', 'C-CF-001', 225, 'SD', 'SETTLEMENT'],
[11.8336, 24.6381, 50, 'WAYPOINT', 'Waypoint C-CF-001-7', 'C-CF-001', 263, 'SD', 'SETTLEMENT'],
[12.05, 24.8833, 50, 'END', 'Nyala', 'C-CF-001', 300, 'SD', 'PRECISE'],
// C-GL-001: Goma → Gisenyi
[-1.6833, 29.2333, 50, 'START', 'Goma', 'C-GL-001', 0, 'CD', 'PRECISE'],
[-1.6903, 29.2697, 50, 'PHANTOM', 'Phantom Crossing C-GL-001', 'C-GL-001', 2, 'RW', 'INFERRED'],
[-1.7, 29.2667, 50, 'END', 'Gisenyi', 'C-GL-001', 4, 'RW', 'PRECISE'],
// C-CD-002: Gbadolite → Bangui
[4.2833, 21.0167, 50, 'START', 'Gbadolite', 'C-CD-002', 0, 'CD', 'PRECISE'],
[4.3157, 20.7319, 50, 'BORDER', 'Border Zone CD/CF', 'C-CD-002', 35, 'CD', 'SETTLEMENT'],
[4.2801, 20.3842, 50, 'WAYPOINT', 'Waypoint C-CD-002-2', 'C-CD-002', 70, 'CD', 'SETTLEMENT'],
[4.3306, 20.1108, 50, 'WAYPOINT', 'Waypoint C-CD-002-3', 'C-CD-002', 105, 'CD', 'SETTLEMENT'],
[4.3292, 19.7991, 50, 'PHANTOM', 'Phantom Crossing C-CD-002', 'C-CD-002', 140, 'CF', 'INFERRED'],
[4.3163, 19.5079, 50, 'WAYPOINT', 'Waypoint C-CD-002-5', 'C-CD-002', 175, 'CF', 'SETTLEMENT'],
[4.3542, 19.1945, 50, 'WAYPOINT', 'Waypoint C-CD-002-6', 'C-CD-002', 210, 'CF', 'SETTLEMENT'],
[4.3354, 18.8895, 50, 'WAYPOINT', 'Waypoint C-CD-002-7', 'C-CD-002', 245, 'CF', 'SETTLEMENT'],
[4.3667, 18.5833, 50, 'END', 'Bangui', 'C-CD-002', 280, 'CF', 'PRECISE'],
// C-ET-002: Dolo Ado → Baidoa
[4.1833, 42.0667, 50, 'START', 'Dolo Ado', 'C-ET-002', 0, 'ET', 'PRECISE'],
[3.9934, 42.3439, 50, 'BORDER', 'Border Zone ET/SO', 'C-ET-002', 35, 'ET', 'SETTLEMENT'],
[3.8294, 42.6012, 50, 'WAYPOINT', 'Waypoint C-ET-002-2', 'C-ET-002', 70, 'ET', 'SETTLEMENT'],
[3.6443, 42.8797, 50, 'PHANTOM', 'Phantom Crossing C-ET-002', 'C-ET-002', 105, 'SO', 'INFERRED'],
[3.4561, 43.1219, 50, 'WAYPOINT', 'Waypoint C-ET-002-4', 'C-ET-002', 140, 'SO', 'SETTLEMENT'],
[3.2831, 43.3886, 50, 'WAYPOINT', 'Waypoint C-ET-002-5', 'C-ET-002', 175, 'SO', 'SETTLEMENT'],
[3.1167, 43.65, 50, 'END', 'Baidoa', 'C-ET-002', 210, 'SO', 'PRECISE'],
// C-SS-002: Renk → Ed Damazin
[11.7667, 32.7833, 50, 'START', 'Renk', 'C-SS-002', 0, 'SS', 'PRECISE'],
[11.7647, 33.0720, 50, 'BORDER', 'Border Zone SS/SD', 'C-SS-002', 35, 'SS', 'SETTLEMENT'],
[11.7466, 33.3905, 50, 'PHANTOM', 'Phantom Crossing C-SS-002', 'C-SS-002', 70, 'SS', 'INFERRED'],
[11.7421, 33.7326, 50, 'WAYPOINT', 'Waypoint C-SS-002-3', 'C-SS-002', 105, 'SD', 'SETTLEMENT'],
[11.7580, 34.0386, 50, 'WAYPOINT', 'Waypoint C-SS-002-4', 'C-SS-002', 140, 'SD', 'SETTLEMENT'],
[11.7667, 34.35, 50, 'END', 'Ed Damazin', 'C-SS-002', 175, 'SD', 'PRECISE'],
// C-ET-003: Humera → Kassala
[14.3, 36.6333, 50, 'START', 'Humera', 'C-ET-003', 0, 'ET', 'PRECISE'],
[14.5750, 36.5867, 50, 'BORDER', 'Border Zone ET/SD', 'C-ET-003', 39, 'ET', 'SETTLEMENT'],
[14.8646, 36.5130, 50, 'PHANTOM', 'Phantom Crossing C-ET-003', 'C-ET-003', 78, 'SD', 'INFERRED'],
[15.1642, 36.4728, 50, 'WAYPOINT', 'Waypoint C-ET-003-3', 'C-ET-003', 116, 'SD', 'SETTLEMENT'],
[15.45, 36.4, 50, 'END', 'Kassala', 'C-ET-003', 155, 'SD', 'PRECISE'],
// C-NG-002: Gwoza → Mora
[11.0833, 13.6833, 50, 'START', 'Gwoza', 'C-NG-002', 0, 'NG', 'PRECISE'],
[11.0299, 13.8960, 50, 'PHANTOM', 'Phantom Crossing C-NG-002', 'C-NG-002', 26, 'CM', 'INFERRED'],
[11.0167, 14.1333, 50, 'END', 'Mora', 'C-NG-002', 52, 'CM', 'PRECISE'],
// C-SD-001: Metema → Gallabat
[12.95, 36.2, 50, 'START', 'Metema', 'C-SD-001', 0, 'SD', 'PRECISE'],
[12.9741, 36.1677, 50, 'PHANTOM', 'Phantom Crossing C-SD-001', 'C-SD-001', 4, 'ET', 'INFERRED'],
[12.9667, 36.15, 50, 'END', 'Gallabat', 'C-SD-001', 8, 'ET', 'PRECISE'],
// C-SS-003: Kapoeta → Lodwar
[4.7667, 33.5833, 50, 'START', 'Kapoeta', 'C-SS-003', 0, 'SS', 'PRECISE'],
[4.5274, 33.8537, 50, 'BORDER', 'Border Zone SS/KE', 'C-SS-003', 35, 'SS', 'SETTLEMENT'],
[4.3103, 34.1758, 50, 'WAYPOINT', 'Waypoint C-SS-003-2', 'C-SS-003', 70, 'SS', 'SETTLEMENT'],
[4.0643, 34.4249, 50, 'PHANTOM', 'Phantom Crossing C-SS-003', 'C-SS-003', 105, 'SS', 'INFERRED'],
[3.8198, 34.7470, 50, 'WAYPOINT', 'Waypoint C-SS-003-4', 'C-SS-003', 140, 'KE', 'SETTLEMENT'],
[3.6073, 35.0036, 50, 'WAYPOINT', 'Waypoint C-SS-003-5', 'C-SS-003', 175, 'KE', 'SETTLEMENT'],
[3.3284, 35.3191, 50, 'WAYPOINT', 'Waypoint C-SS-003-6', 'C-SS-003', 210, 'KE', 'SETTLEMENT'],
[3.1167, 35.6, 50, 'END', 'Lodwar', 'C-SS-003', 245, 'KE', 'PRECISE'],
// C-NG-003: Banki → Amchide
[11.05, 13.5833, 50, 'START', 'Banki', 'C-NG-003', 0, 'NG', 'PRECISE'],
[10.8955, 13.9212, 50, 'PHANTOM', 'Phantom Crossing C-NG-003', 'C-NG-003', 37, 'CM', 'INFERRED'],
[10.75, 14.2167, 50, 'END', 'Amchide', 'C-NG-003', 73, 'CM', 'PRECISE']
];
nodes.forEach(n => {
const [lat, lng, alt, type, name, cid, km, country, precision] = n;
let color, pixelSize, labelText, labelOffset, labelColor;
switch(type) {
case 'START':
color = Cesium.Color.LIME;
pixelSize = 12;
labelText = name;
labelOffset = -14;
labelColor = Cesium.Color.WHITE;
break;
case 'END':
color = Cesium.Color.RED;
pixelSize = 12;
labelText = name;
labelOffset = -14;
labelColor = Cesium.Color.WHITE;
break;
case 'PHANTOM':
color = Cesium.Color.GOLD;
pixelSize = 14;
labelText = name;
labelOffset = -16;
labelColor = Cesium.Color.GOLD;
break;
case 'BORDER':
color = Cesium.Color.ORANGE;
pixelSize = 6;
labelText = undefined;
break;
default: // WAYPOINT
color = Cesium.Color.WHITE.withAlpha(0.6);
pixelSize = 6;
labelText = undefined;
}
viewer.entities.add({
id: `${cid}-${type}-${km}`,
name: name,
description: `<b>Type:</b> ${type}<br>
<b>Corridor:</b> ${cid}<br>
<b>KM:</b> ${km}<br>
<b>Country:</b> ${country}<br>
<b>Precision:</b> ${precision}<br>
${type === 'PHANTOM' ? '<b style="color:gold">⚡ PHANTOM POE — No formal border post</b>' : ''}`,
position: Cesium.Cartesian3.fromDegrees(lng, lat, alt),
point: {
pixelSize: pixelSize,
color: color,
outlineColor: Cesium.Color.BLACK,
outlineWidth: type === 'PHANTOM' ? 2 : 1,
scaleByDistance: type === 'PHANTOM' ? new Cesium.NearFarScalar(1000, 2.0, 500000, 0.8) : undefined
},
label: labelText ? {
text: labelText,
font: type === 'PHANTOM' ? 'bold 13pt sans-serif' : '11pt sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, labelOffset),
fillColor: labelColor,
outlineColor: Cesium.Color.BLACK,
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 300000)
} : undefined
});
});
}
// Cascade Animation System
let cascadeEntities = [];
let cascadeFrame = 0;
let cascadePlaying = false;
let cascadeCorridor = null;
let cascadeInterval = null;
function toggleCascadeMenu() {
const menu = document.getElementById('cascade-menu');
menu.classList.toggle('hidden');
}
function startCascade(corridorId) {
// Hide menu
document.getElementById('cascade-menu').classList.add('hidden');
// Clear previous cascade
clearCascade();
// Get corridor info
const corridor = corridors.find(c => c.id === corridorId);
if (!corridor) return;
cascadeCorridor = corridorId;
cascadeFrame = 0;
// Filter and sort evidence for this corridor
const corridorEvidence = EVIDENCE
.filter(e => e.cid === corridorId)
.sort((a, b) => a.day - b.day || a.km - b.km);
// If no specific evidence, generate synthetic signals
const signals = corridorEvidence.length > 0 ? corridorEvidence : generateSyntheticSignals(corridorId);
// Group by day
const days = {};
signals.forEach(s => {
if (!days[s.day]) days[s.day] = [];
days[s.day].push(s);
});
const sortedDays = Object.keys(days).map(Number).sort((a, b) => a - b);
const totalDays = sortedDays.length;
// Show info panel
const info = document.getElementById('cascade-info');
info.classList.remove('hidden');
document.getElementById('cascade-corridor-name').textContent = corridor.name;
document.getElementById('cascade-day').textContent = `0/${totalDays}`;
document.getElementById('cascade-score').textContent = '0.000';
document.getElementById('cascade-progress').style.width = '0%';
// Fly to corridor
flyToCorridor(corridorId);
let cumulativeScore = 0;
let phantomDetected = false;
cascadePlaying = true;
cascadeInterval = setInterval(() => {
if (cascadeFrame >= sortedDays.length) {
stopCascade();
return;
}
const day = sortedDays[cascadeFrame];
const daySignals = days[day];
const sources = new Set();
daySignals.forEach(sig => {
sources.add(sig.src);
cumulativeScore = Math.min(1, cumulativeScore + sig.score * 0.08);
// Create signal marker
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(sig.lng, sig.lat, 200),
point: {
pixelSize: 10 + sig.score * 15,
color: SOURCE_COLORS[sig.src] || Cesium.Color.WHITE,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
scaleByDistance: new Cesium.NearFarScalar(500, 2.5, 200000, 0.7)
},
label: {
text: sig.tag,
font: 'bold 12pt sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -20),
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 150000),
showBackground: true,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.7)
}
});
cascadeEntities.push(entity);
});
// Phantom POE detection moment
if (sources.size >= 2 && cumulativeScore > 0.3 && !phantomDetected) {
phantomDetected = true;
const pivotSig = daySignals[0];
const canvas = createPhantomPOECanvas();
const billboardOpts = canvas ? {
image: canvas,
width: 64,
height: 64,
scaleByDistance: new Cesium.NearFarScalar(500, 3, 300000, 0.5)
} : undefined;
const phantomEntity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(pivotSig.lng, pivotSig.lat, 500),
billboard: billboardOpts,
label: {
text: '⚡ PHANTOM POE DETECTED',
font: 'bold 16pt sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 3,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -40),
fillColor: Cesium.Color.GOLD,
outlineColor: Cesium.Color.BLACK,
showBackground: true,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.85)
}
});
cascadeEntities.push(phantomEntity);
}
// Update UI
document.getElementById('cascade-day').textContent = `${cascadeFrame + 1}/${totalDays}`;
document.getElementById('cascade-score').textContent = cumulativeScore.toFixed(3);
document.getElementById('cascade-progress').style.width = `${((cascadeFrame + 1) / totalDays) * 100}%`;
cascadeFrame++;
}, 2000); // 2 seconds per day
}
function generateSyntheticSignals(cid) {
// Generate synthetic signals if no real data
const path = corridorPaths[cid];
const signals = [];
if (!path || path.length < 3) return signals;
const numPoints = Math.floor(path.length / 3);
const corridor = corridors.find(c => c.id === cid);
const maxDist = corridor ? corridor.distance : 280;
for (let i = 0; i < 7; i++) { // 7 days
const idx = Math.min(Math.floor((i / 6) * (numPoints - 1)), numPoints - 1);
const lat = path[idx * 3 + 1];
const lng = path[idx * 3];
const km = Math.floor((i / 6) * maxDist);
const sources = ['ACLED', 'IOM-DTM', 'DHIS2'];
const types = ['CONFLICT', 'DISPLACEMENT', 'HEALTH'];
if (lat && lng) {
signals.push({
cid: cid,
day: i,
lat: lat,
lng: lng,
km: km,
src: sources[i % 3],
type: types[i % 3],
tag: `${types[i % 3]}: Day ${i}`,
loc: `Waypoint ${i}`,
score: 0.3 + Math.random() * 0.4,
precision: i === 0 ? 'PRECISE' : (i === 6 ? 'PRECISE' : 'SETTLEMENT')
});
}
}
return signals;
}
function stopCascade() {
cascadePlaying = false;
if (cascadeInterval) clearInterval(cascadeInterval);
cascadeInterval = null;
}
function clearCascade() {
stopCascade();
cascadeEntities.forEach(e => viewer.entities.remove(e));
cascadeEntities = [];
cascadeFrame = 0;
document.getElementById('cascade-info').classList.add('hidden');
}
function createPhantomPOECanvas() {
try {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
if (!ctx) return null;
// Gold glow
const grad = ctx.createRadialGradient(32, 32, 5, 32, 32, 30);
grad.addColorStop(0, 'rgba(245, 158, 11, 1)');
grad.addColorStop(0.5, 'rgba(245, 158, 11, 0.5)');
grad.addColorStop(1, 'rgba(245, 158, 11, 0)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 64, 64);
// Lightning bolt
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 36px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('⚡', 32, 32);
return canvas;
} catch (e) {
console.error('Error creating canvas:', e);
return null;
}
}
// Fly to functions
function flyToCorridor(id) {
const coords = corridorPaths[id];
if (!coords) return;
// Calculate center
let sumLat = 0, sumLng = 0, count = 0;
for (let i = 0; i < coords.length; i += 3) {
sumLng += coords[i];
sumLat += coords[i + 1];
count++;
}
const centerLat = sumLat / count;
const centerLng = sumLng / count;
// Calculate appropriate height based on corridor length
const c = corridors.find(x => x.id === id);
const height = c.distance * 50; // Rough estimation
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(centerLng, centerLat, height),
orientation: { heading: 0, pitch: Cesium.Math.toRadians(-55), roll: 0 },
duration: 2
});
}
function resetView() {
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(25, 5, 5000000),
orientation: { heading: 0, pitch: Cesium.Math.toRadians(-90), roll: 0 },
duration: 2
});
}
// Initialize
createCorridors();
createNodes();
// Initial view
resetView();
// Click outside to close cascade menu
document.addEventListener('click', (e) => {
const menu = document.getElementById('cascade-menu');
const btn = e.target.closest('button');
let isToggleBtn = false;
if (btn && btn.onclick && typeof btn.onclick === 'function') {
const fnString = btn.onclick.toString();
isToggleBtn = fnString.includes('toggleCascadeMenu');
}
if (!menu.contains(e.target) && !isToggleBtn) {
menu.classList.add('hidden');
}
});
// Initialize icons after a short delay to ensure DOM is ready
setTimeout(() => {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
}, 100);
console.log('PHANTOM POE MAP initialized: 14 corridors, 91 nodes ready');
</script>
</body>
</html>