Marcel0123 commited on
Commit
72d7449
·
verified ·
1 Parent(s): e8eb478

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +389 -15
index.html CHANGED
@@ -225,14 +225,146 @@
225
  .header-center-logo .reakt-name{display:block;font-family:Arial,'Segoe UI',sans-serif;font-weight:800;font-size:clamp(40px,3vw,56px);letter-spacing:-2.6px;line-height:.99;color:#ff5a0a;}
226
  .header-center-logo .reakt-tagline{display:block;font-family:Arial,'Segoe UI',sans-serif;font-weight:500;font-size:clamp(12px,.9vw,15px);line-height:1.16;letter-spacing:-.2px;color:#ff5a0a;margin-top:4px;}
227
  @media(max-width:1080px){.header-center-logo .reakt-name{font-size:44px}.header-center-logo .reakt-tagline{font-size:12px}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  </style></head>
229
  <body>
230
  <div class="app">
231
  <main class="main">
232
- <header><div class="header-copy"><div class="eyebrow">REAKT / AI ANALYTICS PLATFORM</div><h1>Inloop <span>Intelligence</span> <em>ORCHESTRA</em></h1><p>Een intelligente blik op ontwikkeling, kwaliteit en herstelondersteunende inloop</p></div><div class="orchestra-hero-title" aria-label="ORCHESTRA by Marcel Ooms"><span class="orchestra-hero-word">ORCHESTRA</span><span class="orchestra-hero-credit">by Marcel Ooms</span></div><div class="header-center-logo"><div class="reakt-wordmark" role="img" aria-label="Reakt — Werken aan je toekomst"><strong class="reakt-name">Reakt</strong><span class="reakt-tagline">Werken aan je toekomst</span></div></div><div class="header-actions"><span class="pill"><i></i> LOKALE ANALYSE</span><span class="clock" id="clock"></span></div></header>
233
- <div class="topbar"><div><span class="section-kicker">CONTROL CENTER</span><h2>Multi-Agent Orchestration <span class="live-dot"></span></h2></div><div class="actions"><label class="pace-label" for="paceSelect">Weergave <select id="paceSelect" aria-label="Animatiesnelheid"><option value="presentation">Presentatie ± 30 sec</option><option value="fast">Snel ± 6 sec</option></select></label><button id="resetBtn" class="ghost">↺ Reset</button><button id="startBtn" class="primary">▶ Start demo-analyse</button></div></div>
234
  <section class="import-panel panel" aria-label="Lokale Excelanalyse met 2026 en historische referentie 2025">
235
- <div class="import-intro"><span class="section-kicker">V1.6.2 · GECORRIGEERDE REKENREGELS</span><strong>Scanregistratie 2026 + referentie 2025</strong><small>Browser-lokale analyse · alle bronlocaties · geen bestanden in Space-repository</small></div>
236
  <label class="import-file-label" for="scanFile"><span>▤</span> Rapportage 2026 (.xlsx)</label><input type="file" id="scanFile" accept=".xlsx" aria-label="Rapportagebestand januari tot en met september 2026" />
237
  <label class="import-file-label history-label" for="historyFile"><span>◴</span> Historie 2025 (.xlsx)</label><input type="file" id="historyFile" accept=".xlsx" aria-label="Historisch referentiebestand 2025" disabled/>
238
  <label class="site-label" for="siteSelect">Locatie</label><select id="siteSelect" disabled><option value="*">Alle locaties</option></select>
@@ -241,16 +373,20 @@
241
  <div id="historyStatus" class="history-status">◴ Historie 2025 nog niet geladen. Zonder deze referentie zijn instroomcijfers voorlopig.</div>
242
  </section>
243
  <section class="orchestra panel">
244
- <div class="mesh"></div><div class="conductor-wrap"><div class="conductor" id="conductor"><div class="conductor-glow"></div><div class="conductor-icon" aria-hidden="true"><svg class="brain-symbol" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5c-2.7-3.2-7.4-.9-7.1 2.4C2 9 2 13.6 5 15.2c-.7 4.4 4.9 6.8 7 2.6M12 5c2.7-3.2 7.4-.9 7.1 2.4 2.9 1.6 2.9 6.2-.1 7.8.7 4.4-4.9 6.8-7 2.6M12 5v13"/><path d="M6.6 10.1 9 12v3M17.4 10.1 15 12v3"/><circle cx="9" cy="15.5" r=".9" fill="currentColor" stroke="none"/><circle cx="15" cy="15.5" r=".9" fill="currentColor" stroke="none"/></svg></div><div><small>MASTER ORCHESTRATOR</small><strong>THE CONDUCTOR</strong><span id="conductorStatus">Gereed voor analyse</span></div><div class="conductor-indicator"></div></div></div>
245
- <svg class="connections" viewBox="0 0 1000 128" preserveAspectRatio="none" aria-hidden="true"><path class="stem" d="M500 0 V30 H100 V90 M500 30 H300 V90 M500 30 V90 M500 30 H700 V90 M500 30 H900 V90"/><path class="flowline" d="M500 0 V30 H100 V90 M500 30 H300 V90 M500 30 V90 M500 30 H700 V90 M500 30 H900 V90"/><g class="signal-layer" aria-hidden="true"></g></svg>
246
  <div class="agents">
247
  <article class="agent" id="agent-data" style="--accent:#34e6b3"><div class="agent-top"><span>01 / DATA</span><span class="status">STANDBY</span></div><div class="agent-symbol">▤</div><h3>Data Agent</h3><p>Structuur & kwaliteit</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual bars"><b></b><b></b><b></b><b></b><b></b><b></b><b></b></div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
248
- <article class="agent" id="agent-analysis" style="--accent:#ffcb62"><div class="agent-top"><span>02 / ANALYSE</span><span class="status">STANDBY</span></div><div class="agent-symbol">⌁</div><h3>Analysis Agent</h3><p>Trends & vergelijkingen</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual spark">▁ ▃ ▂ ▅ ▄ ▆ ▇</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
249
- <article class="agent" id="agent-insight" style="--accent:#51b9ff"><div class="agent-top"><span>03 / INZICHT</span><span class="status">STANDBY</span></div><div class="agent-symbol">✦</div><h3>Insight Agent</h3><p>Signalen & duiding</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual rings">◎ <span>◉</span> ◎</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
250
- <article class="agent" id="agent-verify" style="--accent:#b18aff"><div class="agent-top"><span>04 / CONTROLE</span><span class="status">STANDBY</span></div><div class="agent-symbol">⬡</div><h3>Verification Agent</h3><p>Schaduwberekeningen</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual checkicons">✓ &nbsp; ✓ &nbsp; ! &nbsp; ✓</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
251
- <article class="agent" id="agent-report" style="--accent:#ff9a65"><div class="agent-top"><span>05 / RAPPORT</span><span class="status">STANDBY</span></div><div class="agent-symbol">▧</div><h3>Report Agent</h3><p>Infographic & export</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual reportlines">▰▰▰▰▰<br>▰▰▰▰<br>▰▰▰</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
 
 
 
 
252
  </div>
253
- <div class="orchestra-footer"><span><i class="dot green"></i> Systeem gereed</span><span id="flowState">Wacht op startcommando</span><span>◌ <span id="completed">0</span> / 5 agenten voltooid</span></div>
254
  </section>
255
  <div class="content-grid">
256
  <div class="left-content">
@@ -304,14 +440,14 @@ window.addEventListener('error', function(event){
304
  'use strict';
305
  let demoData=null, running=false, completed=0;
306
  const $=id=>document.getElementById(id);
307
- const ids=['data','analysis','insight','verify','report'];
308
  const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
309
  const escapeHTML=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
310
  function clock(){ $('clock').textContent=new Intl.DateTimeFormat('nl-NL',{hour:'2-digit',minute:'2-digit',day:'numeric',month:'short'}).format(new Date()); }
311
  clock();setInterval(clock,60000);
312
  function renderKPIs(data){$('kpis').innerHTML=data.kpis.map(k=>`<div class="kpi"><small>${escapeHTML(k.label)}</small><strong>${escapeHTML(k.value)}</strong><span>${escapeHTML(k.change)}</span></div>`).join('');}
313
  function renderChecks(data){$('checks').innerHTML=data.checks.map(c=>{const delta=c.ours-c.shadow;return `<div class="check-row"><span>${escapeHTML(c.label)}</span><span>${c.ours.toLocaleString('nl-NL')} / ${c.shadow.toLocaleString('nl-NL')} &nbsp; <strong class="${delta?'bad':''}">${delta?'⚠ '+(delta>0?'+':'')+delta:'✓ 0'}</strong></span></div>`}).join('');}
314
- function drawChart(data){const canvas=$('chart'),ctx=canvas.getContext('2d'),w=640,h=250;ctx.clearRect(0,0,w,h);ctx.font='13px Segoe UI';ctx.textAlign='left';const max=500;for(let i=0;i<=4;i++){let y=25+i*45;ctx.strokeStyle='#29405a';ctx.beginPath();ctx.moveTo(44,y);ctx.lineTo(w-15,y);ctx.stroke();ctx.fillStyle='#8298b4';ctx.fillText(String(Math.round(max*(4-i)/4)),5,y+4);}const cols=['#3ee1b6','#ffc66a','#5b9cff','#ad88ff'];data.locations.forEach((loc,index)=>{ctx.strokeStyle=cols[index];ctx.lineWidth=3;ctx.beginPath();loc.visits.forEach((v,j)=>{const x=72+(result.months.length===1?0:j*(w-105)/(result.months.length-1)),y=205-(v/max)*180;if(j===0)ctx.moveTo(x,y);else ctx.lineTo(x,y)});ctx.stroke();loc.visits.forEach((v,j)=>{const x=72+(result.months.length===1?0:j*(w-105)/(result.months.length-1)),y=205-(v/max)*180;ctx.fillStyle=cols[index];ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()})});ctx.textAlign='center';ctx.fillStyle='#a6b9d0';['JUL','AUG','SEP'].forEach((m,i)=>ctx.fillText(m,72+(result.months.length===1?0:i*(w-105)/(result.months.length-1)),239));}
315
  function log(text){const now=new Date().toLocaleTimeString('nl-NL',{hour:'2-digit',minute:'2-digit',second:'2-digit'});if($('logs').querySelector('.muted'))$('logs').innerHTML='';$('logs').insertAdjacentHTML('afterbegin',`<div class="logitem"><time>${now}</time>${escapeHTML(text)}</div>`)}
316
  // De lichtpunten volgen de vijf bestaande SVG-verbindingen. Ze lopen alleen tijdens een actieve processtap.
317
  const signalPaths={data:'M500 0 V30 H100 V90',analysis:'M500 0 V30 H300 V90',insight:'M500 0 V90',verify:'M500 0 V30 H700 V90',report:'M500 0 V30 H900 V90'};
@@ -791,8 +927,8 @@ function drawImportedChart(result){
791
  const colors=['#3ee1b6','#ffc66a','#5b9cff','#ad88ff'];
792
  series.forEach((loc,li)=>{
793
  ctx.strokeStyle=colors[li];ctx.lineWidth=3;ctx.beginPath();
794
- loc.visits.forEach((v,j)=>{const x=72+(result.months.length===1?0:j*(w-105)/(result.months.length-1)),y=205-(v/max)*180;j===0?ctx.moveTo(x,y):ctx.lineTo(x,y)});ctx.stroke();
795
- loc.visits.forEach((v,j)=>{const x=72+(result.months.length===1?0:j*(w-105)/(result.months.length-1)),y=205-(v/max)*180;ctx.fillStyle=colors[li];ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()});
796
  });ctx.fillStyle='#a6b9d0';ctx.textAlign='center';
797
  result.months.forEach((m,i)=>ctx.fillText(m,72+(result.months.length===1?0:i*(w-105)/(result.months.length-1)),239));
798
  const legend=document.querySelector('.legend');
@@ -894,7 +1030,7 @@ $('scanFile').addEventListener('change',async e=>{
894
  $('startBtn').textContent='▶ Analyseer Excelbestand';
895
  $('flowState').textContent='Excelbestand lokaal ingelezen · start de analyse';
896
  log('Excelbestand lokaal verwerkt; analyse moet nog starten. Voor echte data eerst interne toestemming controleren.');
897
- }catch(err){$('startBtn').textContent='▶ Start demo-analyse';state.textContent='⚠ '+err.message;$('siteSelect').disabled=true;$('periodSelect').disabled=true;$('periodType').disabled=true;log('Excel-proef: '+err.message)}finally{$('startBtn').disabled=false}
898
  });
899
  $('historyFile').addEventListener('change',async e=>{
900
  const file=e.target.files?.[0];if(!file||!importedData)return;
@@ -1118,4 +1254,242 @@ function compareController(){
1118
  $('controllerCompare').addEventListener('click',compareController);
1119
  $('controllerClear').addEventListener('click',()=>resetControllerComparison(true));
1120
  controllerDefs.forEach(([,id])=>$(id).addEventListener('input',()=>{if(controllerComparison)resetControllerComparison(false)}));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1121
  </script></body></html>
 
225
  .header-center-logo .reakt-name{display:block;font-family:Arial,'Segoe UI',sans-serif;font-weight:800;font-size:clamp(40px,3vw,56px);letter-spacing:-2.6px;line-height:.99;color:#ff5a0a;}
226
  .header-center-logo .reakt-tagline{display:block;font-family:Arial,'Segoe UI',sans-serif;font-weight:500;font-size:clamp(12px,.9vw,15px);line-height:1.16;letter-spacing:-.2px;color:#ff5a0a;margin-top:4px;}
227
  @media(max-width:1080px){.header-center-logo .reakt-name{font-size:44px}.header-center-logo .reakt-tagline{font-size:12px}}
228
+
229
+ /* ORCHESTRA NEXT: dynamischer command center met vloeiende netwerkanimaties */
230
+ .orchestra{padding:28px 28px 0;background:radial-gradient(ellipse at 50% 10%,#20376a 0%,#0b1730 58%,#08111f 100%);overflow:hidden}
231
+ .orchestra::before,.orchestra::after{content:'';position:absolute;pointer-events:none;z-index:0;border-radius:50%;filter:blur(54px);opacity:.2}
232
+ .orchestra::before{width:320px;height:320px;left:-70px;top:90px;background:#35c9ff}
233
+ .orchestra::after{width:360px;height:360px;right:-95px;top:170px;background:#8549ff}
234
+ .orchestra .conductor-wrap{position:relative;z-index:3;margin-bottom:34px}
235
+ .orchestra .conductor{width:362px;padding:20px 22px;border-color:#c194ff;box-shadow:0 0 45px #8c54df6f,inset 0 0 22px #9a69ff24;transition:transform .35s ease,box-shadow .35s ease,border-color .35s ease;overflow:hidden}
236
+ .orchestra .conductor::before{content:'';position:absolute;inset:-45% auto -45% -60%;width:52%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.18),transparent);transform:rotate(18deg) translateX(-160%);opacity:0}
237
+ .orchestra .conductor::after{content:'';position:absolute;inset:auto;left:50%;top:50%;width:155px;height:155px;background:radial-gradient(circle,rgba(188,150,255,.16),transparent 68%);transform:translate(-50%,-50%);pointer-events:none;opacity:.65}
238
+ .orchestra .conductor strong{font-size:19px}
239
+ .orchestra .conductor.running{animation:conductorFloat 4.8s ease-in-out infinite}
240
+ .orchestra .conductor.transfering{transform:translateY(-4px) scale(1.02);border-color:#efd7ff;box-shadow:0 0 60px #b779ff88,inset 0 0 28px #d0a7ff30}
241
+ .orchestra .conductor.transfering::before{opacity:1;animation:conductorSweep 1s ease-out}
242
+ .orchestra .conductor.transfering .conductor-icon{box-shadow:0 0 28px #d0a7ff99,0 0 54px #7de1ff33}
243
+ .orchestra .conductor.transfering .brain-symbol{animation-duration:.9s}
244
+ .orchestra .conductor.attention{box-shadow:0 0 46px #ffae5990,inset 0 0 26px #ffbf6a24}
245
+ .orchestra .conductor.attention::after{background:radial-gradient(circle,rgba(255,189,106,.18),transparent 68%)}
246
+ .orchestra .agents{display:grid;grid-template-columns:repeat(3,minmax(300px,1fr));gap:34px 58px;max-width:1490px;margin:0 auto;z-index:2;position:relative;padding:20px 18px 28px}
247
+ .orchestra .agent{min-height:178px;padding:16px 18px;position:relative;z-index:2;overflow:hidden;transition:transform .42s ease,box-shadow .42s ease,border-color .42s ease;box-shadow:0 0 16px color-mix(in srgb,var(--accent) 10%,transparent),inset 0 0 25px color-mix(in srgb,var(--accent) 6%,transparent)}
248
+ .orchestra .agent::before{content:'';position:absolute;inset:auto -10% -48% auto;width:110px;height:110px;background:radial-gradient(circle,color-mix(in srgb,var(--accent) 34%,transparent),transparent 72%);opacity:.58;filter:blur(8px);pointer-events:none}
249
+ .orchestra .agent::after{content:'';position:absolute;inset:-30% auto -30% -55%;width:42%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.12),transparent);transform:rotate(12deg) translateX(-210%);opacity:0;pointer-events:none}
250
+ .orchestra .agent:hover{transform:translateY(-2px)}
251
+ .orchestra .agent.active{transform:translateY(-8px) scale(1.012);box-shadow:0 0 34px color-mix(in srgb,var(--accent) 34%,transparent),inset 0 0 24px color-mix(in srgb,var(--accent) 13%,transparent)}
252
+ .orchestra .agent.active::after{opacity:1;animation:agentSweep 1.35s linear infinite}
253
+ .orchestra .agent.done .status{color:#62e5bd}
254
+ .orchestra .agent h3{font-size:17px;margin:4px 0}
255
+ .orchestra .agent .agent-symbol{font-size:23px;margin-top:7px;animation:iconFloat 5.5s ease-in-out infinite}
256
+ .orchestra .agent .mini-visual{margin:5px 0;height:27px;font-size:17px;opacity:.95}
257
+ .orchestra .agent .agent-steps{margin:5px 0;min-height:82px}
258
+ .orchestra .agent .agent-step{font-size:10px}
259
+ .orchestra .agent p{font-size:12px}
260
+ .orchestra .agent-bottom .track{height:6px;background:#223650}
261
+ .orchestra .agent-bottom .track i{transition:width .35s ease}
262
+ #agent-data{order:1}#agent-identity{order:2}#agent-history{order:3}#agent-analysis{order:4}#agent-insight{order:5}#agent-verify{order:6}#agent-privacy{order:7}#agent-report{order:8}#agent-quality{order:9}
263
+ #agent-data .bars b{animation:dataBar 1.75s ease-in-out infinite}
264
+ #agent-data .bars b:nth-child(2){animation-delay:.18s}#agent-data .bars b:nth-child(3){animation-delay:.36s}#agent-data .bars b:nth-child(4){animation-delay:.54s}#agent-data .bars b:nth-child(5){animation-delay:.72s}#agent-data .bars b:nth-child(6){animation-delay:.9s}#agent-data .bars b:nth-child(7){animation-delay:1.08s}
265
+ #agent-analysis .spark{animation:analysisSpark 2.4s ease-in-out infinite;letter-spacing:1px}
266
+ #agent-insight .rings{animation:ringsDrift 7.5s ease-in-out infinite}
267
+ #agent-insight .rings span{display:inline-block;animation:insightPulse 2.1s ease-in-out infinite}
268
+ #agent-verify .checkicons{animation:verifyPulse 2.35s ease-in-out infinite}
269
+ #agent-report .reportlines{animation:reportScan 2.6s ease-in-out infinite}
270
+ #agent-identity .mini-visual{animation:identityLink 2.8s ease-in-out infinite;letter-spacing:1px}
271
+ #agent-history .mini-visual{animation:historyLoop 3.7s ease-in-out infinite}
272
+ #agent-privacy .mini-visual{animation:privacyShield 2.9s ease-in-out infinite}
273
+ #agent-quality .mini-visual{animation:qualitySpark 2.4s ease-in-out infinite}
274
+ #agent-data.active .bars b,#agent-analysis.active .spark,#agent-insight.active .rings,#agent-insight.active .rings span,#agent-verify.active .checkicons,#agent-report.active .reportlines,#agent-identity.active .mini-visual,#agent-history.active .mini-visual,#agent-privacy.active .mini-visual,#agent-quality.active .mini-visual{animation-duration:.95s}
275
+ .network-links{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden}
276
+ #networkSvg{width:100%;height:100%;overflow:visible}
277
+ #networkEdges path{fill:none;stroke:#3e6888;stroke-width:1.55;opacity:.48;vector-effect:non-scaling-stroke;animation:routeGlow 5.5s ease-in-out infinite}
278
+ #networkEdges path.transfer{stroke:#86ecff;stroke-width:3.1;stroke-dasharray:8 12;opacity:1;animation:nextStream .72s linear infinite,routeTransferGlow 1.15s ease-in-out infinite;filter:drop-shadow(0 0 7px #75dafa)}
279
+ #networkEdges path.route-done{stroke:#4cc69f;opacity:.68;filter:drop-shadow(0 0 5px rgba(76,198,159,.35))}
280
+ #networkSignals circle{filter:drop-shadow(0 0 8px #8df3ff)}
281
+ @keyframes nextStream{to{stroke-dashoffset:-40}}
282
+ @keyframes routeGlow{50%{opacity:.72}}
283
+ @keyframes routeTransferGlow{50%{filter:drop-shadow(0 0 12px #9ef4ff)}}
284
+ @keyframes conductorFloat{50%{transform:translateY(-4px)}}
285
+ @keyframes conductorSweep{0%{transform:rotate(18deg) translateX(-160%)}100%{transform:rotate(18deg) translateX(360%)}}
286
+ @keyframes agentSweep{0%{transform:rotate(12deg) translateX(-220%)}100%{transform:rotate(12deg) translateX(360%)}}
287
+ @keyframes iconFloat{50%{transform:translateY(-3px)}}
288
+ @keyframes dataBar{0%,100%{transform:scaleY(.72);opacity:.7}50%{transform:scaleY(1.18);opacity:1}}
289
+ @keyframes analysisSpark{0%,100%{transform:translateX(0);opacity:.78}50%{transform:translateX(4px);opacity:1}}
290
+ @keyframes ringsDrift{50%{transform:translateY(-2px) rotate(4deg)}}
291
+ @keyframes insightPulse{50%{transform:scale(1.16);text-shadow:0 0 10px currentColor}}
292
+ @keyframes verifyPulse{0%,100%{opacity:.75}50%{opacity:1;text-shadow:0 0 14px currentColor}}
293
+ @keyframes reportScan{0%,100%{transform:translateY(0);opacity:.82}50%{transform:translateY(-2px);letter-spacing:4px;opacity:1}}
294
+ @keyframes identityLink{50%{letter-spacing:3px;transform:translateX(2px)}}
295
+ @keyframes historyLoop{50%{transform:translateY(-2px) scale(1.03);opacity:1}}
296
+ @keyframes privacyShield{50%{transform:scale(1.08);text-shadow:0 0 11px currentColor}}
297
+ @keyframes qualitySpark{50%{transform:translateY(-2px) scale(1.08);text-shadow:0 0 10px currentColor}}
298
+ .network-legend{display:flex;gap:20px;flex-wrap:wrap;justify-content:center;font-size:11px;color:#99b3d3;padding:2px 10px 14px;position:relative;z-index:2}
299
+ .network-legend strong{color:#d5eaff}
300
+ .next-narrative{text-align:center;color:#d9ebff;font-size:13px;line-height:1.55;margin:4px auto 15px;max-width:880px;text-shadow:0 0 12px rgba(98,167,255,.12)}
301
+ .next-mode{font-size:11px;color:#70dcc3;border:1px solid #255e5c;padding:6px 11px;border-radius:30px;white-space:nowrap}
302
+ @media(max-width:1080px){.orchestra .agents{max-width:1200px;gap:22px 24px;grid-template-columns:repeat(3,minmax(0,1fr))}.orchestra .agent{padding:13px 12px;min-height:170px}.orchestra .agent h3{font-size:15px}}
303
+ @media(max-width:700px){.orchestra .agents{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.orchestra .agent{min-height:154px}.orchestra .conductor{max-width:100%}.network-links{display:none}.network-legend{justify-content:flex-start}}
304
+ @media(prefers-reduced-motion:reduce){#networkEdges path,#networkEdges path.transfer,.orchestra .conductor,.orchestra .agent .agent-symbol,.orchestra .agent .mini-visual,#agent-data .bars b,#agent-analysis .spark,#agent-insight .rings,#agent-insight .rings span,#agent-verify .checkicons,#agent-report .reportlines,#agent-identity .mini-visual,#agent-history .mini-visual,#agent-privacy .mini-visual,#agent-quality .mini-visual{animation:none!important}.orchestra .agent::after,.orchestra .conductor::before{display:none}}
305
+
306
+ /* ORCHESTRA NEXT v4 — zichtbare lichtstromen, gebaseerd op de SVG-benadering van de originele Space.
307
+ Alleen interface: geen wijziging aan de Excel-import, berekeningen of rapportagefuncties. */
308
+ .orchestra{padding:26px 27px 0;isolation:isolate}
309
+ .orchestra .conductor-wrap{margin-bottom:68px;z-index:4}
310
+ .orchestra .conductor{overflow:visible;isolation:isolate;min-width:330px;max-width:100%;position:relative;animation:conductorBreathe 4s ease-in-out infinite}
311
+ .orchestra .conductor::after{content:"";position:absolute;inset:-8px;border:1px solid rgba(180,128,255,.43);border-radius:19px;box-shadow:0 0 18px rgba(168,112,255,.22);pointer-events:none;z-index:-1;animation:conductorRing 3.2s ease-in-out infinite}
312
+ .orchestra .conductor::before{content:"";position:absolute;inset:-17px;border:1px dashed rgba(154,125,255,.24);border-radius:26px;pointer-events:none;opacity:.45;z-index:-1;animation:conductorRing 5.3s ease-in-out infinite reverse}
313
+ .orchestra .conductor .conductor-icon{overflow:visible;box-shadow:0 0 27px rgba(193,151,255,.6);position:relative}
314
+ .orchestra .conductor .conductor-icon::before{inset:-8px;border-width:2.5px;opacity:1}
315
+ .orchestra .conductor .conductor-icon::after{inset:-16px;border-color:#ae86fc;opacity:.7}
316
+ .orchestra .conductor .brain-symbol{animation:brainBreath 2.8s ease-in-out infinite}
317
+ .orchestra .conductor.running{animation:conductorBreathe 2.5s ease-in-out infinite,conductorPulse 1.3s ease-in-out infinite}
318
+ .orchestra .conductor.transfering{border-color:#eee0ff;box-shadow:0 0 48px rgba(180,112,255,.85),0 0 86px rgba(91,204,255,.29),inset 0 0 35px rgba(151,107,255,.34);animation:conductorDispatch .85s ease-out 1,conductorPulse 1.5s infinite}
319
+ .orchestra .conductor.transfering .brain-symbol{filter:drop-shadow(0 0 10px #e5bcff);animation:brainBreath .9s ease-in-out infinite}
320
+ .orchestra .conductor.transfering .conductor-icon::before{animation-duration:.95s;border-top-color:#fff;border-right-color:#80e9ff}
321
+ .orchestra .agents{max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));gap:88px clamp(65px,6vw,110px);padding:15px 13px 28px;margin:0 auto}
322
+ .orchestra .agent{min-height:228px;padding:18px 18px 15px;overflow:hidden;transition:transform .28s ease,box-shadow .3s ease,border-color .25s ease}
323
+ .orchestra .agent h3{font-size:18px;margin:7px 0 6px}
324
+ .orchestra .agent .agent-symbol{margin-top:8px}
325
+ .orchestra .agent .agent-steps{display:flex!important;flex-direction:column;gap:6px;min-height:110px;margin:12px 0 0;justify-content:flex-start}
326
+ .orchestra .agent .mini-visual{display:none!important}
327
+ .orchestra .agent .agent-bottom{display:none!important}
328
+ .orchestra .agent .agent-step{font-size:11px;line-height:1.4;min-height:19px;color:#9ab2d1;display:flex;gap:7px;animation:lineAppear .27s ease both}
329
+ .orchestra .agent .agent-step .step-mark{width:16px;height:16px;font-size:10px;flex:none;margin-top:1px}
330
+ .orchestra .agent .agent-step.current{color:#f4faff}
331
+ .orchestra .agent .agent-step.complete{color:#c5efe1}
332
+ .orchestra .agent .agent-step.skipped{color:#ffd39b}
333
+ .orchestra .agent.active{transform:translateY(-5px);border-color:var(--accent);box-shadow:0 0 31px color-mix(in srgb,var(--accent) 40%,transparent),inset 0 0 24px color-mix(in srgb,var(--accent) 13%,transparent)}
334
+ .orchestra .agent.active::after{animation:agentSweep 1.15s ease-in-out infinite;opacity:1}
335
+ .orchestra .agent.completed-pulse{animation:agentComplete .55s ease-out 1}
336
+ .orchestra .agent .agent-symbol{animation:iconFloat 3.4s ease-in-out infinite}
337
+ .network-links{position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none}
338
+ #networkSvg{display:block;width:100%;height:100%;overflow:hidden}
339
+ #networkEdges .network-base{fill:none;stroke:#486f94;stroke-width:2;opacity:.7;vector-effect:non-scaling-stroke;animation:none}
340
+ #networkEdges .network-complete{fill:none;stroke:#50cead;stroke-width:2.1;opacity:.47;vector-effect:non-scaling-stroke;animation:none}
341
+ #networkEdges .network-active{fill:none;stroke:#83e9ff;stroke-width:3.4;stroke-dasharray:9 14;opacity:1;vector-effect:non-scaling-stroke;animation:networkChase .72s linear infinite;filter:drop-shadow(0 0 6px #5addff)}
342
+ #networkEdges .network-active-glow{fill:none;stroke:#80dfff;stroke-width:10;opacity:.21;vector-effect:non-scaling-stroke;filter:blur(3px)}
343
+ #networkSignals .network-orb{fill:#e5ffff;stroke:#70eaff;stroke-width:1.5;filter:drop-shadow(0 0 9px #6deeff)}
344
+ #networkSignals .network-orb-halo{fill:#6feaff;opacity:.27;filter:blur(4px)}
345
+ .network-legend{margin-top:7px}
346
+ @keyframes networkChase{to{stroke-dashoffset:-46}}
347
+ @keyframes conductorBreathe{50%{filter:brightness(1.16)}}
348
+ @keyframes conductorRing{50%{box-shadow:0 0 27px rgba(189,147,255,.56);opacity:.96;transform:scale(1.022)}}
349
+ @keyframes conductorDispatch{0%,100%{transform:scale(1)}45%{transform:scale(1.045) translateY(-3px)}}
350
+ @keyframes agentComplete{0%{filter:brightness(1)}45%{filter:brightness(1.6)}100%{filter:brightness(1)}}
351
+ @keyframes lineAppear{from{opacity:.3;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}
352
+ @media(max-width:1120px){.orchestra .agents{gap:65px 34px}.orchestra .agent{min-height:235px;padding:12px}.orchestra .agent h3{font-size:15px}}
353
+ @media(max-width:760px){.orchestra .agents{grid-template-columns:repeat(2,minmax(0,1fr));gap:38px 15px}.orchestra .conductor-wrap{margin-bottom:38px}.orchestra .agent{min-height:248px}.orchestra .agent .agent-step{font-size:10px}.network-links{display:none}}
354
+ @media(max-width:470px){.orchestra .agents{grid-template-columns:1fr}.orchestra .agent{min-height:185px}.orchestra .conductor{width:100%;min-width:0}}
355
+ /* Neutraliseer oudere pseudo-elementafmetingen; anders verschijnt links een vreemd schuin vlak. */
356
+ .orchestra .conductor::before{inset:-17px!important;left:-17px!important;right:-17px!important;top:-17px!important;bottom:-17px!important;width:auto!important;height:auto!important;background:none!important;transform:none!important;filter:none!important}
357
+ .orchestra .conductor::after{inset:-8px!important;left:-8px!important;right:-8px!important;top:-8px!important;bottom:-8px!important;width:auto!important;height:auto!important;background:none!important;transform:none!important;filter:none!important}
358
+ @media(prefers-reduced-motion:reduce){.orchestra .conductor,.orchestra .conductor::before,.orchestra .conductor::after,.orchestra .conductor.transfering,.orchestra .agent .agent-symbol,.orchestra .agent .agent-step,.orchestra .agent.active::after,#networkEdges .network-active,.orchestra .agent.completed-pulse{animation:none!important}#networkSignals{display:none}}
359
+
360
  </style></head>
361
  <body>
362
  <div class="app">
363
  <main class="main">
364
+ <header><div class="header-copy"><div class="eyebrow">REAKT / AI ANALYTICS PLATFORM</div><h1>Inloop <span>Intelligence</span> <em>ORCHESTRA</em></h1><p>Maak complexe informatie begrijpelijk en toegankelijk voor iedereen.</p></div><div class="orchestra-hero-title" aria-label="ORCHESTRA by Marcel Ooms"><span class="orchestra-hero-word">ORCHESTRA</span><span class="orchestra-hero-credit">by Marcel Ooms</span></div><div class="header-center-logo"><div class="reakt-wordmark" role="img" aria-label="Reakt — Werken aan je toekomst"><strong class="reakt-name">Reakt</strong><span class="reakt-tagline">Werken aan je toekomst</span></div></div><div class="header-actions"><span class="pill"><i></i> LOKALE ANALYSE</span><span class="clock" id="clock"></span></div></header>
365
+ <div class="topbar"><div><span class="section-kicker">CONTROL CENTER</span><h2>Agent Command Center <span class="live-dot"></span></h2></div><div class="actions"><label class="pace-label" for="paceSelect">Weergave <select id="paceSelect" aria-label="Animatiesnelheid"><option value="presentation">Presentatie ± 30 sec</option><option value="fast">Snel ± 6 sec</option></select></label><button id="resetBtn" class="ghost">↺ Reset</button><button id="startBtn" class="primary">▶ Start voorbeeldanalyse</button></div></div>
366
  <section class="import-panel panel" aria-label="Lokale Excelanalyse met 2026 en historische referentie 2025">
367
+ <div class="import-intro"><span class="section-kicker">ORCHESTRA NEXT · BROWSER-LOKAAL</span><strong>Scanregistratie 2026 + referentie 2025</strong><small>Browser-lokale analyse · alle bronlocaties · geen bestanden in Space-repository</small></div>
368
  <label class="import-file-label" for="scanFile"><span>▤</span> Rapportage 2026 (.xlsx)</label><input type="file" id="scanFile" accept=".xlsx" aria-label="Rapportagebestand januari tot en met september 2026" />
369
  <label class="import-file-label history-label" for="historyFile"><span>◴</span> Historie 2025 (.xlsx)</label><input type="file" id="historyFile" accept=".xlsx" aria-label="Historisch referentiebestand 2025" disabled/>
370
  <label class="site-label" for="siteSelect">Locatie</label><select id="siteSelect" disabled><option value="*">Alle locaties</option></select>
 
373
  <div id="historyStatus" class="history-status">◴ Historie 2025 nog niet geladen. Zonder deze referentie zijn instroomcijfers voorlopig.</div>
374
  </section>
375
  <section class="orchestra panel">
376
+ <div class="mesh"></div><p class="next-narrative">Van ruwe scangegevens naar helder inzicht · volg taakverdeling, gegevensoverdracht en controles onder de motorkap.</p><div class="conductor-wrap"><div class="conductor" id="conductor"><div class="conductor-glow"></div><div class="conductor-icon" aria-hidden="true"><svg class="brain-symbol" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5c-2.7-3.2-7.4-.9-7.1 2.4C2 9 2 13.6 5 15.2c-.7 4.4 4.9 6.8 7 2.6M12 5c2.7-3.2 7.4-.9 7.1 2.4 2.9 1.6 2.9 6.2-.1 7.8.7 4.4-4.9 6.8-7 2.6M12 5v13"/><path d="M6.6 10.1 9 12v3M17.4 10.1 15 12v3"/><circle cx="9" cy="15.5" r=".9" fill="currentColor" stroke="none"/><circle cx="15" cy="15.5" r=".9" fill="currentColor" stroke="none"/></svg></div><div><small>MASTER ORCHESTRATOR</small><strong>THE CONDUCTOR</strong><span id="conductorStatus">Gereed voor analyse</span></div><div class="conductor-indicator"></div></div></div>
377
+ <div class="network-links" aria-hidden="true"><svg id="networkSvg" preserveAspectRatio="none"><g id="networkEdges"></g><g id="networkSignals"></g></svg></div>
378
  <div class="agents">
379
  <article class="agent" id="agent-data" style="--accent:#34e6b3"><div class="agent-top"><span>01 / DATA</span><span class="status">STANDBY</span></div><div class="agent-symbol">▤</div><h3>Data Agent</h3><p>Structuur & kwaliteit</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual bars"><b></b><b></b><b></b><b></b><b></b><b></b><b></b></div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
380
+ <article class="agent" id="agent-analysis" style="--accent:#ffcb62"><div class="agent-top"><span>04 / ANALYSE</span><span class="status">STANDBY</span></div><div class="agent-symbol">⌁</div><h3>Analysis Agent</h3><p>Trends & vergelijkingen</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual spark">▁ ▃ ▂ ▅ ▄ ▆ ▇</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
381
+ <article class="agent" id="agent-insight" style="--accent:#51b9ff"><div class="agent-top"><span>06 / TREND</span><span class="status">STANDBY</span></div><div class="agent-symbol">✦</div><h3>Trend Agent</h3><p>Maandpatronen & duiding</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual rings">◎ <span>◉</span> ◎</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
382
+ <article class="agent" id="agent-verify" style="--accent:#b18aff"><div class="agent-top"><span>07 / CONTROLE</span><span class="status">STANDBY</span></div><div class="agent-symbol">⬡</div><h3>Verification Agent</h3><p>Schaduwberekeningen</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual checkicons">✓ &nbsp; ✓ &nbsp; ! &nbsp; ✓</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
383
+ <article class="agent" id="agent-report" style="--accent:#ff9a65"><div class="agent-top"><span>09 / RAPPORT</span><span class="status">STANDBY</span></div><div class="agent-symbol">▧</div><h3>Report Agent</h3><p>Infographic & export</p><div class="agent-steps" aria-live="polite" aria-label="Werkstappen"></div><div class="mini-visual reportlines">▰▰▰▰▰<br>▰▰▰▰<br>▰▰▰</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
384
+ <article class="agent" id="agent-identity" style="--accent:#53d5ec"><div class="agent-top"><span>02 / IDENTITEIT</span><span class="status">STANDBY</span></div><div class="agent-symbol">⛓</div><h3>Identity Agent</h3><p>Pas- en cliëntkoppelingen</p><div class="agent-steps" aria-live="polite"></div><div class="mini-visual">◎ ↔ ◎</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
385
+ <article class="agent" id="agent-history" style="--accent:#60bafa"><div class="agent-top"><span>03 / HISTORIE</span><span class="status">STANDBY</span></div><div class="agent-symbol">◷</div><h3>History Agent</h3><p>Referentie 2025</p><div class="agent-steps" aria-live="polite"></div><div class="mini-visual">2025 ↔ 2026</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
386
+ <article class="agent" id="agent-privacy" style="--accent:#f28cbd"><div class="agent-top"><span>08 / PRIVACY</span><span class="status">STANDBY</span></div><div class="agent-symbol">◇</div><h3>Privacy Agent</h3><p>Lokale verwerkingsroute</p><div class="agent-steps" aria-live="polite"></div><div class="mini-visual">◈ ⛨ ◈</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
387
+ <article class="agent" id="agent-quality" style="--accent:#ea88e9"><div class="agent-top"><span>10 / KWALITEIT</span><span class="status">STANDBY</span></div><div class="agent-symbol">✧</div><h3>Quality Agent</h3><p>Rapportagestatus</p><div class="agent-steps" aria-live="polite"></div><div class="mini-visual">✓ / !</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
388
  </div>
389
+ <div class="orchestra-footer"><span><i class="dot green"></i> Systeem gereed</span><span id="flowState">Wacht op startcommando</span><span>◌ <span id="completed">0</span> / 9 agenten voltooid</span></div>
390
  </section>
391
  <div class="content-grid">
392
  <div class="left-content">
 
440
  'use strict';
441
  let demoData=null, running=false, completed=0;
442
  const $=id=>document.getElementById(id);
443
+ const ids=['data','identity','history','analysis','insight','verify','privacy','report','quality'];
444
  const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
445
  const escapeHTML=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
446
  function clock(){ $('clock').textContent=new Intl.DateTimeFormat('nl-NL',{hour:'2-digit',minute:'2-digit',day:'numeric',month:'short'}).format(new Date()); }
447
  clock();setInterval(clock,60000);
448
  function renderKPIs(data){$('kpis').innerHTML=data.kpis.map(k=>`<div class="kpi"><small>${escapeHTML(k.label)}</small><strong>${escapeHTML(k.value)}</strong><span>${escapeHTML(k.change)}</span></div>`).join('');}
449
  function renderChecks(data){$('checks').innerHTML=data.checks.map(c=>{const delta=c.ours-c.shadow;return `<div class="check-row"><span>${escapeHTML(c.label)}</span><span>${c.ours.toLocaleString('nl-NL')} / ${c.shadow.toLocaleString('nl-NL')} &nbsp; <strong class="${delta?'bad':''}">${delta?'⚠ '+(delta>0?'+':'')+delta:'✓ 0'}</strong></span></div>`}).join('');}
450
+ function drawChart(data){const canvas=$('chart'),ctx=canvas.getContext('2d'),w=640,h=250;ctx.clearRect(0,0,w,h);ctx.font='13px Segoe UI';ctx.textAlign='left';const max=500;for(let i=0;i<=4;i++){let y=25+i*45;ctx.strokeStyle='#29405a';ctx.beginPath();ctx.moveTo(44,y);ctx.lineTo(w-15,y);ctx.stroke();ctx.fillStyle='#8298b4';ctx.fillText(String(Math.round(max*(4-i)/4)),5,y+4);}const cols=['#3ee1b6','#ffc66a','#5b9cff','#ad88ff'];data.locations.forEach((loc,index)=>{ctx.strokeStyle=cols[index];ctx.lineWidth=3;ctx.beginPath();loc.visits.forEach((v,j)=>{const x=72+(loc.visits.length===1?0:j*(w-105)/(loc.visits.length-1)),y=205-(v/max)*180;if(j===0)ctx.moveTo(x,y);else ctx.lineTo(x,y)});ctx.stroke();loc.visits.forEach((v,j)=>{const x=72+(loc.visits.length===1?0:j*(w-105)/(loc.visits.length-1)),y=205-(v/max)*180;ctx.fillStyle=cols[index];ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()})});ctx.textAlign='center';ctx.fillStyle='#a6b9d0';['JUL','AUG','SEP'].forEach((m,i)=>ctx.fillText(m,72+(3===1?0:i*(w-105)/2),239));}
451
  function log(text){const now=new Date().toLocaleTimeString('nl-NL',{hour:'2-digit',minute:'2-digit',second:'2-digit'});if($('logs').querySelector('.muted'))$('logs').innerHTML='';$('logs').insertAdjacentHTML('afterbegin',`<div class="logitem"><time>${now}</time>${escapeHTML(text)}</div>`)}
452
  // De lichtpunten volgen de vijf bestaande SVG-verbindingen. Ze lopen alleen tijdens een actieve processtap.
453
  const signalPaths={data:'M500 0 V30 H100 V90',analysis:'M500 0 V30 H300 V90',insight:'M500 0 V90',verify:'M500 0 V30 H700 V90',report:'M500 0 V30 H900 V90'};
 
927
  const colors=['#3ee1b6','#ffc66a','#5b9cff','#ad88ff'];
928
  series.forEach((loc,li)=>{
929
  ctx.strokeStyle=colors[li];ctx.lineWidth=3;ctx.beginPath();
930
+ loc.visits.forEach((v,j)=>{const x=72+(loc.visits.length===1?0:j*(w-105)/(loc.visits.length-1)),y=205-(v/max)*180;j===0?ctx.moveTo(x,y):ctx.lineTo(x,y)});ctx.stroke();
931
+ loc.visits.forEach((v,j)=>{const x=72+(loc.visits.length===1?0:j*(w-105)/(loc.visits.length-1)),y=205-(v/max)*180;ctx.fillStyle=colors[li];ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()});
932
  });ctx.fillStyle='#a6b9d0';ctx.textAlign='center';
933
  result.months.forEach((m,i)=>ctx.fillText(m,72+(result.months.length===1?0:i*(w-105)/(result.months.length-1)),239));
934
  const legend=document.querySelector('.legend');
 
1030
  $('startBtn').textContent='▶ Analyseer Excelbestand';
1031
  $('flowState').textContent='Excelbestand lokaal ingelezen · start de analyse';
1032
  log('Excelbestand lokaal verwerkt; analyse moet nog starten. Voor echte data eerst interne toestemming controleren.');
1033
+ }catch(err){$('startBtn').textContent='▶ Start voorbeeldanalyse';state.textContent='⚠ '+err.message;$('siteSelect').disabled=true;$('periodSelect').disabled=true;$('periodType').disabled=true;log('Excel-proef: '+err.message)}finally{$('startBtn').disabled=false}
1034
  });
1035
  $('historyFile').addEventListener('change',async e=>{
1036
  const file=e.target.files?.[0];if(!file||!importedData)return;
 
1254
  $('controllerCompare').addEventListener('click',compareController);
1255
  $('controllerClear').addEventListener('click',()=>resetControllerComparison(true));
1256
  controllerDefs.forEach(([,id])=>$(id).addEventListener('input',()=>{if(controllerComparison)resetControllerComparison(false)}));
1257
+
1258
+
1259
+ // ORCHESTRA NEXT v4. De concrete rekenfuncties hierboven blijven ongewijzigd.
1260
+ // De netwerkpaden worden aan de kaartRANDEN gekoppeld, niet achter het midden van de kaart.
1261
+ // Bewegende lichtpunten gebruiken requestAnimationFrame/getPointAtLength: zichtbaar in moderne browsers,
1262
+ // zonder afhankelijkheid van SVG animateMotion. De wachttijd is presentatie; echte rekentijd meten we apart.
1263
+ const nextRoutes=[
1264
+ ['conductor','identity'],['data','identity'],['identity','history'],
1265
+ ['data','analysis'],['identity','insight'],['history','verify'],
1266
+ ['analysis','insight'],['insight','verify'],['analysis','privacy'],
1267
+ ['insight','report'],['verify','quality'],['privacy','report'],['report','quality']
1268
+ ];
1269
+ const nextIds=['data','identity','history','analysis','insight','verify','privacy','report','quality'];
1270
+ const nextRouteMap=new Map();
1271
+ let nextActiveKeys=[],nextFinishedKeys=[],nextAnimationFrame=0,nextAnimationStart=0,nextSignalCircles=[],nextIsRunning=false,nextTransferTimeout=0;
1272
+ const nextMotionDisabled=()=>window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1273
+ const nextAgentElement=id=>id==='conductor'?$('conductor'):$('agent-'+id);
1274
+ const nextRouteKey=(a,b)=>a+'>'+b;
1275
+ const nextWait=ms=>new Promise(resolve=>setTimeout(resolve,ms));
1276
+ const nextShowCount=()=>{$('completed').textContent=String(nextIds.filter(id=>$('agent-'+id).classList.contains('done')).length)};
1277
+ function nextPathFor(a,b,frame){
1278
+ const acx=(a.left+a.right)/2,acy=(a.top+a.bottom)/2,bcx=(b.left+b.right)/2,bcy=(b.top+b.bottom)/2;
1279
+ const sameRow=Math.abs(acy-bcy)<Math.min(a.height,b.height)*.58;
1280
+ let x1,y1,x2,y2;
1281
+ if(sameRow){const towardsRight=bcx>=acx;x1=towardsRight?a.right:a.left;y1=acy;x2=towardsRight?b.left:b.right;y2=bcy;
1282
+ const bend=Math.min(20,Math.abs(x2-x1)*.15);return `M ${x1-frame.left} ${y1-frame.top} C ${x1-frame.left+(towardsRight?bend:-bend)} ${y1-frame.top-10},${x2-frame.left-(towardsRight?bend:-bend)} ${y2-frame.top+10},${x2-frame.left} ${y2-frame.top}`;}
1283
+ const downward=bcy>=acy;
1284
+ x1=acx;y1=downward?a.bottom:a.top;x2=bcx;y2=downward?b.top:b.bottom;
1285
+ const mid=(y1+y2)/2;
1286
+ return `M ${x1-frame.left} ${y1-frame.top} C ${x1-frame.left} ${mid-frame.top},${x2-frame.left} ${mid-frame.top},${x2-frame.left} ${y2-frame.top}`;
1287
+ }
1288
+ function nextMakeSvg(tag,attrs={},className=''){
1289
+ const n=document.createElementNS('http://www.w3.org/2000/svg',tag);
1290
+ Object.entries(attrs).forEach(([k,v])=>n.setAttribute(k,String(v)));
1291
+ if(className)n.setAttribute('class',className);
1292
+ return n;
1293
+ }
1294
+ function nextBuildGraph(){
1295
+ const root=document.querySelector('.orchestra'),svg=$('networkSvg'),edge=$('networkEdges');
1296
+ if(!root||!svg||!edge)return;
1297
+ const frame=root.getBoundingClientRect();if(!frame.width||!frame.height)return;
1298
+ svg.setAttribute('viewBox',`0 0 ${frame.width} ${frame.height}`);
1299
+ edge.replaceChildren();nextRouteMap.clear();
1300
+ for(const [from,to] of nextRoutes){
1301
+ const a=nextAgentElement(from)?.getBoundingClientRect(),b=nextAgentElement(to)?.getBoundingClientRect();
1302
+ if(!a||!b)continue;
1303
+ const d=nextPathFor(a,b,frame),key=nextRouteKey(from,to);
1304
+ const base=nextMakeSvg('path',{d},'network-base');base.dataset.route=key;edge.appendChild(base);
1305
+ const glow=nextMakeSvg('path',{d},'network-active-glow');glow.dataset.route=key;edge.appendChild(glow);
1306
+ const flow=nextMakeSvg('path',{d},'network-active');flow.dataset.route=key;edge.appendChild(flow);
1307
+ nextRouteMap.set(key,{base,glow,flow,length:0});
1308
+ }
1309
+ nextDrawRouteStates();
1310
+ }
1311
+ function nextDrawRouteStates(){
1312
+ for(const [key,r] of nextRouteMap){
1313
+ const active=nextActiveKeys.includes(key),done=nextFinishedKeys.includes(key);
1314
+ r.base.setAttribute('class',done?'network-complete':'network-base');
1315
+ r.glow.style.display=active?'':'none';r.flow.style.display=active?'':'none';
1316
+ }
1317
+ nextBuildOrbs();
1318
+ }
1319
+ function nextBuildOrbs(){
1320
+ const layer=$('networkSignals');if(!layer)return;
1321
+ if(nextAnimationFrame)cancelAnimationFrame(nextAnimationFrame);
1322
+ nextAnimationFrame=0;nextSignalCircles=[];layer.replaceChildren();
1323
+ if(!nextActiveKeys.length||nextMotionDisabled()||!document.querySelector('.orchestra.running'))return;
1324
+ for(const key of nextActiveKeys){
1325
+ const r=nextRouteMap.get(key);if(!r)continue;
1326
+ try{r.length=r.flow.getTotalLength()}catch(e){continue}
1327
+ if(!r.length)continue;
1328
+ for(let j=0;j<2;j++){
1329
+ const halo=nextMakeSvg('circle',{r:13,cx:0,cy:0},'network-orb-halo');
1330
+ const dot=nextMakeSvg('circle',{r:4.3,cx:0,cy:0},'network-orb');
1331
+ layer.append(halo,dot);nextSignalCircles.push({r,halo,dot,offset:j*.52});
1332
+ }
1333
+ }
1334
+ nextAnimationStart=performance.now();
1335
+ if(nextSignalCircles.length)nextAnimationFrame=requestAnimationFrame(nextTickOrbs);
1336
+ }
1337
+ function nextTickOrbs(now){
1338
+ const ms=$('paceSelect').value==='fast'?560:1450;
1339
+ for(const o of nextSignalCircles){
1340
+ const progress=((now-nextAnimationStart)/ms+o.offset)%1;
1341
+ const p=o.r.flow.getPointAtLength(o.r.length*progress);
1342
+ o.halo.setAttribute('cx',p.x);o.halo.setAttribute('cy',p.y);
1343
+ o.dot.setAttribute('cx',p.x);o.dot.setAttribute('cy',p.y);
1344
+ }
1345
+ nextAnimationFrame=requestAnimationFrame(nextTickOrbs);
1346
+ }
1347
+ function nextConductorSignal(){
1348
+ const c=$('conductor');if(!c)return;
1349
+ clearTimeout(nextTransferTimeout);c.classList.remove('transfering');
1350
+ // Herstart de CSS-puls betrouwbaar bij elke OVERDRACHT, ook bij opeenvolgende overdrachten.
1351
+ void c.offsetWidth;c.classList.add('transfering');
1352
+ nextTransferTimeout=setTimeout(()=>c.classList.remove('transfering'),$('paceSelect').value==='fast'?500:1000);
1353
+ }
1354
+ function nextRoutesActive(pairs=[]){
1355
+ nextActiveKeys=pairs.map(([a,b])=>nextRouteKey(a,b)).filter(k=>nextRouteMap.has(k));
1356
+ nextDrawRouteStates();
1357
+ if(nextActiveKeys.length)nextConductorSignal();
1358
+ }
1359
+ function nextLines(id,lines,doneIndex=-1,warning=false){
1360
+ const card=$('agent-'+id),wrap=card.querySelector('.agent-steps');card.classList.add('has-steps');wrap.replaceChildren();
1361
+ lines.forEach((line,i)=>{
1362
+ const item=document.createElement('div');item.className='agent-step '+(i<=doneIndex?(warning?'skipped':'complete'):i===doneIndex+1?'current':'');
1363
+ const mark=document.createElement('span');mark.className='step-mark';mark.textContent=i<=doneIndex?(warning?'!':'✓'):i===doneIndex+1?'◌':'·';
1364
+ const txt=document.createElement('span');txt.textContent=line;
1365
+ item.append(mark,txt);wrap.appendChild(item);
1366
+ });
1367
+ }
1368
+ function nextSetStatus(id,value){
1369
+ const card=$('agent-'+id);card.classList.toggle('active',value==='active');card.classList.toggle('done',value==='done'||value==='warning');
1370
+ card.querySelector('.status').textContent={active:'ACTIEF',done:'GEREED',warning:'LET OP',skipped:'NIET BESCHIKBAAR',idle:'STANDBY'}[value]||'STANDBY';
1371
+ }
1372
+ function nextInitCards(){
1373
+ for(const id of nextIds){
1374
+ const card=$('agent-'+id);card.classList.remove('has-steps','active','done','completed-pulse');
1375
+ nextSetStatus(id,'idle');
1376
+ nextLines(id,['Wacht op lokale analyse','Werkstappen verschijnen hier','Overdracht wordt zichtbaar'], -2);
1377
+ }
1378
+ nextShowCount();
1379
+ }
1380
+ async function nextPhase(id,lines,routes=[],warning=false){
1381
+ nextSetStatus(id,'active');nextLines(id,lines,-1,false);
1382
+ nextRoutesActive(routes);
1383
+ $('conductorStatus').textContent='Overdracht · '+$('agent-'+id).querySelector('h3').textContent;
1384
+ $('flowState').textContent=lines[0];
1385
+ log('Procesweergave: '+$('agent-'+id).querySelector('h3').textContent);
1386
+ const phaseDuration=$('paceSelect').value==='fast'?650:3300;
1387
+ const durationStep=phaseDuration/lines.length;
1388
+ for(let i=0;i<lines.length;i++){
1389
+ await nextWait(durationStep);
1390
+ nextLines(id,lines,i,false);
1391
+ $('flowState').textContent=lines[i];
1392
+ }
1393
+ for(const key of nextActiveKeys)if(!nextFinishedKeys.includes(key))nextFinishedKeys.push(key);
1394
+ nextSetStatus(id,warning?'warning':'done');nextLines(id,lines,lines.length-1,warning);
1395
+ const card=$('agent-'+id);card.classList.remove('completed-pulse');void card.offsetWidth;card.classList.add('completed-pulse');
1396
+ log((warning?'Aandacht: ':'Afgerond: ')+id+' · '+lines.at(-1));
1397
+ nextShowCount();
1398
+ }
1399
+ function nextClearNetwork(reset=false){
1400
+ nextActiveKeys=[];if(reset)nextFinishedKeys=[];
1401
+ clearTimeout(nextTransferTimeout);$('conductor').classList.remove('transfering');
1402
+ if(nextAnimationFrame)cancelAnimationFrame(nextAnimationFrame);nextAnimationFrame=0;
1403
+ nextSignalCircles=[];$('networkSignals')?.replaceChildren();nextDrawRouteStates();
1404
+ }
1405
+ function nextResetUI(){nextClearNetwork(true);nextInitCards();requestAnimationFrame(nextBuildGraph)}
1406
+ const nextOriginalReset=reset;
1407
+ reset=function(){nextOriginalReset();nextResetUI()};
1408
+ $('resetBtn').removeEventListener('click',nextOriginalReset);
1409
+ $('resetBtn').addEventListener('click',reset);
1410
+ const nextOriginalStart=start;
1411
+ async function nextStartImported(){
1412
+ if(running||!importedData)return;
1413
+ running=true;nextIsRunning=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;
1414
+ const conductor=$('conductor');conductor.classList.remove('attention');conductor.classList.add('running');
1415
+ document.querySelector('.orchestra').classList.add('running');
1416
+ $('logs').replaceChildren();$('logStatus').textContent='Weergave actief';
1417
+ nextFinishedKeys=[];nextInitCards();nextBuildGraph();
1418
+ const timestamp=performance.now(),fmt=n=>Number(n||0).toLocaleString('nl-NL');let cpu=0;
1419
+ try{
1420
+ await nextPhase('data',[
1421
+ 'Bestand 2026 eerder lokaal ingelezen',
1422
+ fmt(importedData.rawCount)+' bronregels beoordeeld bij het inlezen',
1423
+ importedData.sites.length+' inlooplocaties herkend'
1424
+ ],[['conductor','identity'],['data','identity']]);
1425
+ const t1=performance.now();
1426
+ const result=calculateImported($('siteSelect').value);
1427
+ cpu+=performance.now()-t1;
1428
+ await nextPhase('identity',[
1429
+ 'Pas- en cliëntkoppelingen toegepast',
1430
+ fmt(result.multiPassClients)+' cliënten met meerdere passen gesignaleerd',
1431
+ 'Herhaalde scans apart geteld: '+fmt(result.duplicates)
1432
+ ],[['data','identity'],['identity','history']]);
1433
+ await nextPhase('history',result.historyLoaded?[
1434
+ 'Historische referentie 2025 gebruikt',
1435
+ fmt(result.correctedExisting)+' cliënten uit 2025 herkend',
1436
+ 'Eerste registraties over jaren vergeleken'
1437
+ ]:[
1438
+ 'Geen referentiebestand 2025 geladen',
1439
+ 'Eerdere bezoeken zijn niet volledig te toetsen',
1440
+ 'Nieuwe instroom blijft voorlopig'
1441
+ ],[['identity','history'],['history','verify']],!result.historyLoaded);
1442
+ await nextPhase('analysis',[
1443
+ fmt(result.rows)+' scans in de geselecteerde periode',
1444
+ fmt(result.visits)+' unieke bezoekdagen vastgesteld',
1445
+ fmt(result.active)+' actieve cliënten berekend'
1446
+ ],[['data','analysis'],['identity','insight']]);
1447
+ await nextPhase('insight',[
1448
+ result.months.length+' maandperioden verwerkt',
1449
+ 'Getelde trends beschikbaar voor de grafiek',
1450
+ 'Geen AI-duiding: alleen feitelijke patronen'
1451
+ ],[['analysis','insight'],['insight','verify']]);
1452
+ const v=performance.now();result.verification=independentlyVerify(result);importedData.last=result;cpu+=performance.now()-v;
1453
+ await nextPhase('verify',[
1454
+ result.verification.checks.length+' interne telvergelijkingen uitgevoerd',
1455
+ result.verification.mismatches+' interne telverschillen gevonden',
1456
+ result.verification.sourceIssues.length+' typen bronafwijkingen gerapporteerd'
1457
+ ],[['history','verify'],['insight','verify'],['verify','quality']],result.verification.mismatches>0);
1458
+ await nextPhase('privacy',[
1459
+ 'Excelgegevens lokaal in de browser verwerkt',
1460
+ 'Geen AI-API voor deze agentweergave',
1461
+ 'Geen zelfstandige AVG-certificering uitgevoerd'
1462
+ ],[['analysis','privacy']]);
1463
+ resetControllerComparison(true);updateControllerContext();
1464
+ const t3=performance.now();renderImported(result);cpu+=performance.now()-t3;
1465
+ await nextPhase('report',[
1466
+ 'Dashboard met berekende KPI’s bijgewerkt',
1467
+ 'Witte managementrapportage beschikbaar',
1468
+ 'Concept: niet extern gevalideerd'
1469
+ ],[['insight','report'],['privacy','report'],['report','quality']]);
1470
+ const concern=result.verification.mismatches>0||!result.historyLoaded;
1471
+ await nextPhase('quality',[
1472
+ 'Interne rekenvergelijking: '+(result.verification.mismatches?'afwijking':'geen telverschil'),
1473
+ 'Historie 2025: '+(result.historyLoaded?'meegenomen':'niet aangeleverd'),
1474
+ 'Geen formele vrijgave door businesscontroller'
1475
+ ],[['verify','quality'],['report','quality']],concern);
1476
+ conductor.classList.add('attention');$('conductorStatus').textContent='Verwerkingsweergave afgerond';
1477
+ $('flowState').textContent='Concept beschikbaar · NIET EXTERN GEVALIDEERD';$('logStatus').textContent='Afgerond';
1478
+ log(`Echte berekening, interne verificatie en weergave: ${(cpu/1000).toFixed(2)} sec. Geanimeerde procesweergave: ${((performance.now()-timestamp)/1000).toFixed(1)} sec (exclusief Excel-invoer).`);
1479
+ }catch(err){log('FOUT: '+err.message);$('flowState').textContent='Berekening of weergave mislukt';conductor.classList.add('attention');$('conductorStatus').textContent='Fout gedetecteerd';}
1480
+ finally{nextClearNetwork(false);running=false;nextIsRunning=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;conductor.classList.remove('running');document.querySelector('.orchestra').classList.remove('running');}
1481
+ }
1482
+ start=async function(){
1483
+ if(running)return;
1484
+ if(importedData)return nextStartImported();
1485
+ nextClearNetwork(true);
1486
+ for(const id of ['identity','history','privacy','quality']){nextSetStatus(id,'skipped');nextLines(id,['Geen echte Excelbron in demonstratie','Controle niet uitgevoerd'],1,true);}
1487
+ return nextOriginalStart();
1488
+ };
1489
+ $('startBtn').removeEventListener('click',nextOriginalStart);
1490
+ $('startBtn').addEventListener('click',start);
1491
+ window.addEventListener('resize',()=>requestAnimationFrame(nextBuildGraph));
1492
+ if(typeof ResizeObserver!=='undefined')new ResizeObserver(()=>requestAnimationFrame(nextBuildGraph)).observe(document.querySelector('.orchestra'));
1493
+ nextInitCards();requestAnimationFrame(nextBuildGraph);
1494
+
1495
  </script></body></html>