SC CODE: Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("var_header_name", "index_single.html")
31 STORE("var_header_description", "")
32 STORE("var_header_icon", "")
33 STORE("dURL", "")
34 STORE("docType", "TELA-HTML-1")
35 STORE("subDir", "/")
36 STORE("fileCheckC", "2895784b97d7a22e7f65f63420c8aeed48d26e5367460ef03803ffe6afcc07f8")
37 STORE("fileCheckS", "03e80fa6d60c943a1c516578edd8b3edea1fb297086275c023776bf85d705c99")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LinkTracer -- Uncovers the Truth</title>
<style>*{box-sizing:border-box;margin:0;padding:0}html,body{height:100%;background:#040d1a;color:rgba(255,255,255,0.88);font-family:system-ui,sans-serif;overflow:hidden}.app{display:flex;height:100vh;flex-direction:column}.header{display:flex;align-items:center;justify-content:space-between;padding:0 24px;height:64px;border-bottom:1px solid rgba(255,255,255,0.08);background:rgba(4,7,21,0.9);flex-shrink:0}.logo{font-size:22px;font-weight:800;letter-spacing:0.1em;color:rgba(255,255,255,0.9)}.logo span{color:#e07b39}.nav{display:flex;gap:24px}.nav button{background:none;border:none;color:rgba(255,255,255,0.5);font-size:12px;font-weight:600;letter-spacing:0.12em;text-transform:uppercase;cursor:pointer;padding:4px 0}.nav button.active{color:rgba(255,255,255,0.9);border-bottom:2px solid #e07b39}.canvas{flex:1;position:relative;overflow:hidden}.empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px}.search-wrap{width:100%;max-width:600px;padding:0 24px}.search-box{display:flex;align-items:center;gap:12px;padding:16px 20px;border:1px solid rgba(224,123,57,0.3);border-radius:12px;background:rgba(255,255,255,0.04)}.search-box input{flex:1;background:none;border:none;outline:none;font-size:18px;color:rgba(255,255,255,0.88);caret-color:#e07b39}.hint{font-size:11px;letter-spacing:0.2em;text-transform:uppercase;color:rgba(255,255,255,0.25)}.suggestions{position:absolute;top:100%;left:0;right:0;margin-top:6px;background:rgba(4,7,21,0.97);border:1px solid rgba(255,255,255,0.1);border-radius:12px;overflow:hidden;z-index:50}.sug-item{padding:12px 16px;display:flex;align-items:center;gap:12px;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.05)}.sug-item:hover{background:rgba(255,255,255,0.05)}.sug-av{width:32px;height:32px;border-radius:8px;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:rgba(255,255,255,0.5);flex-shrink:0}.sug-name{font-size:13px;font-weight:600}.sug-type{font-size:10px;color:rgba(255,255,255,0.4)}.card{position:absolute;width:160px;border-radius:10px;border:1px solid rgba(255,255,255,0.11);background:rgba(255,255,255,0.055);cursor:pointer;transition:border-color 0.2s;user-select:none}.card:hover,.card.sel{border-color:rgba(224,123,57,0.4);background:rgba(224,123,57,0.07)}.card-img{width:100%;height:100px;background:linear-gradient(135deg,#1e2a3a,#0f1a2e);display:flex;align-items:center;justify-content:center;border-radius:9px 9px 0 0;overflow:hidden;font-size:24px;font-weight:700;color:rgba(255,255,255,0.2)}.card-body{padding:8px 10px}.card-name{font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card-role{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:0.1em;color:rgba(255,255,255,0.35);margin-top:2px}.panel{position:fixed;right:0;top:64px;width:360px;height:calc(100vh - 64px);background:rgba(8,16,36,0.92);border-left:1px solid rgba(255,255,255,0.08);backdrop-filter:blur(20px);transform:translateX(100%);transition:transform 0.3s;display:flex;flex-direction:column;z-index:60}.panel.open{transform:translateX(0)}.panel-head{padding:16px 20px;border-bottom:1px solid rgba(255,255,255,0.07);display:flex;align-items:center;justify-content:space-between}.panel-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:rgba(255,255,255,0.4)}.close-btn{background:none;border:none;color:rgba(255,255,255,0.3);cursor:pointer;font-size:18px;line-height:1}.panel-body{flex:1;overflow-y:auto;padding:20px}.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.08em}.badge-v{background:rgba(34,197,94,0.12);border:1px solid rgba(34,197,94,0.4);color:rgba(34,197,94,0.9)}.badge-c{background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.4);color:rgba(245,158,11,0.9)}.badge-u{background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.4);color:rgba(239,68,68,0.9)}.conf{font-size:12px;color:rgba(255,255,255,0.45);font-family:monospace}.edge-label{font-size:20px;font-weight:700;color:rgba(255,255,255,0.92);margin:8px 0 4px}.edge-sub{font-size:13px;color:rgba(255,255,255,0.45);margin-bottom:16px}.desc-box{padding:14px;border-radius:8px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.04);font-size:13px;color:rgba(255,255,255,0.75);line-height:1.7;font-style:italic;margin-bottom:16px}.sources-title{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:rgba(255,255,255,0.35);margin-bottom:10px}.source-item{padding:10px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);margin-bottom:6px}.source-title{font-size:13px;font-weight:600;color:rgba(255,255,255,0.85)}.source-meta{font-size:10px;color:rgba(255,255,255,0.4);font-family:monospace;margin-top:3px}.node-av{width:56px;height:56px;border-radius:10px;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:rgba(255,255,255,0.4);flex-shrink:0}.node-info{margin-left:14px}.node-type{font-size:10px;padding:2px 6px;border-radius:3px;border:1px solid rgba(255,255,255,0.15);color:rgba(255,255,255,0.5);text-transform:uppercase;font-size:9px;font-weight:600;letter-spacing:0.08em}.node-name{font-size:22px;font-weight:700;margin-top:6px;line-height:1.2}.node-role{font-size:13px;color:rgba(255,255,255,0.5);margin-top:4px}.node-desc{font-size:13px;color:rgba(255,255,255,0.72);line-height:1.7;margin-top:14px}.tip{font-size:11px;color:rgba(255,255,255,0.28);margin-top:14px}.toolbar{position:absolute;bottom:20px;right:20px;display:flex;gap:0;border-radius:8px;overflow:hidden;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05)}.tb-btn{padding:8px 16px;background:none;border:none;border-right:1px solid rgba(255,255,255,0.08);color:rgba(255,255,255,0.4);font-size:11px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;cursor:pointer}.tb-btn:last-child{border-right:none}.tb-btn:hover{color:rgba(255,255,255,0.8);background:rgba(255,255,255,0.05)}.dero-badge{position:fixed;bottom:20px;left:16px;background:rgba(4,13,30,0.95);border:1px solid rgba(59,130,246,0.35);border-radius:8px;padding:8px 12px;font-size:10px;color:rgba(59,130,246,0.8);font-weight:700;letter-spacing:0.08em;text-transform:uppercase;z-index:100}svg.edges{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:5}</style>
</head>
<body>
<div class="app">
<header class="header">
<div class="logo">LINK<span>TRACER</span></div>
<nav class="nav">
<button class="active" onclick="showTab('graph')">Kaart</button>
</nav>
<div style="font-size:11px;color:rgba(255,255,255,0.3);font-weight:600;letter-spacing:0.1em;text-transform:uppercase" id="dos-label">Rob de Wijk</div>
</header>
<div class="canvas" id="canvas" onclick="handleCanvasClick(event)">
<svg class="edges" id="svg"></svg>
<div class="empty" id="empty">
<div class="search-wrap" style="position:relative">
<div class="search-box">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgba(224,123,57,0.6)" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<input id="search" placeholder="Zoek een entiteit..." oninput="onSearch(this.value)" onkeydown="onSearchKey(event)" autocomplete="off">
</div>
<div class="suggestions" id="sugg" style="display:none"></div>
</div>
<div class="hint">Zoek een naam om te beginnen</div>
</div>
<div class="toolbar" id="toolbar" style="display:none">
<button class="tb-btn" onclick="resetGraph()">Reset</button>
<button class="tb-btn" onclick="expandAll()">Uitbreiden</button>
</div>
</div>
<div class="panel" id="panel">
<div class="panel-head">
<span class="panel-title">Details</span>
<button class="close-btn" onclick="closePanel()">x</button>
</div>
<div class="panel-body" id="panel-body"></div>
</div>
</div>
<div class="dero-badge">TELA / DERO</div>
<script>
var D={
nodes:[
{id:'rdw',name:'Rob de Wijk',type:'Person',role:'Defensie-expert',desc:'Nederlandse defensie- en veiligheidsexpert, oprichter van het HCSS en hoogleraar aan de Universiteit Leiden.'},
{id:'hcss',name:'HCSS',type:'Org',role:'Think Tank',desc:'The Hague Centre for Strategic Studies -- een invloedrijke geopolitieke denktank in Den Haag, opgericht in 2007.'},
{id:'leiden',name:'Univ. Leiden',type:'Org',role:'Academisch',desc:'Oudste universiteit van Nederland, waar Rob de Wijk als hoogleraar verbonden is.'},
{id:'navo',name:'NAVO',type:'Org',role:'Militaire Alliantie',desc:'Noord-Atlantische Verdragsorganisatie.'},
{id:'media',name:'Ned. Media',type:'Concept',role:'Publieke optredens',desc:'NOS, RTL, NPO Radio 1 -- regelmatige optredens.'},
{id:'def',name:'Min. Defensie',type:'Government',role:'Opdrachtgever',desc:'Het Ministerie van Defensie Nederland ontvangt beleidsadviezen van HCSS.'},
],
edges:[
{id:'e1',from:'rdw',to:'hcss',label:'Oprichter & Directeur',status:'verified',confidence:95,desc:'Rob de Wijk richtte HCSS op in 2007.',sources:[{title:'HCSS Over Ons',pub:'HCSS.nl',date:'2007'}]},
{id:'e2',from:'rdw',to:'leiden',label:'Hoogleraar',status:'verified',confidence:90,desc:'Verbonden als hoogleraar Internationale Betrekkingen.',sources:[{title:'Staffpagina Leiden',pub:'Universiteit Leiden',date:'2010'}]},
{id:'e3',from:'rdw',to:'media',label:'Analyst & Commentator',status:'verified',confidence:85,desc:'Frequente media-optredens bij NOS, RTL en andere nationale media.',sources:[{title:'NOS Nieuwsarchief',pub:'NOS',date:'2022'}]},
{id:'e4',from:'hcss',to:'navo',label:'Onderzoeksrelatie',status:'contested',confidence:55,desc:'HCSS ontvangt deels NAVO-gerelateerde onderzoeksfinanciering.',sources:[{title:'HCSS Projectoverzicht',pub:'HCSS',date:'2021'}]},
{id:'e5',from:'hcss',to:'def',label:'Beleidsadvies',status:'verified',confidence:70,desc:'HCSS levert structurele beleidsadviezen aan het Ministerie van Defensie.',sources:[{title:'Kamerstuk 2021',pub:'Rijksoverheid',date:'2021'}]},
{id:'e6',from:'rdw',to:'navo',label:'Adviseur (historisch)',status:'unverified',confidence:35,desc:'Mogelijke historische adviesrol -- nog niet onafhankelijk geverifieerd.',sources:[]},
]
};
var nodes=[],selId=null,selEdge=null,dragging=null,dragOff={x:0,y:0};
function ini(name){var p=name.split(' ');return((p[0]||'')[0]||(p[1]||'')[0]||'?').toUpperCase()+(p[1]?p[1][0].toUpperCase():'')}
function onSearch(v){
var s=document.getElementById('sugg');
if(v.trim().length<2){s.style.display='none';return;}
var t=v.toLowerCase();
var res=D.nodes.filter(function(n){return n.name.toLowerCase().indexOf(t)>=0&&!nodes.find(function(x){return x.id===n.id})}).slice(0,5);
if(!res.length){s.style.display='none';return;}
s.innerHTML=res.map(function(n){return'<div class="sug-item" onclick="addNode(\''+n.id+'\')"><div class="sug-av">'+ini(n.name)+'</div><div><div class="sug-name">'+n.name+'</div><div class="sug-type">'+n.type+'</div></div></div>'}).join('');
s.style.display='block';
}
function onSearchKey(e){
if(e.key==='Enter'){
var v=document.getElementById('search').value.trim();
if(v.length<2)return;
var t=v.toLowerCase();
var r=D.nodes.find(function(n){return n.name.toLowerCase().indexOf(t)>=0&&!nodes.find(function(x){return x.id===n.id})});
if(r)addNode(r.id);
}
}
function addNode(id){
if(nodes.find(function(n){return n.id===id}))return;
var dn=D.nodes.find(function(n){return n.id===id});
if(!dn)return;
var c=document.getElementById('canvas');
var cx=c.clientWidth/2-80,cy=c.clientHeight/2-80;
var phi=2.4;
var pos={x:cx,y:cy};
if(nodes.length>0){var r=120+nodes.length*30,a=nodes.length*phi;pos={x:Math.max(10,Math.min(c.clientWidth-170,cx+Math.cos(a)*r)),y:Math.max(70,Math.min(c.clientHeight-130,cy+Math.sin(a)*r))}}
var node=Object.assign({},dn,{x:pos.x,y:pos.y});
nodes.push(node);
document.getElementById('search').value='';
document.getElementById('sugg').style.display='none';
document.getElementById('empty').style.display='none';
document.getElementById('toolbar').style.display='flex';
renderAll();
selId=id;selEdge=null;
renderAll();
showNodePanel(node);
}
function renderAll(){
var c=document.getElementById('canvas');
var existing=c.querySelectorAll('.card');
existing.forEach(function(el){el.remove()});
var svg=document.getElementById('svg');
svg.innerHTML='';
var nodeMap={};
nodes.forEach(function(n){nodeMap[n.id]=n});
D.edges.forEach(function(e){
var s=nodeMap[e.from],t=nodeMap[e.to];
if(!s||!t)return;
var x1=s.x+80,y1=s.y+70,x2=t.x+80,y2=t.y+70;
var col=e.status==='verified'?'rgba(34,197,94,':'e.status==='contested'?'rgba(245,158,11,':'rgba(239,68,68,';
col=e.status==='verified'?'rgba(34,197,94,':e.status==='contested'?'rgba(245,158,11,':'rgba(239,68,68,';
var op=(0.3+e.confidence/100*0.7);
var color='rgba('+(e.status==='verified'?'34,197,94':e.status==='contested'?'245,158,11':'239,68,68')+','+op+')';
var isActive=selEdge&&selEdge.id===e.id;
var line=document.createElementNS('http:
line.setAttribute('x1',x1);line.setAttribute('y1',y1);line.setAttribute('x2',x2);line.setAttribute('y2',y2);
line.setAttribute('stroke',color);line.setAttribute('stroke-width',isActive?3.5:2);
line.setAttribute('stroke-dasharray',e.status==='unverified'?'6,5':'0');
line.style.cursor='pointer';line.style.pointerEvents='auto';
var eid=e.id;
line.addEventListener('click',function(ev){ev.stopPropagation();selEdge=D.edges.find(function(x){return x.id===eid});selId=null;renderAll();showEdgePanel(selEdge,nodeMap)});
svg.appendChild(line);
if(isActive){
var dot=document.createElementNS('http:
dot.setAttribute('cx',(x1+x2)/2);dot.setAttribute('cy',(y1+y2)/2);dot.setAttribute('r',6);dot.setAttribute('fill',color);svg.appendChild(dot);
}
});
nodes.forEach(function(n){
var div=document.createElement('div');
div.className='card'+(n.id===selId?' sel':'');
div.style.left=n.x+'px';div.style.top=n.y+'px';
div.innerHTML='<div class="card-img">'+ini(n.name)+'</div><div class="card-body"><div class="card-name">'+n.name+'</div><div class="card-role">'+n.role+'</div></div>';
div.addEventListener('mousedown',function(e){e.stopPropagation();dragging=n.id;dragOff={x:e.clientX-n.x,y:e.clientY-n.y}});
div.addEventListener('click',function(e){e.stopPropagation();selId=n.id;selEdge=null;renderAll();showNodePanel(n)});
document.getElementById('canvas').appendChild(div);
});
}
document.addEventListener('mousemove',function(e){
if(!dragging)return;
var n=nodes.find(function(x){return x.id===dragging});
if(n){n.x=e.clientX-dragOff.x;n.y=e.clientY-dragOff.y;renderAll()}
});
document.addEventListener('mouseup',function(){dragging=null});
function handleCanvasClick(e){
if(e.target.closest('.card')||e.target.closest('.panel')||e.target.closest('.search-wrap')||e.target.closest('.toolbar'))return;
selId=null;selEdge=null;closePanel();renderAll();
}
function closePanel(){document.getElementById('panel').classList.remove('open')}
function showNodePanel(n){
var pb=document.getElementById('panel-body');
pb.innerHTML='<div style="display:flex;align-items:flex-start;margin-bottom:16px"><div class="node-av">'+ini(n.name)+'</div><div class="node-info"><span class="node-type">'+n.type+'</span><div class="node-name">'+n.name+'</div><div class="node-role">'+n.role+'</div></div></div>'+(n.desc?'<div class="node-desc">'+n.desc+'</div>':'')+'<p class="tip">Klik op een lijn voor details.</p>';
document.getElementById('panel').classList.add('open');
}
function showEdgePanel(e,nm){
var statusClass=e.status==='verified'?'badge-v':e.status==='contested'?'badge-c':'badge-u';
var statusLabel=e.status==='verified'?'Geverifieerd':e.status==='contested'?'Betwist':'Niet geverifieerd';
var srcHtml=e.sources.length?e.sources.map(function(s){return'<div class="source-item"><div class="source-title">'+s.title+'</div><div class="source-meta">'+s.pub+(s.date?' -- '+s.date:'')+'</div></div>'}).join(''):'<p style="font-size:13px;color:rgba(255,255,255,0.35);font-style:italic">Geen bronnen.</p>';
var pb=document.getElementById('panel-body');
pb.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span class="badge '+statusClass+'">'+statusLabel+'</span><span class="conf">'+e.confidence+'%</span></div><div class="edge-label">'+e.label+'</div><div class="edge-sub">'+(nm[e.from]?nm[e.from].name:'?')+' -- '+(nm[e.to]?nm[e.to].name:'?')+'</div>'+(e.desc?'<div class="desc-box">"'+e.desc+'"</div>':'')+'<div class="sources-title">Bronnen ('+e.sources.length+')</div>'+srcHtml;
document.getElementById('panel').classList.add('open');
}
function resetGraph(){nodes=[];selId=null;selEdge=null;document.getElementById('empty').style.display='flex';document.getElementById('toolbar').style.display='none';closePanel();renderAll()}
function expandAll(){
var nm={};nodes.forEach(function(n){nm[n.id]=n});
var c=document.getElementById('canvas');
D.edges.forEach(function(e){
[e.from,e.to].forEach(function(id){
if(!nm[id]){var dn=D.nodes.find(function(n){return n.id===id});if(dn){var cx=c.clientWidth/2-80,cy=c.clientHeight/2-80,r=150+nodes.length*25,a=nodes.length*2.4;var node=Object.assign({},dn,{x:Math.max(10,Math.min(c.clientWidth-170,cx+Math.cos(a)*r)),y:Math.max(70,Math.min(c.clientHeight-130,cy+Math.sin(a)*r))});nodes.push(node);nm[id]=node}}
});
});
renderAll();
}
function showTab(t){
document.querySelectorAll('.nav button').forEach(function(b){b.classList.remove('active')});
event.target.classList.add('active');
}
</script>
</body>
</html>
*/ |
| SC Arguments: [Name:SC_ACTION Type:uint64 Value:'1' Name:SC_CODE Type:string Value:'Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("var_header_name", "index_single.html")
31 STORE("var_header_description", "")
32 STORE("var_header_icon", "")
33 STORE("dURL", "")
34 STORE("docType", "TELA-HTML-1")
35 STORE("subDir", "/")
36 STORE("fileCheckC", "2895784b97d7a22e7f65f63420c8aeed48d26e5367460ef03803ffe6afcc07f8")
37 STORE("fileCheckS", "03e80fa6d60c943a1c516578edd8b3edea1fb297086275c023776bf85d705c99")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LinkTracer -- Uncovers the Truth</title>
<style>*{box-sizing:border-box;margin:0;padding:0}html,body{height:100%;background:#040d1a;color:rgba(255,255,255,0.88);font-family:system-ui,sans-serif;overflow:hidden}.app{display:flex;height:100vh;flex-direction:column}.header{display:flex;align-items:center;justify-content:space-between;padding:0 24px;height:64px;border-bottom:1px solid rgba(255,255,255,0.08);background:rgba(4,7,21,0.9);flex-shrink:0}.logo{font-size:22px;font-weight:800;letter-spacing:0.1em;color:rgba(255,255,255,0.9)}.logo span{color:#e07b39}.nav{display:flex;gap:24px}.nav button{background:none;border:none;color:rgba(255,255,255,0.5);font-size:12px;font-weight:600;letter-spacing:0.12em;text-transform:uppercase;cursor:pointer;padding:4px 0}.nav button.active{color:rgba(255,255,255,0.9);border-bottom:2px solid #e07b39}.canvas{flex:1;position:relative;overflow:hidden}.empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px}.search-wrap{width:100%;max-width:600px;padding:0 24px}.search-box{display:flex;align-items:center;gap:12px;padding:16px 20px;border:1px solid rgba(224,123,57,0.3);border-radius:12px;background:rgba(255,255,255,0.04)}.search-box input{flex:1;background:none;border:none;outline:none;font-size:18px;color:rgba(255,255,255,0.88);caret-color:#e07b39}.hint{font-size:11px;letter-spacing:0.2em;text-transform:uppercase;color:rgba(255,255,255,0.25)}.suggestions{position:absolute;top:100%;left:0;right:0;margin-top:6px;background:rgba(4,7,21,0.97);border:1px solid rgba(255,255,255,0.1);border-radius:12px;overflow:hidden;z-index:50}.sug-item{padding:12px 16px;display:flex;align-items:center;gap:12px;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.05)}.sug-item:hover{background:rgba(255,255,255,0.05)}.sug-av{width:32px;height:32px;border-radius:8px;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:rgba(255,255,255,0.5);flex-shrink:0}.sug-name{font-size:13px;font-weight:600}.sug-type{font-size:10px;color:rgba(255,255,255,0.4)}.card{position:absolute;width:160px;border-radius:10px;border:1px solid rgba(255,255,255,0.11);background:rgba(255,255,255,0.055);cursor:pointer;transition:border-color 0.2s;user-select:none}.card:hover,.card.sel{border-color:rgba(224,123,57,0.4);background:rgba(224,123,57,0.07)}.card-img{width:100%;height:100px;background:linear-gradient(135deg,#1e2a3a,#0f1a2e);display:flex;align-items:center;justify-content:center;border-radius:9px 9px 0 0;overflow:hidden;font-size:24px;font-weight:700;color:rgba(255,255,255,0.2)}.card-body{padding:8px 10px}.card-name{font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card-role{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:0.1em;color:rgba(255,255,255,0.35);margin-top:2px}.panel{position:fixed;right:0;top:64px;width:360px;height:calc(100vh - 64px);background:rgba(8,16,36,0.92);border-left:1px solid rgba(255,255,255,0.08);backdrop-filter:blur(20px);transform:translateX(100%);transition:transform 0.3s;display:flex;flex-direction:column;z-index:60}.panel.open{transform:translateX(0)}.panel-head{padding:16px 20px;border-bottom:1px solid rgba(255,255,255,0.07);display:flex;align-items:center;justify-content:space-between}.panel-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:rgba(255,255,255,0.4)}.close-btn{background:none;border:none;color:rgba(255,255,255,0.3);cursor:pointer;font-size:18px;line-height:1}.panel-body{flex:1;overflow-y:auto;padding:20px}.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.08em}.badge-v{background:rgba(34,197,94,0.12);border:1px solid rgba(34,197,94,0.4);color:rgba(34,197,94,0.9)}.badge-c{background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.4);color:rgba(245,158,11,0.9)}.badge-u{background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.4);color:rgba(239,68,68,0.9)}.conf{font-size:12px;color:rgba(255,255,255,0.45);font-family:monospace}.edge-label{font-size:20px;font-weight:700;color:rgba(255,255,255,0.92);margin:8px 0 4px}.edge-sub{font-size:13px;color:rgba(255,255,255,0.45);margin-bottom:16px}.desc-box{padding:14px;border-radius:8px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.04);font-size:13px;color:rgba(255,255,255,0.75);line-height:1.7;font-style:italic;margin-bottom:16px}.sources-title{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:rgba(255,255,255,0.35);margin-bottom:10px}.source-item{padding:10px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);margin-bottom:6px}.source-title{font-size:13px;font-weight:600;color:rgba(255,255,255,0.85)}.source-meta{font-size:10px;color:rgba(255,255,255,0.4);font-family:monospace;margin-top:3px}.node-av{width:56px;height:56px;border-radius:10px;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:rgba(255,255,255,0.4);flex-shrink:0}.node-info{margin-left:14px}.node-type{font-size:10px;padding:2px 6px;border-radius:3px;border:1px solid rgba(255,255,255,0.15);color:rgba(255,255,255,0.5);text-transform:uppercase;font-size:9px;font-weight:600;letter-spacing:0.08em}.node-name{font-size:22px;font-weight:700;margin-top:6px;line-height:1.2}.node-role{font-size:13px;color:rgba(255,255,255,0.5);margin-top:4px}.node-desc{font-size:13px;color:rgba(255,255,255,0.72);line-height:1.7;margin-top:14px}.tip{font-size:11px;color:rgba(255,255,255,0.28);margin-top:14px}.toolbar{position:absolute;bottom:20px;right:20px;display:flex;gap:0;border-radius:8px;overflow:hidden;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05)}.tb-btn{padding:8px 16px;background:none;border:none;border-right:1px solid rgba(255,255,255,0.08);color:rgba(255,255,255,0.4);font-size:11px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;cursor:pointer}.tb-btn:last-child{border-right:none}.tb-btn:hover{color:rgba(255,255,255,0.8);background:rgba(255,255,255,0.05)}.dero-badge{position:fixed;bottom:20px;left:16px;background:rgba(4,13,30,0.95);border:1px solid rgba(59,130,246,0.35);border-radius:8px;padding:8px 12px;font-size:10px;color:rgba(59,130,246,0.8);font-weight:700;letter-spacing:0.08em;text-transform:uppercase;z-index:100}svg.edges{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:5}</style>
</head>
<body>
<div class="app">
<header class="header">
<div class="logo">LINK<span>TRACER</span></div>
<nav class="nav">
<button class="active" onclick="showTab('graph')">Kaart</button>
</nav>
<div style="font-size:11px;color:rgba(255,255,255,0.3);font-weight:600;letter-spacing:0.1em;text-transform:uppercase" id="dos-label">Rob de Wijk</div>
</header>
<div class="canvas" id="canvas" onclick="handleCanvasClick(event)">
<svg class="edges" id="svg"></svg>
<div class="empty" id="empty">
<div class="search-wrap" style="position:relative">
<div class="search-box">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgba(224,123,57,0.6)" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<input id="search" placeholder="Zoek een entiteit..." oninput="onSearch(this.value)" onkeydown="onSearchKey(event)" autocomplete="off">
</div>
<div class="suggestions" id="sugg" style="display:none"></div>
</div>
<div class="hint">Zoek een naam om te beginnen</div>
</div>
<div class="toolbar" id="toolbar" style="display:none">
<button class="tb-btn" onclick="resetGraph()">Reset</button>
<button class="tb-btn" onclick="expandAll()">Uitbreiden</button>
</div>
</div>
<div class="panel" id="panel">
<div class="panel-head">
<span class="panel-title">Details</span>
<button class="close-btn" onclick="closePanel()">x</button>
</div>
<div class="panel-body" id="panel-body"></div>
</div>
</div>
<div class="dero-badge">TELA / DERO</div>
<script>
var D={
nodes:[
{id:'rdw',name:'Rob de Wijk',type:'Person',role:'Defensie-expert',desc:'Nederlandse defensie- en veiligheidsexpert, oprichter van het HCSS en hoogleraar aan de Universiteit Leiden.'},
{id:'hcss',name:'HCSS',type:'Org',role:'Think Tank',desc:'The Hague Centre for Strategic Studies -- een invloedrijke geopolitieke denktank in Den Haag, opgericht in 2007.'},
{id:'leiden',name:'Univ. Leiden',type:'Org',role:'Academisch',desc:'Oudste universiteit van Nederland, waar Rob de Wijk als hoogleraar verbonden is.'},
{id:'navo',name:'NAVO',type:'Org',role:'Militaire Alliantie',desc:'Noord-Atlantische Verdragsorganisatie.'},
{id:'media',name:'Ned. Media',type:'Concept',role:'Publieke optredens',desc:'NOS, RTL, NPO Radio 1 -- regelmatige optredens.'},
{id:'def',name:'Min. Defensie',type:'Government',role:'Opdrachtgever',desc:'Het Ministerie van Defensie Nederland ontvangt beleidsadviezen van HCSS.'},
],
edges:[
{id:'e1',from:'rdw',to:'hcss',label:'Oprichter & Directeur',status:'verified',confidence:95,desc:'Rob de Wijk richtte HCSS op in 2007.',sources:[{title:'HCSS Over Ons',pub:'HCSS.nl',date:'2007'}]},
{id:'e2',from:'rdw',to:'leiden',label:'Hoogleraar',status:'verified',confidence:90,desc:'Verbonden als hoogleraar Internationale Betrekkingen.',sources:[{title:'Staffpagina Leiden',pub:'Universiteit Leiden',date:'2010'}]},
{id:'e3',from:'rdw',to:'media',label:'Analyst & Commentator',status:'verified',confidence:85,desc:'Frequente media-optredens bij NOS, RTL en andere nationale media.',sources:[{title:'NOS Nieuwsarchief',pub:'NOS',date:'2022'}]},
{id:'e4',from:'hcss',to:'navo',label:'Onderzoeksrelatie',status:'contested',confidence:55,desc:'HCSS ontvangt deels NAVO-gerelateerde onderzoeksfinanciering.',sources:[{title:'HCSS Projectoverzicht',pub:'HCSS',date:'2021'}]},
{id:'e5',from:'hcss',to:'def',label:'Beleidsadvies',status:'verified',confidence:70,desc:'HCSS levert structurele beleidsadviezen aan het Ministerie van Defensie.',sources:[{title:'Kamerstuk 2021',pub:'Rijksoverheid',date:'2021'}]},
{id:'e6',from:'rdw',to:'navo',label:'Adviseur (historisch)',status:'unverified',confidence:35,desc:'Mogelijke historische adviesrol -- nog niet onafhankelijk geverifieerd.',sources:[]},
]
};
var nodes=[],selId=null,selEdge=null,dragging=null,dragOff={x:0,y:0};
function ini(name){var p=name.split(' ');return((p[0]||'')[0]||(p[1]||'')[0]||'?').toUpperCase()+(p[1]?p[1][0].toUpperCase():'')}
function onSearch(v){
var s=document.getElementById('sugg');
if(v.trim().length<2){s.style.display='none';return;}
var t=v.toLowerCase();
var res=D.nodes.filter(function(n){return n.name.toLowerCase().indexOf(t)>=0&&!nodes.find(function(x){return x.id===n.id})}).slice(0,5);
if(!res.length){s.style.display='none';return;}
s.innerHTML=res.map(function(n){return'<div class="sug-item" onclick="addNode(\''+n.id+'\')"><div class="sug-av">'+ini(n.name)+'</div><div><div class="sug-name">'+n.name+'</div><div class="sug-type">'+n.type+'</div></div></div>'}).join('');
s.style.display='block';
}
function onSearchKey(e){
if(e.key==='Enter'){
var v=document.getElementById('search').value.trim();
if(v.length<2)return;
var t=v.toLowerCase();
var r=D.nodes.find(function(n){return n.name.toLowerCase().indexOf(t)>=0&&!nodes.find(function(x){return x.id===n.id})});
if(r)addNode(r.id);
}
}
function addNode(id){
if(nodes.find(function(n){return n.id===id}))return;
var dn=D.nodes.find(function(n){return n.id===id});
if(!dn)return;
var c=document.getElementById('canvas');
var cx=c.clientWidth/2-80,cy=c.clientHeight/2-80;
var phi=2.4;
var pos={x:cx,y:cy};
if(nodes.length>0){var r=120+nodes.length*30,a=nodes.length*phi;pos={x:Math.max(10,Math.min(c.clientWidth-170,cx+Math.cos(a)*r)),y:Math.max(70,Math.min(c.clientHeight-130,cy+Math.sin(a)*r))}}
var node=Object.assign({},dn,{x:pos.x,y:pos.y});
nodes.push(node);
document.getElementById('search').value='';
document.getElementById('sugg').style.display='none';
document.getElementById('empty').style.display='none';
document.getElementById('toolbar').style.display='flex';
renderAll();
selId=id;selEdge=null;
renderAll();
showNodePanel(node);
}
function renderAll(){
var c=document.getElementById('canvas');
var existing=c.querySelectorAll('.card');
existing.forEach(function(el){el.remove()});
var svg=document.getElementById('svg');
svg.innerHTML='';
var nodeMap={};
nodes.forEach(function(n){nodeMap[n.id]=n});
D.edges.forEach(function(e){
var s=nodeMap[e.from],t=nodeMap[e.to];
if(!s||!t)return;
var x1=s.x+80,y1=s.y+70,x2=t.x+80,y2=t.y+70;
var col=e.status==='verified'?'rgba(34,197,94,':'e.status==='contested'?'rgba(245,158,11,':'rgba(239,68,68,';
col=e.status==='verified'?'rgba(34,197,94,':e.status==='contested'?'rgba(245,158,11,':'rgba(239,68,68,';
var op=(0.3+e.confidence/100*0.7);
var color='rgba('+(e.status==='verified'?'34,197,94':e.status==='contested'?'245,158,11':'239,68,68')+','+op+')';
var isActive=selEdge&&selEdge.id===e.id;
var line=document.createElementNS('http:
line.setAttribute('x1',x1);line.setAttribute('y1',y1);line.setAttribute('x2',x2);line.setAttribute('y2',y2);
line.setAttribute('stroke',color);line.setAttribute('stroke-width',isActive?3.5:2);
line.setAttribute('stroke-dasharray',e.status==='unverified'?'6,5':'0');
line.style.cursor='pointer';line.style.pointerEvents='auto';
var eid=e.id;
line.addEventListener('click',function(ev){ev.stopPropagation();selEdge=D.edges.find(function(x){return x.id===eid});selId=null;renderAll();showEdgePanel(selEdge,nodeMap)});
svg.appendChild(line);
if(isActive){
var dot=document.createElementNS('http:
dot.setAttribute('cx',(x1+x2)/2);dot.setAttribute('cy',(y1+y2)/2);dot.setAttribute('r',6);dot.setAttribute('fill',color);svg.appendChild(dot);
}
});
nodes.forEach(function(n){
var div=document.createElement('div');
div.className='card'+(n.id===selId?' sel':'');
div.style.left=n.x+'px';div.style.top=n.y+'px';
div.innerHTML='<div class="card-img">'+ini(n.name)+'</div><div class="card-body"><div class="card-name">'+n.name+'</div><div class="card-role">'+n.role+'</div></div>';
div.addEventListener('mousedown',function(e){e.stopPropagation();dragging=n.id;dragOff={x:e.clientX-n.x,y:e.clientY-n.y}});
div.addEventListener('click',function(e){e.stopPropagation();selId=n.id;selEdge=null;renderAll();showNodePanel(n)});
document.getElementById('canvas').appendChild(div);
});
}
document.addEventListener('mousemove',function(e){
if(!dragging)return;
var n=nodes.find(function(x){return x.id===dragging});
if(n){n.x=e.clientX-dragOff.x;n.y=e.clientY-dragOff.y;renderAll()}
});
document.addEventListener('mouseup',function(){dragging=null});
function handleCanvasClick(e){
if(e.target.closest('.card')||e.target.closest('.panel')||e.target.closest('.search-wrap')||e.target.closest('.toolbar'))return;
selId=null;selEdge=null;closePanel();renderAll();
}
function closePanel(){document.getElementById('panel').classList.remove('open')}
function showNodePanel(n){
var pb=document.getElementById('panel-body');
pb.innerHTML='<div style="display:flex;align-items:flex-start;margin-bottom:16px"><div class="node-av">'+ini(n.name)+'</div><div class="node-info"><span class="node-type">'+n.type+'</span><div class="node-name">'+n.name+'</div><div class="node-role">'+n.role+'</div></div></div>'+(n.desc?'<div class="node-desc">'+n.desc+'</div>':'')+'<p class="tip">Klik op een lijn voor details.</p>';
document.getElementById('panel').classList.add('open');
}
function showEdgePanel(e,nm){
var statusClass=e.status==='verified'?'badge-v':e.status==='contested'?'badge-c':'badge-u';
var statusLabel=e.status==='verified'?'Geverifieerd':e.status==='contested'?'Betwist':'Niet geverifieerd';
var srcHtml=e.sources.length?e.sources.map(function(s){return'<div class="source-item"><div class="source-title">'+s.title+'</div><div class="source-meta">'+s.pub+(s.date?' -- '+s.date:'')+'</div></div>'}).join(''):'<p style="font-size:13px;color:rgba(255,255,255,0.35);font-style:italic">Geen bronnen.</p>';
var pb=document.getElementById('panel-body');
pb.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span class="badge '+statusClass+'">'+statusLabel+'</span><span class="conf">'+e.confidence+'%</span></div><div class="edge-label">'+e.label+'</div><div class="edge-sub">'+(nm[e.from]?nm[e.from].name:'?')+' -- '+(nm[e.to]?nm[e.to].name:'?')+'</div>'+(e.desc?'<div class="desc-box">"'+e.desc+'"</div>':'')+'<div class="sources-title">Bronnen ('+e.sources.length+')</div>'+srcHtml;
document.getElementById('panel').classList.add('open');
}
function resetGraph(){nodes=[];selId=null;selEdge=null;document.getElementById('empty').style.display='flex';document.getElementById('toolbar').style.display='none';closePanel();renderAll()}
function expandAll(){
var nm={};nodes.forEach(function(n){nm[n.id]=n});
var c=document.getElementById('canvas');
D.edges.forEach(function(e){
[e.from,e.to].forEach(function(id){
if(!nm[id]){var dn=D.nodes.find(function(n){return n.id===id});if(dn){var cx=c.clientWidth/2-80,cy=c.clientHeight/2-80,r=150+nodes.length*25,a=nodes.length*2.4;var node=Object.assign({},dn,{x:Math.max(10,Math.min(c.clientWidth-170,cx+Math.cos(a)*r)),y:Math.max(70,Math.min(c.clientHeight-130,cy+Math.sin(a)*r))});nodes.push(node);nm[id]=node}}
});
});
renderAll();
}
function showTab(t){
document.querySelectorAll('.nav button').forEach(function(b){b.classList.remove('active')});
event.target.classList.add('active');
}
</script>
</body>
</html>
*/'] |