(function(){
var nodes=[],canvas,ctx,animFrame=null,linkOffset=0;
var zoom=1,panX=0,panY=0,isPanning=false,panStartX,panStartY,panOriginX,panOriginY;
var nodeEls={};
var draggingNode=false;
var ROLE_COLORS={origin:'#818cf8',shield:'#fbbf24',dns:'#60a5fa',proxy:'#a78bfa',filter:'#f472b6',payment:'#4ade80',email:'#22d3ee',other:'#9ca3af'};
var ROLE_SVG={dns:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#60a5fa" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>',proxy:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#a78bfa" stroke-width="2"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>',filter:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#f472b6" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>',shield:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fbbf24" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',origin:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#818cf8" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>',payment:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#4ade80" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>',email:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#22d3ee" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>',other:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v16"/></svg>'};
var ROLE_LAYER={dns:1,proxy:2,filter:3,shield:4,origin:5,payment:6,email:7};
var CSRF=(document.querySelector('meta[name="csrf-token"]')||{}).content||'';
var TOPO_API='api/fleet/topology.php';
var PING_SVG='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>';
function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
function toScreenX(x){return panX+scaleX(x||410)*zoom;}
function toScreenY(y){return panY+scaleY(y||220)*zoom;}
function scaleX(x){return canvas?Math.max(70,Math.min(canvas.width-70,(x/820)*canvas.width)):x;}
function scaleY(y){return canvas?Math.max(50,Math.min(canvas.height-50,(y/440)*canvas.height)):y;}
function nodeIdPrefix(n){
var layer=ROLE_LAYER[n.node_role]||'?';
var nid=n.node_id||'';
if(nid.length>=1&&nid[0]==String(layer))return nid;
return layer+'xxxxx';
}
function topoLoad(){
fetch(TOPO_API+'?action=list',{credentials:'same-origin'})
.then(function(r){
if(!r.ok)throw new Error('API returned '+r.status);
return r.json();
}).then(function(d){
if(d.error){void 0;return;}
nodes=d.nodes||[];
startAnimation();
renderNodes();
renderTable();
renderStats();
populateStandbySelect(nodes);
}).catch(function(err){void 0;});
}
function renderStats(){
var up=nodes.filter(function(n){return n.is_reachable==1;}).length;
var cost=nodes.reduce(function(s,n){return s+parseFloat(n.monthly_cost_cad||0);},0);
var standbyCount=nodes.filter(function(n){return n.standby_ready==1;}).length;
var el=document.getElementById('topo-stats');
if(!el)return;
el.innerHTML=
'<div class="topo-stat"><div class="topo-stat-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg></div><b>'+nodes.length+'</b><span>Total Nodes</span></div>'+
'<div class="topo-stat"><div class="topo-stat-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg></div><b style="color:#22c55e">'+up+'</b><span>Online</span></div>'+
'<div class="topo-stat"><div class="topo-stat-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><circle cx="12" cy="12" r="10" stroke-dasharray="4 2"/></svg></div><b style="color:#ef4444">'+(nodes.length-up)+'</b><span>Offline</span></div>'+
'<div class="topo-stat"><div class="topo-stat-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg></div><b>$'+cost.toFixed(2)+'</b><span>CAD / month</span></div>'+
'<div class="topo-stat"><div class="topo-stat-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg></div><b>'+standbyCount+'</b><span>Standby</span></div>';
}
function startAnimation(){
if(animFrame)cancelAnimationFrame(animFrame);
initCanvas();
var lastW=0,lastH=0;
function frame(){
try{
var wrap=document.querySelector('.topo-map-wrap');
if(wrap){
var r=wrap.getBoundingClientRect();
if(Math.abs(r.width-lastW)>1||Math.abs(r.height-lastH)>1){
lastW=r.width;lastH=r.height;
canvas=document.getElementById('topo-canvas');
if(canvas){canvas.width=lastW;canvas.height=lastH;}
ctx=canvas?canvas.getContext('2d'):null;
for(var id in nodeEls){if(nodeEls.hasOwnProperty(id)){
var nn=nodes.find(function(n){return n.id==id;});
if(nn)syncNodePosition(nodeEls[id],nn);
}}
}
}
if(ctx){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawGrid();
drawLinks();
}
}catch(e){void 0;}
linkOffset+=0.3;
if(linkOffset>16)linkOffset=0;
animFrame=requestAnimationFrame(frame);
}
frame();
}
function initCanvas(){
var wrap=document.querySelector('.topo-map-wrap');
if(!wrap)return;
var rect=wrap.getBoundingClientRect();
canvas=document.getElementById('topo-canvas');
if(!canvas)return;
canvas.width=rect.width;canvas.height=rect.height;
ctx=canvas.getContext('2d');
}
function drawGrid(){
var w=canvas.width/zoom,h=canvas.height/zoom;
ctx.save();
ctx.translate(panX,panY);
ctx.scale(zoom,zoom);
ctx.strokeStyle='rgba(45,106,79,.06)';
ctx.lineWidth=1;
for(var x=0;x<canvas.width;x+=32){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,canvas.height);ctx.stroke();}
for(var y=0;y<canvas.height;y+=32){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(canvas.width,y);ctx.stroke();}
var cx=canvas.width/2,cy=canvas.height/2;
var grd=ctx.createRadialGradient(cx,cy,0,cx,cy,Math.max(canvas.width,canvas.height)*0.5);
grd.addColorStop(0,'rgba(45,106,79,.04)');grd.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle=grd;ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.restore();
}
function drawLinks(){
ctx.save();
ctx.translate(panX,panY);
ctx.scale(zoom,zoom);
nodes.forEach(function(n){
if(!n.wg_peers)return;
var peers=(n.wg_peers||'').split(',').map(function(p){return p.trim().toLowerCase();});
peers.forEach(function(peer){
var target=nodes.find(function(t){return(t.node_name&&t.node_name.toLowerCase()===peer)||(t.node_role&&t.node_role.toLowerCase()===peer);});
if(!target||target.id<=n.id)return;
var sx=scaleX(n.canvas_x),sy=scaleY(n.canvas_y);
var ex=scaleX(target.canvas_x),ey=scaleY(target.canvas_y);
var c1=ROLE_COLORS[n.node_role]||'#374151';
var c2=ROLE_COLORS[target.node_role]||'#374151';
var grad=ctx.createLinearGradient(sx,sy,ex,ey);
grad.addColorStop(0,c1+'90');grad.addColorStop(0.5,c1+'40');grad.addColorStop(1,c2+'90');
ctx.strokeStyle=grad;
ctx.lineWidth=2;
ctx.setLineDash([6,6]);
ctx.lineDashOffset=-linkOffset;
ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(ex,ey);ctx.stroke();
ctx.setLineDash([]);
var glow=ctx.createLinearGradient(sx,sy,ex,ey);
glow.addColorStop(0,c1+'15');glow.addColorStop(1,c2+'15');
ctx.strokeStyle=glow;
ctx.lineWidth=8;
ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(ex,ey);ctx.stroke();
var mx=(sx+ex)/2,my=(sy+ey)/2;
var angle=Math.atan2(ey-sy,ex-sx);
ctx.fillStyle=c2+'cc';
ctx.beginPath();
ctx.moveTo(mx+Math.cos(angle)*6,my+Math.sin(angle)*6);
ctx.lineTo(mx+Math.cos(angle+2.5)*5,my+Math.sin(angle+2.5)*5);
ctx.lineTo(mx+Math.cos(angle-2.5)*5,my+Math.sin(angle-2.5)*5);
ctx.closePath();ctx.fill();
});
});
ctx.restore();
}
function renderNodes(){
var layer=document.getElementById('topo-nodes-layer');
if(!layer)return;
layer.innerHTML='';
nodeEls={};
nodes.forEach(function(n){
var status=n.is_reachable==1?'up':'unknown';
var role=n.node_role||'other';
var color=ROLE_COLORS[role]||ROLE_COLORS.other;
var svg=ROLE_SVG[role]||ROLE_SVG.other;
var nid=nodeIdPrefix(n);
var latency=n.latency_ms?n.latency_ms+'ms':'\u2014';
var el=document.createElement('div');
el.className='topo-node';
el.dataset.id=n.id;
el.style.left=toScreenX(n.canvas_x)+'px';
el.style.top=toScreenY(n.canvas_y)+'px';
el.style.transform='translate(-50%,-50%)';
el.innerHTML='<div class="topo-node-inner '+status+'" style="--node-color:'+color+'">'+
'<div class="topo-node-head">'+
'<span class="topo-node-icon">'+svg+'</span>'+
'<span class="topo-node-dot '+status+'"></span>'+
'</div>'+
'<div class="topo-node-role">'+esc(role)+'</div>'+
'<div class="topo-node-name">'+esc(n.node_name||n.node_role)+'</div>'+
'<div class="topo-node-id">'+esc(nid)+'</div>'+
'<div class="topo-node-ip">'+(n.wg_ip?esc(n.wg_ip):'')+'</div>'+
'<div class="topo-node-latency">'+latency+'</div>'+
'</div>';
el.style.pointerEvents='all';
el.addEventListener('click',function(){topoEdit(n);});
makeDraggable(el,n);
layer.appendChild(el);
nodeEls[n.id]=el;
});
}
function syncNodePosition(el,n){
el.style.left=toScreenX(n.canvas_x)+'px';
el.style.top=toScreenY(n.canvas_y)+'px';
}
function makeDraggable(el,node){
var isDrag=false,startX,startY,origX,origY;
el.addEventListener('mousedown',function(e){
if(draggingNode)return;
draggingNode=true;
isDrag=false;
startX=e.clientX;startY=e.clientY;
origX=node.canvas_x||410;origY=node.canvas_y||220;
e.stopPropagation();
function onMove(e){
if(Math.abs(e.clientX-startX)>3||Math.abs(e.clientY-startY)>3)isDrag=true;
if(!isDrag)return;
var rect=canvas.getBoundingClientRect();
var relX=(e.clientX-rect.left-panX)/zoom;
var relY=(e.clientY-rect.top-panY)/zoom;
var nx=Math.max(70,Math.min(820-70,relX/rect.width*820));
var ny=Math.max(50,Math.min(440-50,relY/rect.height*440));
node.canvas_x=Math.round(nx);
node.canvas_y=Math.round(ny);
syncNodePosition(el,node);
}
function onUp(){
document.removeEventListener('mousemove',onMove);
document.removeEventListener('mouseup',onUp);
draggingNode=false;
if(isDrag)saveCanvasPositions();
}
document.addEventListener('mousemove',onMove);
document.addEventListener('mouseup',onUp);
e.preventDefault();
});
}
function saveCanvasPositions(){
fetch(TOPO_API+'?action=update_canvas',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({positions:nodes.map(function(n){return{id:n.id,x:n.canvas_x,y:n.canvas_y};})})
});
}
function renderTable(){
var tbody=document.getElementById('topo-tbody');
if(!tbody)return;
if(!nodes.length){tbody.innerHTML='<tr><td colspan="12" class="loading-row">No nodes registered.</td></tr>';return;}
tbody.innerHTML=nodes.map(function(n){
var status=n.is_reachable==1?'up':'unknown';
var standby=nodes.find(function(s){return s.id==n.standby_node_id;});
var nid=nodeIdPrefix(n);
var layerNum=ROLE_LAYER[n.node_role]||'?';
return '<tr>'+
'<td><span class="status-dot '+status+'"></span>'+(status==='up'?'Online':'&mdash;')+'</td>'+
'<td><code class="topo-nid" style="color:'+ROLE_COLORS[n.node_role]+'">'+esc(nid)+'</code><br><small style="color:rgba(200,204,212,.3)">Layer '+layerNum+'</small></td>'+
'<td><b>'+esc(n.node_name)+'</b><br><small style="color:#6b7280">'+esc(n.public_ip||'')+'</small></td>'+
'<td><span class="badge-role badge-'+n.node_role+'">'+esc(n.node_role)+'</span></td>'+
'<td>'+esc((n.location_city||'')+(n.location_country?' ('+n.location_country+')':''))+'<br><small>'+esc(n.datacenter||'')+'</small></td>'+
'<td>'+esc(n.provider||'\u2014')+'</td>'+
'<td><small>'+(n.cpu_cores?n.cpu_cores+'vCPU ':'')+(n.ram_gb?n.ram_gb+'GB ':'')+(n.disk_gb?n.disk_gb+'GB':'')+'</small><br><small style="color:#6b7280">'+esc(n.os||'')+'</small></td>'+
'<td>'+(n.monthly_cost_cad?'$'+parseFloat(n.monthly_cost_cad).toFixed(2)+' CAD':'\u2014')+'</td>'+
'<td>'+esc(n.payment_method||'\u2014')+'</td>'+
'<td>'+esc(n.renewal_date||'\u2014')+'</td>'+
'<td>'+(standby?'<span style="color:#22c55e">&check; '+esc(standby.node_name)+(n.standby_ready?' (Ready)':'')+'</span>':'\u2014')+'</td>'+
'<td><button class="btn-ghost btn-xs" data-topo-action="edit" data-node-id="'+n.id+'">Edit</button></td>'+
'</tr>';
}).join('');
}
function populateStandbySelect(nodes){
var sel=document.getElementById('tf-standby');
if(!sel)return;
var cur=sel.value;
sel.innerHTML='<option value="">None</option>';
nodes.forEach(function(n){sel.innerHTML+='<option value="'+n.id+'">'+esc(n.node_name)+' ('+n.node_role+')</option>';});
if(cur)sel.value=cur;
}
function topoShowAdd(){
document.getElementById('tf-id').value='';
document.getElementById('topo-modal-title').textContent='Add Node';
document.getElementById('topo-delete-btn').style.display='none';
['name','pubip','wgip','provider','dc','city','country','os','notes','peers','pay','renew'].forEach(function(f){
var el=document.getElementById('tf-'+f);if(el)el.value='';
});
['ssh'].forEach(function(f){var el=document.getElementById('tf-'+f);if(el)el.value='33022';});
['cpu','ram','disk','cost','api'].forEach(function(f){var el=document.getElementById('tf-'+f);if(el)el.value='';});
document.getElementById('tf-role').value='origin';
document.getElementById('tf-standby').value='';
document.getElementById('topo-modal').style.display='flex';
}
function topoEdit(n){
document.getElementById('tf-id').value=n.id;
document.getElementById('topo-modal-title').textContent='Edit \u2014 '+n.node_name+' ['+nodeIdPrefix(n)+']';
document.getElementById('topo-delete-btn').style.display='block';
document.getElementById('tf-name').value=n.node_name||'';
document.getElementById('tf-role').value=n.node_role||'origin';
document.getElementById('tf-pubip').value=n.public_ip||'';
document.getElementById('tf-wgip').value=n.wg_ip||'';
document.getElementById('tf-ssh').value=n.ssh_port||33022;
document.getElementById('tf-api').value=n.api_port||'';
document.getElementById('tf-provider').value=n.provider||'';
document.getElementById('tf-dc').value=n.datacenter||'';
document.getElementById('tf-city').value=n.location_city||'';
document.getElementById('tf-country').value=n.location_country||'';
document.getElementById('tf-os').value=n.os||'';
document.getElementById('tf-cpu').value=n.cpu_cores||'';
document.getElementById('tf-ram').value=n.ram_gb||'';
document.getElementById('tf-disk').value=n.disk_gb||'';
document.getElementById('tf-cost').value=n.monthly_cost_cad||'';
document.getElementById('tf-pay').value=n.payment_method||'';
document.getElementById('tf-renew').value=n.renewal_date||'';
document.getElementById('tf-peers').value=n.wg_peers||'';
document.getElementById('tf-notes').value=n.notes||'';
document.getElementById('tf-standby').value=n.standby_node_id||'';
document.getElementById('topo-modal').style.display='flex';
}
function topoSave(){
var payload={
_csrf:CSRF,
id:document.getElementById('tf-id').value,
node_name:document.getElementById('tf-name').value,
node_role:document.getElementById('tf-role').value,
public_ip:document.getElementById('tf-pubip').value,
wg_ip:document.getElementById('tf-wgip').value,
ssh_port:document.getElementById('tf-ssh').value,
api_port:document.getElementById('tf-api').value,
provider:document.getElementById('tf-provider').value,
datacenter:document.getElementById('tf-dc').value,
location_city:document.getElementById('tf-city').value,
location_country:document.getElementById('tf-country').value,
os:document.getElementById('tf-os').value,
cpu_cores:document.getElementById('tf-cpu').value,
ram_gb:document.getElementById('tf-ram').value,
disk_gb:document.getElementById('tf-disk').value,
monthly_cost_cad:document.getElementById('tf-cost').value,
payment_method:document.getElementById('tf-pay').value,
renewal_date:document.getElementById('tf-renew').value,
wg_peers:document.getElementById('tf-peers').value,
notes:document.getElementById('tf-notes').value,
standby_node_id:document.getElementById('tf-standby').value,
};
fetch(TOPO_API+'?action=save',{
method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)
}).then(function(r){return r.json();}).then(function(d){
if(d.success){topoCloseModal();topoLoad();}
else if(typeof Systems!=='undefined')Systems.notify(d.error||'Save failed','error');
else alert('Error: '+d.error);
});
}
function topoDelete(){
if(!confirm('Deactivate this node?'))return;
fetch(TOPO_API+'?action=delete',{
method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({_csrf:CSRF,id:document.getElementById('tf-id').value})
}).then(function(r){return r.json();}).then(function(){topoCloseModal();topoLoad();});
}
function topoPing(){
var btn=document.getElementById('topo-ping-btn');
btn.innerHTML='Pinging&hellip;';btn.disabled=true;
fetch(TOPO_API+'?action=reachability')
.then(function(r){return r.json();}).then(function(){btn.innerHTML=PING_SVG+' Ping All';btn.disabled=false;topoLoad();});
}
function topoCloseModal(){document.getElementById('topo-modal').style.display='none';}
var wsEl=document.getElementById('ws-topology');
if(wsEl){
wsEl.addEventListener('click',function(e){
var btn=e.target.closest('[data-topo-action]');
if(!btn)return;
var action=btn.dataset.topoAction;
if(action==='ping')topoPing();
else if(action==='refresh')topoLoad();
else if(action==='add')topoShowAdd();
else if(action==='close-modal')topoCloseModal();
else if(action==='save')topoSave();
else if(action==='delete')topoDelete();
else if(action==='edit'){
var nodeId=btn.dataset.nodeId;
var node=nodes.find(function(n){return n.id==nodeId;});
if(node)topoEdit(node);
}
});
} else { void 0; }

/* --- Zoom + Pan --- */
var mapWrap=document.querySelector('.topo-map-wrap');
if(mapWrap){
mapWrap.addEventListener('wheel',function(e){
if(e.target.closest('.topo-node'))return;
e.preventDefault();
var oldZ=zoom;
var d=e.deltaY>0?0.88:1.12;
zoom=Math.max(0.25,Math.min(5.0,zoom*d));
var rect=mapWrap.getBoundingClientRect();
var mx=e.clientX-rect.left;
var my=e.clientY-rect.top;
panX=mx-(mx-panX)*(zoom/oldZ);
panY=my-(my-panY)*(zoom/oldZ);
for(var id in nodeEls){if(nodeEls.hasOwnProperty(id)){
var nn=nodes.find(function(n){return n.id==id;});
if(nn)syncNodePosition(nodeEls[id],nn);
}}
},{passive:false});

mapWrap.addEventListener('mousedown',function(e){
if(e.target.closest('.topo-node')||e.target.closest('.topo-map-legend'))return;
isPanning=true;
panStartX=e.clientX;
panStartY=e.clientY;
panOriginX=panX;
panOriginY=panY;
mapWrap.style.cursor='grabbing';
function pm(e){
if(!isPanning)return;
panX=panOriginX+(e.clientX-panStartX);
panY=panOriginY+(e.clientY-panStartY);
for(var id in nodeEls){if(nodeEls.hasOwnProperty(id)){
var nn=nodes.find(function(n){return n.id==id;});
if(nn)syncNodePosition(nodeEls[id],nn);
}}
}
function pu(){isPanning=false;mapWrap.style.cursor='';document.removeEventListener('mousemove',pm);document.removeEventListener('mouseup',pu);}
document.addEventListener('mousemove',pm);
document.addEventListener('mouseup',pu);
});
}

window.addEventListener('resize',function(){
if(nodes.length&&!draggingNode){
initCanvas();
for(var id in nodeEls){if(nodeEls.hasOwnProperty(id)){
var nn=nodes.find(function(n){return n.id==id;});
if(nn)syncNodePosition(nodeEls[id],nn);
}}
}
});

topoLoad();
})();
