File size: 48,904 Bytes
fabf837
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
<!doctype html>
<html lang="nl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Reakt | ORCHESTRA</title><style>
:root{font-family:Inter,'Segoe UI',Arial,sans-serif;color:#eaf2ff;background:#060d1d;font-synthesis:none}*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 50% -15%,#162d58 0%,#081224 43%,#060b18 100%);min-height:100vh}button,input{font:inherit}button{cursor:pointer}button:disabled{opacity:.5;cursor:wait}.app{display:flex;min-height:100vh}.sidebar{width:225px;flex:none;border-right:1px solid #223351;background:#091326;padding:25px 16px;position:sticky;top:0;height:100vh}.brand{display:flex;align-items:center;gap:12px;margin-bottom:55px}.brand-icon{font-size:34px;color:#54d9ff;text-shadow:0 0 20px #38baff}.brand strong{font-size:24px;letter-spacing:1.5px}.brand-dot,.paper-top span{color:#58d8f5}.brand small{display:block;font-size:9px;letter-spacing:1.8px;color:#7791b5;margin-top:3px}.nav-label{font-size:10px;letter-spacing:1.5px;color:#607a9e;margin:0 8px 15px}.nav-label.second{margin-top:42px}.nav{display:flex;gap:13px;align-items:center;padding:13px 12px;color:#8fa6c7;font-size:13px;border-radius:9px;margin-bottom:5px}.nav.active{color:#c5ecff;background:linear-gradient(90deg,#153d68,#102039);border:1px solid #24517c;box-shadow:inset 3px 0 #4fc9ff}.side-note{border:1px solid #244568;border-radius:10px;background:#0d2137;padding:15px;color:#c5e2f7;font-size:12px;line-height:2}.side-note small{color:#839dbb}.dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#8194ad;margin-right:7px}.dot.green{background:#32e3aa;box-shadow:0 0 10px #32e3aa}.sidebar-bottom{position:absolute;bottom:26px;font-size:12px;color:#8ba3c2;line-height:1.9}.sidebar-bottom span{color:#40cbfb}.sidebar-bottom small{font-size:10px}.main{min-width:0;flex:1;max-width:1900px;padding:24px 30px;margin:auto}header{display:flex;justify-content:space-between;gap:16px;align-items:center;padding-bottom:23px;border-bottom:1px solid #1c3451}.eyebrow,.section-kicker{font-size:10px;letter-spacing:2px;color:#56b8e9;font-weight:700}h1{font-size:29px;letter-spacing:-.7px;margin:8px 0 5px;font-weight:800}h1 span{color:#75b8ff}h1 em{font-size:10px;font-style:normal;border:1px solid #674cb5;border-radius:5px;color:#bda7ff;padding:5px 8px;letter-spacing:1px;vertical-align:middle;margin-left:8px}header p{margin:0;color:#8299b8;font-size:12px}.header-actions{display:flex;align-items:center;gap:20px}.pill{border:1px solid #23866c;color:#7aefd0;background:#0b2e2c;border-radius:30px;padding:9px 14px;font-size:10px;font-weight:700;letter-spacing:1px}.pill i{display:inline-block;width:6px;height:6px;background:#54f6bc;border-radius:50%;margin-right:7px}.clock{color:#879dbc;font-size:12px}.topbar{display:flex;justify-content:space-between;align-items:end;margin:23px 0 13px}.topbar h2{font-size:20px;margin:6px 0 0}.live-dot{display:inline-block;background:#34dca7;width:7px;height:7px;border-radius:50%;vertical-align:middle;box-shadow:0 0 13px #24e6a7}.actions{display:flex;gap:10px}button{border:0;border-radius:8px;padding:11px 15px;color:#e9f4ff;background:#172c48;font-size:12px;font-weight:700}.ghost{border:1px solid #2c4568}.primary{background:linear-gradient(110deg,#1369d9,#6b47d4);box-shadow:0 5px 25px #235be355}.panel{border:1px solid #223a5a;background:linear-gradient(150deg,#101e35ee,#0b1629ee);border-radius:13px;box-shadow:0 10px 35px #0003}.orchestra{position:relative;overflow:hidden;padding:18px 20px 0;isolation:isolate}.mesh{position:absolute;inset:0;z-index:-1;background-image:linear-gradient(#28507914 1px,transparent 1px),linear-gradient(90deg,#28507914 1px,transparent 1px);background-size:24px 24px;mask-image:linear-gradient(to bottom,transparent,#000 20%,#000 80%,transparent)}.conductor-wrap{display:flex;justify-content:center}.conductor{width:285px;display:flex;align-items:center;gap:14px;padding:13px 17px;border:1px solid #8054df;border-radius:13px;background:linear-gradient(110deg,#2d1e57,#161d43);box-shadow:0 0 26px #864de94a, inset 0 0 20px #a063ff17;position:relative}.conductor-icon{width:46px;height:46px;display:grid;place-items:center;border-radius:50%;border:1px solid #9a73ef;color:#ddc5ff;font-size:29px;text-shadow:0 0 15px #b79bff;box-shadow:0 0 20px #a77aff44}.conductor small{display:block;color:#ae9bd7;font-size:9px;letter-spacing:1.5px}.conductor strong{display:block;font-size:17px;margin:4px 0;font-weight:750}.conductor span{font-size:12px;color:#cbbce9}.conductor-indicator{position:absolute;right:14px;top:14px;width:7px;height:7px;border-radius:50%;background:#8e7bad}.conductor.running{animation:conductorPulse 1.5s infinite}.conductor.running .conductor-indicator{background:#c5a2ff;box-shadow:0 0 12px #d1a9ff}.connections{display:block;width:100%;height:90px;overflow:visible}.connections path{fill:none;stroke:#38527a;stroke-width:1.5;vector-effect:non-scaling-stroke}.connections .flowline{stroke:#a77bff;stroke-width:2;stroke-dasharray:7 20;opacity:.2}.orchestra.running .flowline{opacity:1;animation:dash 1.2s linear infinite;filter:drop-shadow(0 0 5px #b38cff)}.agents{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;position:relative}.agent{min-width:0;border:1px solid color-mix(in srgb,var(--accent) 55%,#1d3150);background:linear-gradient(150deg,#142841,#0a182c);border-radius:12px;padding:13px 12px 11px;box-shadow:inset 0 0 25px color-mix(in srgb,var(--accent) 6%,transparent);transition:box-shadow .3s,transform .3s,border-color .3s}.agent.active{transform:translateY(-5px);border-color:var(--accent);box-shadow:0 0 24px color-mix(in srgb,var(--accent) 43%,transparent),inset 0 0 22px color-mix(in srgb,var(--accent) 12%,transparent)}.agent.done{border-color:var(--accent)}.agent-top{display:flex;justify-content:space-between;gap:4px;align-items:center;color:var(--accent);font-size:9px;letter-spacing:1px}.agent .status{font-size:8px;color:#8499b6;letter-spacing:0}.agent.active .status{color:var(--accent)}.agent-symbol{font-size:27px;color:var(--accent);margin-top:13px;text-shadow:0 0 17px var(--accent)}.agent h3{font-size:17px;line-height:1.22;font-weight:750;letter-spacing:.1px;margin:9px 0 5px}.agent p{font-size:13px;line-height:1.35;color:#c2d2e8;margin:0;font-weight:500}.mini-visual{height:42px;margin:13px 0;color:var(--accent);font-size:24px;display:flex;align-items:center;justify-content:center}.mini-visual.bars{align-items:end;gap:6px;padding-bottom:6px}.bars b{background:var(--accent);opacity:.8;width:9px;border-radius:2px 2px 0 0;height:45%}.bars b:nth-child(2){height:80%}.bars b:nth-child(3){height:55%}.bars b:nth-child(4){height:95%}.bars b:nth-child(5){height:65%}.bars b:nth-child(6){height:75%}.bars b:nth-child(7){height:45%}.spark{font-size:27px;letter-spacing:-2px}.rings{font-size:22px;gap:9px}.rings span{font-size:29px}.checkicons{font-size:19px}.reportlines{font-size:13px;line-height:1.1;letter-spacing:3px}.agent-bottom{display:flex;align-items:center;gap:7px}.track{height:5px;flex:1;background:#26344e;border-radius:20px;overflow:hidden}.track i{display:block;height:100%;width:0;background:var(--accent);box-shadow:0 0 9px var(--accent);transition:width .13s linear}.percent{font-size:9px;color:#a3b3cc;width:25px;text-align:right}.orchestra-footer{display:flex;justify-content:space-between;gap:10px;border-top:1px solid #203550;margin-top:20px;padding:13px 0;color:#849bb8;font-size:10px}.content-grid{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(295px,.75fr);gap:14px;margin-top:15px}.left-content,.right-content{display:flex;flex-direction:column;gap:14px;min-width:0}.kpi-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:11px}.kpi{padding:15px;border:1px solid #2b4160;background:linear-gradient(135deg,#11223b,#0c192d);border-radius:10px}.kpi small{display:block;font-size:10px;color:#829bbb}.kpi strong{display:block;font-size:24px;margin:8px 0 3px}.kpi span{color:#55dcb5;font-size:10px}.kpi:last-child span{color:#ffba6b}.charts-grid{display:grid;grid-template-columns:1.15fr 1fr;gap:14px}.chart-panel,.verification,.logpanel,.chatpanel,.preview{padding:17px}.panel-heading{display:flex;align-items:start;justify-content:space-between;gap:10px;margin-bottom:12px}.panel-heading h3{font-size:14px;margin:5px 0}.subtle{font-size:10px;color:#8aa2c0}.chart-panel canvas{display:block;width:100%;height:165px}.legend{display:flex;gap:10px;flex-wrap:wrap;color:#9caec7;font-size:9px;margin-top:10px}.legend i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:3px}.checks{display:flex;flex-direction:column;gap:9px}.check-row{display:flex;justify-content:space-between;font-size:10px;color:#aebed3;border-bottom:1px solid #223650;padding-bottom:7px;gap:5px}.check-row strong{color:#61e4b5}.check-row strong.bad{color:#ffb56b}.warning{background:#5b3d1e55;border:1px solid #9c6b37;border-radius:7px;padding:9px;margin-top:12px;color:#ffc48b;font-size:10px}.warn-label{font-size:9px;color:#ffb971;border:1px solid #7b5834;padding:5px 8px;border-radius:5px}.logpanel{min-height:145px}.logs{max-height:150px;overflow:auto;font-size:11px;color:#abc0d9}.logitem{padding:6px 0;border-bottom:1px solid #1d314b}.logitem time{color:#6985a7;margin-right:10px}.logitem.muted{color:#7287a4}.chatpanel{display:flex;flex-direction:column;min-height:425px}.chat-icon{color:#b2a0ff}.demo-badge{font-size:9px;border:1px solid #4a6384;color:#9eb5d3;padding:5px 7px;border-radius:6px}.chat-messages{flex:1;overflow:auto;max-height:275px;min-height:140px;display:flex;flex-direction:column;gap:12px}.message{padding:12px;border-radius:10px;font-size:11px;line-height:1.55}.message.assistant{background:#142c49;border:1px solid #264c74;align-self:flex-start}.message.user{background:#25385c;border:1px solid #3e5681;align-self:flex-end}.message small{color:#6ed0f2;font-size:9px;letter-spacing:1px}.message p{margin:6px 0 0}.quick-questions{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0}.quick-questions button{font-size:9px;padding:7px;background:#152944;border:1px solid #315075}.chat-form{display:flex;gap:7px}.chat-form input{min-width:0;flex:1;background:#0b1b30;border:1px solid #2c4b6e;border-radius:8px;padding:12px;color:white;font-size:11px}.chat-form button{background:#275cba;font-size:16px;padding:7px 13px}.chat-disclaimer{font-size:9px;color:#7e91ac;margin-top:9px}.paper{background:#f3f7fd;color:#163354;border-radius:5px;padding:16px;min-height:225px;position:relative;overflow:hidden}.paper-top{display:flex;justify-content:space-between;align-items:center;border-bottom:2px solid #0e618c;padding-bottom:7px}.paper-top strong{font-size:14px}.paper-top small{font-size:7px;color:#5b7490}.paper h4{font-size:18px;margin:14px 0 2px}.paper p{font-size:9px;color:#67809c;margin:0}.paper-kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:5px;margin:13px 0}.paper-kpis div{background:#e0ebf5;border-radius:4px;padding:7px}.paper-kpis strong{font-size:14px;display:block}.paper-kpis small{font-size:8px;color:#52728c}.paper-bars{height:43px;display:flex;align-items:end;gap:5px}.paper-bars i{flex:1;height:45%;background:#338bb9}.paper-bars i:nth-child(3n){height:80%;background:#f2aa65}.paper-bars i:nth-child(4n){height:65%}.paper-bars i:nth-child(5n){height:95%;background:#42b9a2}.paper-watermark{position:absolute;bottom:0;left:0;right:0;background:#a13c3c;color:white;text-align:center;padding:4px;font-size:9px;letter-spacing:1px}.export{width:100%;margin-top:10px;border:1px solid #345579;background:#183354}.export:disabled{opacity:.6}footer{display:flex;justify-content:space-between;gap:10px;padding:22px 0;color:#6e88a9;font-size:9px;letter-spacing:1px}@keyframes dash{to{stroke-dashoffset:-54}}@keyframes conductorPulse{50%{box-shadow:0 0 42px #9a6bffbb,inset 0 0 28px #a063ff33}}@media(max-width:1250px){.sidebar{width:185px}.main{padding:18px}.agents{gap:7px}.agent{padding:10px 8px}.agent h3{font-size:14px}.agent p{font-size:12px}.charts-grid{grid-template-columns:1fr}}@media(max-width:900px){.sidebar{display:none}.content-grid{grid-template-columns:1fr}.right-content{display:grid;grid-template-columns:1fr 1fr}.agents{grid-template-columns:repeat(5,minmax(0,1fr))}}@media(max-width:680px){.main{padding:12px}header{align-items:start}h1{font-size:21px}.header-actions .clock{display:none}.topbar{align-items:start;gap:10px}.topbar h2{font-size:16px}.actions{flex-direction:column}.agents{grid-template-columns:repeat(2,minmax(0,1fr))}.connections{display:none}.orchestra{padding:12px}.agent:last-child{grid-column:span 2}.kpi-grid{grid-template-columns:repeat(2,1fr)}.right-content{display:flex}.orchestra-footer{flex-wrap:wrap}.clock{display:none}footer span{display:none}}@media print{.sidebar,header,.topbar,.orchestra,.chatpanel,.logpanel,.chart-panel,.verification,.kpi-grid,footer,.export{display:none!important}.content-grid,.right-content{display:block}.preview{border:0;background:white}.paper{max-width:750px;min-height:800px;margin:auto}.paper-watermark{font-size:15px}.paper h4{font-size:40px}}


/* ORCHESTRA v0.3 — alleen de dirigent: subtiele activiteit en gestuurde lichtsignalen */
.conductor-icon {position:relative;isolation:isolate;flex:none;}
.conductor-icon::before,.conductor-icon::after {content:"";position:absolute;border-radius:50%;pointer-events:none;}
.conductor-icon::before {inset:-7px;border:2px solid transparent;border-top-color:#cfb0ff;border-right-color:#9367ff;filter:drop-shadow(0 0 5px #ab74ff);animation:orbitConductor 10s linear infinite;opacity:.9;}
.conductor-icon::after {inset:-3px;border:1px dashed #9470db;animation:orbitConductor 18s linear infinite reverse;opacity:.38;}
.conductor-icon .brain-symbol {width:30px;height:30px;display:block;position:relative;z-index:1;filter:drop-shadow(0 0 5px #ad82ff);animation:brainBreath 3.8s ease-in-out infinite;transform-origin:center;}
.conductor.running .conductor-icon::before {animation-duration:2.1s;border-top-color:#e6d3ff;border-right-color:#be98ff;filter:drop-shadow(0 0 9px #c99aff);}
.conductor.running .conductor-icon::after {animation-duration:5s;opacity:.75;}
.conductor.running .brain-symbol {animation-duration:1.7s;}
.conductor.attention .conductor-icon::before {border-top-color:#ffbc6d;border-right-color:#f59c4b;filter:drop-shadow(0 0 6px #ffb167);}
.conductor.attention .brain-symbol {color:#ffd3a0;filter:drop-shadow(0 0 7px #ffb167);}
.connections .signal-layer .signal-particle {stroke:none;filter:drop-shadow(0 0 4px #c69aff);}
.connections .signal-layer .signal-halo {stroke:none;opacity:.24;filter:drop-shadow(0 0 7px #b78cff);}
@keyframes orbitConductor {to{transform:rotate(360deg)}}
@keyframes brainBreath {0%,100% {opacity:.78;transform:scale(.94)}50% {opacity:1;transform:scale(1.08)}}
@media (prefers-reduced-motion:reduce) { .conductor-icon::before,.conductor-icon::after,.conductor-icon .brain-symbol {animation:none!important} .connections .signal-layer {display:none} }


/* v0.4 – compacte invoerbalk; bestaande dashboardstijl bewust ongewijzigd */
.import-panel{display:flex;align-items:center;gap:13px;flex-wrap:wrap;padding:12px 17px;margin-bottom:13px;border-color:#305278;background:linear-gradient(135deg,#10263c,#0d192f)}
.import-intro{display:flex;flex-direction:column;gap:4px;flex:1;min-width:250px}.import-intro strong{font-size:14px}.import-intro small{font-size:10px;color:#9bb0cd}
.import-file-label{padding:10px 14px;border:1px solid #3666a4;border-radius:9px;background:#162c49;color:#dcf1ff;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap}
.import-file-label span{color:#53d9bf;font-size:16px;margin-right:7px}
#scanFile{display:none}.site-label{font-size:11px;color:#a6bad8}#siteSelect{background:#132947;border:1px solid #325172;border-radius:8px;color:#eaf2ff;max-width:185px;padding:9px;font:inherit;font-size:12px}
#siteSelect:disabled{opacity:.55}.import-state{color:#78e0c3;font-size:11px;max-width:210px;line-height:1.4}
@media(max-width:760px){.import-panel{align-items:start}.import-intro{flex-basis:100%}.import-state{flex-basis:100%;max-width:none}}
</style></head>
<body>
<div class="app">
 <main class="main">
  <header><div><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="header-actions"><span class="pill"><i></i> DEMO MODE</span><span class="clock" id="clock"></span></div></header>
  <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"><button id="resetBtn" class="ghost">↺ Reset</button><button id="startBtn" class="primary">▶ Start demo-analyse</button></div></div>
   <section class="import-panel panel" aria-label="Excel-proef met fictieve gegevens">
     <div class="import-intro"><span class="section-kicker">VOLGENDE STAP · EXCEL-PROEF</span><strong>Lokale scandata-test</strong><small>Alleen fictieve of volledig geanonimiseerde testbestanden · geen upload naar een server</small></div>
     <label class="import-file-label" for="scanFile"><span>▤</span> Kies testbestand (.xlsx)</label><input type="file" id="scanFile" accept=".xlsx" />
     <label class="site-label" for="siteSelect">Locatie</label><select id="siteSelect" disabled><option value="*">Alle locaties</option></select>
     <span id="importState" class="import-state">Demo staat klaar</span>
   </section>
  <section class="orchestra panel">
   <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>
   <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>
   <div class="agents">
    <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="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>
    <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="mini-visual spark">▁ ▃ ▂ ▅ ▄ ▆ ▇</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
    <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="mini-visual rings">◎ <span>◉</span> ◎</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
    <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="mini-visual checkicons">✓ &nbsp; ✓ &nbsp; ! &nbsp; ✓</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
    <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="mini-visual reportlines">▰▰▰▰▰<br>▰▰▰▰<br>▰▰▰</div><div class="agent-bottom"><div class="track"><i></i></div><span class="percent">0%</span></div></article>
   </div>
   <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>
  </section>
  <div class="content-grid">
   <div class="left-content">
    <div class="kpi-grid" id="kpis"></div>
    <div class="charts-grid"><section class="panel chart-panel"><div class="panel-heading"><div><span class="section-kicker">TRENDANALYSE</span><h3>Bezoeken per locatie</h3></div><span class="subtle">Jul – Sep 2026</span></div><canvas id="chart" width="640" height="250" aria-label="Grafiek fictieve bezoeken per locatie"></canvas><div class="legend"><span><i style="background:#3ee1b6"></i> Soeda</span><span><i style="background:#ffc66a"></i> Rietdijk</span><span><i style="background:#5b9cff"></i> Vredehof</span><span><i style="background:#ad88ff"></i> Gouda</span></div></section>
    <section class="panel verification"><div class="panel-heading"><div><span class="section-kicker">KWALITEITSBORGING</span><h3>Verificatie Businesscontroller</h3></div><span class="warn-label">DEMO</span></div><div id="checks" class="checks"></div><div class="warning">⚠ 1 fictieve afwijking. Rapport niet gevalideerd.</div></section></div>
    <section class="panel logpanel"><div class="panel-heading"><div><span class="section-kicker">REAL-TIME PROCESS</span><h3>Agent activity log</h3></div><span class="subtle" id="logStatus">Stand-by</span></div><div class="logs" id="logs"><div class="logitem muted">◌ &nbsp; Klik op ‘Start demo-analyse’ om de workflow te bekijken.</div></div></section>
   </div>
   <aside class="right-content"><section class="panel chatpanel"><div class="panel-heading"><div><span class="section-kicker">MANAGEMENT ASSISTENT</span><h3><span class="chat-icon">✧</span> Vraag het aan Inloop AI</h3></div><span class="demo-badge">DEMO</span></div><div class="chat-messages" id="messages"><div class="message assistant"><small>INLOOP AI · DEMONSTRATIE</small><p>Welkom! Start de demo-analyse en stel daarna een vraag over de fictieve locatiecijfers.</p></div></div><div class="quick-questions"><button data-q="Welke locatie groeit het sterkst?">Grootste groei?</button><button data-q="Zijn de cijfers gevalideerd?">Validatiestatus?</button><button data-q="Maak een managementsamenvatting">Samenvatting?</button></div><form id="chatForm" class="chat-form"><input id="chatInput" autocomplete="off" placeholder="Stel een vraag over de demo..." aria-label="Vraag aan Inloop AI"><button aria-label="Verstuur vraag">➤</button></form><div class="chat-disclaimer">Vooraf ingestelde demo-antwoorden · geen echte AI</div></section>
   <section class="panel preview"><div class="panel-heading"><div><span class="section-kicker">OUTPUT</span><h3>Infographic preview</h3></div><span class="warn-label">CONCEPT</span></div><div class="paper"><div class="paper-top"><strong>REAKT<span>.</span></strong><small>INLOOPMONITOR / Q3 2026</small></div><h4>Inloop in beeld</h4><p>Illustratieve managementrapportage</p><div class="paper-kpis"><div><strong>12.480</strong><small>Bezoeken</small></div><div><strong>1.286</strong><small>Uniek</small></div><div><strong>164</strong><small>Instroom</small></div></div><div class="paper-bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div><div class="paper-watermark">FICTIEVE DATA · NIET GEVALIDEERD</div></div><button class="export" id="exportBtn">↓ Print demo-infographic</button></section></aside>
  </div>
  <footer>REAKT INLOOP INTELLIGENCE <span>ORCHESTRA PROTOTYPE · ALLEEN FICTIEVE TESTGEGEVENS · GEEN PRODUCTIEOMGEVING</span></footer>
 </main>
</div><script type="application/json" id="orchestra-demo-data">{"mode": "DEMO", "period": "Q3 2026 · fictief", "kpis": [{"label": "Bezoeken", "value": "12.480", "change": "+8,4%"}, {"label": "Unieke bezoekers", "value": "1.286", "change": "+3,2%"}, {"label": "Nieuwe instroom", "value": "164", "change": "+5,1%"}, {"label": "Verificatie", "value": "4 / 5", "change": "1 afwijking"}], "locations": [{"name": "Soeda", "visits": [350, 395, 428], "unique": 121}, {"name": "Rietdijk", "visits": [285, 305, 334], "unique": 108}, {"name": "Vredehof", "visits": [245, 268, 294], "unique": 93}, {"name": "Gouda", "visits": [320, 343, 365], "unique": 115}], "checks": [{"label": "Bezoeken", "ours": 2845, "shadow": 2845}, {"label": "Unieke bezoekers", "ours": 386, "shadow": 381}, {"label": "Nieuwe instroom", "ours": 42, "shadow": 42}, {"label": "Bezoeken vorig jaar", "ours": 2610, "shadow": 2610}, {"label": "Actieve locaties", "ours": 4, "shadow": 4}], "steps": [{"agent": "data", "text": "Demo-gegevens ingelezen en structuur gecontroleerd"}, {"agent": "analysis", "text": "Maandtrends en locatievergelijkingen berekend"}, {"agent": "insight", "text": "Opvallende ontwikkelingen samengevat (demo)"}, {"agent": "verify", "text": "Schaduwberekeningen vergeleken: 1 afwijking"}, {"agent": "report", "text": "Conceptinfographic voorbereid · NIET GEVALIDEERD"}]}</script>
<script>
window.addEventListener('error', function(event){
 var status=document.getElementById('flowState');
 if(status) status.textContent='Scriptfout – meld dit bij de ontwikkelaar';
 console.error('ORCHESTRA scriptfout:', event.message);
});
'use strict';
let demoData=null, running=false, completed=0;
const $=id=>document.getElementById(id);
const ids=['data','analysis','insight','verify','report'];
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
const escapeHTML=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
function clock(){ $('clock').textContent=new Intl.DateTimeFormat('nl-NL',{hour:'2-digit',minute:'2-digit',day:'numeric',month:'short'}).format(new Date()); }
clock();setInterval(clock,60000);
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('');}
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('');}
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=90+j*235,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=90+j*235,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,90+i*235,239));}
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>`)}
// De lichtpunten volgen de vijf bestaande SVG-verbindingen. Ze lopen alleen tijdens een actieve processtap.
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'};
const signalColors={data:'#34e6b3',analysis:'#ffcb62',insight:'#51b9ff',verify:'#b18aff',report:'#ff9a65'};
function clearSignal(){const layer=document.querySelector('.signal-layer');if(layer)layer.replaceChildren();}
function signalFor(agent){
 clearSignal();
 const layer=document.querySelector('.signal-layer');
 if(!layer||!signalPaths[agent]||window.matchMedia('(prefers-reduced-motion: reduce)').matches)return;
 const ns='http://www.w3.org/2000/svg';
 for(const [radius,cls,delay] of [[7,'signal-halo','0s'],[3.2,'signal-particle','0s']]){
  const circle=document.createElementNS(ns,'circle');circle.setAttribute('r',radius);circle.setAttribute('class',cls);circle.setAttribute('fill',signalColors[agent]);
  const motion=document.createElementNS(ns,'animateMotion');motion.setAttribute('path',signalPaths[agent]);motion.setAttribute('dur','1.35s');motion.setAttribute('repeatCount','indefinite');motion.setAttribute('begin',delay);
  circle.appendChild(motion);layer.appendChild(circle);
 }
}
function reset(){if(running)return;clearSignal();$('conductor').classList.remove('attention');completed=0;$('completed').textContent='0';$('conductorStatus').textContent='Gereed voor analyse';$('flowState').textContent='Wacht op startcommando';$('logStatus').textContent='Stand-by';$('logs').innerHTML='<div class="logitem muted">◌ &nbsp; Klik op ‘Start demo-analyse’ om de workflow te bekijken.</div>';$('orchestra-root');document.querySelector('.orchestra').classList.remove('running');$('conductor').classList.remove('running');ids.forEach(id=>{let e=$('agent-'+id);e.classList.remove('active','done');e.querySelector('.status').textContent='STANDBY';e.querySelector('.track i').style.width='0%';e.querySelector('.percent').textContent='0%'});}
async function start(){if(importedData)return startImported();if(running)return;clearSignal();$('conductor').classList.remove('attention');running=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;completed=0;ids.forEach(id=>{const e=$('agent-'+id);e.classList.remove('done','active');e.querySelector('.status').textContent='STANDBY';e.querySelector('.track i').style.width='0%';e.querySelector('.percent').textContent='0%'});$('logs').innerHTML='';$('completed').textContent='0';$('conductor').classList.add('running');document.querySelector('.orchestra').classList.add('running');$('conductorStatus').textContent='Workflow wordt aangestuurd';$('logStatus').textContent='Actief';log('Dirigent start de gesimuleerde workflow');try{const response=await Promise.resolve({ok:true,json:async()=>JSON.parse(document.getElementById('orchestra-demo-data').textContent)});if(!response.ok)throw Error('API reageert niet');demoData=await response.json();renderKPIs(demoData);renderChecks(demoData);drawChart(demoData);for(let i=0;i<demoData.steps.length;i++){const step=demoData.steps[i];const e=$('agent-'+step.agent);signalFor(step.agent);e.classList.add('active');e.querySelector('.status').textContent='ACTIEF';$('flowState').textContent=step.text;log('Start: '+e.querySelector('h3').textContent);for(let p=0;p<=100;p+=5){e.querySelector('.track i').style.width=p+'%';e.querySelector('.percent').textContent=p+'%';await sleep(36);}clearSignal();e.classList.remove('active');e.classList.add('done');if(step.agent==='verify' && demoData.checks.some(c=>c.ours!==c.shadow))$('conductor').classList.add('attention');e.querySelector('.status').textContent=step.agent==='verify'?'AFWIJKING':'GEREED';completed++;$('completed').textContent=String(completed);log(step.text);await sleep(280)}$('conductorStatus').textContent='Analyse voltooid · controle vereist';$('flowState').textContent='Concept gereed · NIET GEVALIDEERD';$('logStatus').textContent='Afgerond';log('Eindstatus: concept beschikbaar, niet vrijgegeven');}catch(e){log('FOUT: '+e.message);$('flowState').textContent='Verwerking mislukt';$('conductorStatus').textContent='Fout gedetecteerd';}finally{clearSignal();running=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;$('conductor').classList.remove('running');document.querySelector('.orchestra').classList.remove('running')}}
function reply(question){const q=question.toLowerCase();if(!completed)return 'Start eerst de demo-analyse. Daarna kan ik vragen beantwoorden over de fictieve cijfers.';if(/valid|wouter|controle|betrouw|verschil|akkoord/.test(q))return 'In deze fictieve demo wijken de unieke bezoekers af: monitor 386 tegenover schaduwberekening 381, een verschil van 5. De oorzaak is niet vastgesteld. Daarom blijft de infographic NIET GEVALIDEERD.';if(/groei|stijg|locatie|soeda|rietdijk|vredehof|gouda/.test(q))return 'In de fictieve reeks juli–september stijgt Soeda van 350 naar 428 bezoeken (+22,3%). Rietdijk stijgt van 285 naar 334 (+17,2%), Vredehof van 245 naar 294 (+20,0%) en Gouda van 320 naar 365 (+14,1%). Soeda heeft de grootste relatieve groei.';if(/samenvat|rapport|infographic|management/.test(q))return 'Conceptmanagementsamenvatting (fictief): alle vier getoonde locaties laten in juli–september groei in bezoeken zien. Er is een afwijking van 5 unieke bezoekers bij de schaduwcontrole. Publicatie is niet toegestaan totdat dit verschil is onderzocht en beoordeeld.';if(/bezoek|uniek|instroom|kwartaal|cijfer/.test(q))return 'De fictieve KPI-kaarten tonen 12.480 bezoeken, 1.286 unieke bezoekers en 164 nieuwe instromers. Deze totaalcijfers zijn illustratief en vormen geen optelsom van de vier voorbeeldlocaties in de grafiek.';return 'Dit is een demo-chat zonder AI-API. Ik kan alleen vooraf gedefinieerde vragen over de fictieve locatiegroei, KPI’s, verificatie en managementsamenvatting beantwoorden.'}
function addMessage(role,text){const e=document.createElement('div');e.className='message '+role;if(role==='assistant'){const s=document.createElement('small');s.textContent='INLOOP AI · DEMONSTRATIE';e.appendChild(s)}const p=document.createElement('p');p.textContent=text;e.appendChild(p);$('messages').appendChild(e);$('messages').scrollTop=$('messages').scrollHeight;}
function ask(q){if(!q.trim())return;addMessage('user',q);addMessage('assistant',reply(q));}
// v0.4: veilige browser-lokale Excel-proef, uitsluitend met fictieve / geanonimiseerde voorbeelden.
// Er vindt GEEN netwerkverzoek, bestandsoverdracht of externe AI-aanroep plaats.
let importedData=null;
const excelLimit=2*1024*1024; // om echte grote (en mogelijk cliëntgevoelige) dumps nog niet te verwerken
const excelNS='http://schemas.openxmlformats.org/spreadsheetml/2006/main';
const decoder=new TextDecoder('utf-8');
function bytesText(a){return decoder.decode(a)}
function zipDirectory(buffer){
 const bytes=new Uint8Array(buffer), view=new DataView(buffer);
 let eocd=-1;
 for(let i=bytes.length-22;i>=Math.max(0,bytes.length-65557);i--){if(view.getUint32(i,true)===0x06054b50){eocd=i;break}}
 if(eocd<0)throw Error('Geen geldig XLSX-/ZIP-bestand');
 const count=view.getUint16(eocd+10,true), offset=view.getUint32(eocd+16,true);
 if(count>1000)throw Error('Dit testbestand heeft te veel onderdelen');
 const entries=new Map();let pos=offset;
 for(let i=0;i<count;i++){
  if(view.getUint32(pos,true)!==0x02014b50)throw Error('ZIP-structuur ongeldig');
  const method=view.getUint16(pos+10,true), size=view.getUint32(pos+20,true),len=view.getUint16(pos+28,true),extra=view.getUint16(pos+30,true),comment=view.getUint16(pos+32,true),local=view.getUint32(pos+42,true);
  const name=bytesText(bytes.subarray(pos+46,pos+46+len));
  if(name.startsWith('xl/'))entries.set(name,{method,size,local});
  pos+=46+len+extra+comment;
 }
 return {view,bytes,entries};
}
async function xmlEntry(zip,name){
 const entry=zip.entries.get(name);if(!entry)return '';
 const {view,bytes}=zip, off=entry.local;
 if(view.getUint32(off,true)!==0x04034b50)throw Error('ZIP-bestandskop ongeldig');
 const start=off+30+view.getUint16(off+26,true)+view.getUint16(off+28,true);
 const compressed=bytes.slice(start,start+entry.size);
 if(entry.method===0)return bytesText(compressed);
 if(entry.method!==8)throw Error('Niet-ondersteunde Excel-compressie');
 if(typeof DecompressionStream!=='function')throw Error('Deze browser ondersteunt lokale Excel-decompressie niet');
 return await new Response(new Blob([compressed]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).text();
}
function xmlDoc(str){
 const doc=new DOMParser().parseFromString(str,'application/xml');
 if(doc.getElementsByTagName('parsererror').length)throw Error('Excel-XML niet leesbaar');return doc;
}
function cellsToDict(row,shared){
 const values={};
 for(const c of row.getElementsByTagNameNS(excelNS,'c')){
  const key=(c.getAttribute('r')||'').match(/^[A-Z]+/);if(!key)continue;
  const v=c.getElementsByTagNameNS(excelNS,'v')[0],is=c.getElementsByTagNameNS(excelNS,'is')[0];let value=v?.textContent??'';
  if(c.getAttribute('t')==='s')value=shared[Number(value)]??'';
  else if(c.getAttribute('t')==='inlineStr')value=is?.textContent??'';
  values[key[0]]=value;
 }return values;
}
function scanDate(v){
 if(v===''||v===undefined||v===null)return '';
 const numeric=Number(v);
 if(v!==''&&Number.isFinite(numeric)&&numeric>30000&&numeric<80000){
  const d=new Date(Date.UTC(1899,11,30)+Math.floor(numeric)*86400000);
  return d.toISOString().slice(0,10);
 }
 const str=String(v).trim(),iso=str.match(/^(\d{4})-(\d{2})-(\d{2})/);
 if(iso)return `${iso[1]}-${iso[2]}-${iso[3]}`;
 const nl=str.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{4})/);
 if(nl)return `${nl[3]}-${nl[2].padStart(2,'0')}-${nl[1].padStart(2,'0')}`;
 return '';
}
async function readExcelLocal(file){
 if(!file.name.toLowerCase().endsWith('.xlsx'))throw Error('Kies een XLSX-testbestand');
 if(file.size>excelLimit)throw Error('Dit is een grote productiedump. Gebruik voorlopig uitsluitend het bijgeleverde kleine fictieve testbestand.');
 const buf=await file.arrayBuffer(),zip=zipDirectory(buf);
 const shared=[];
 const sharedXML=await xmlEntry(zip,'xl/sharedStrings.xml');
 if(sharedXML){
  const doc=xmlDoc(sharedXML);
  for(const si of doc.getElementsByTagNameNS(excelNS,'si'))shared.push(si.textContent??'');
 }
 const sheetXML=await xmlEntry(zip,'xl/worksheets/sheet1.xml');
 if(!sheetXML)throw Error('Werkblad Data dump niet gevonden');
 const doc=xmlDoc(sheetXML),rows=doc.getElementsByTagNameNS(excelNS,'row');
 if(rows.length<2)throw Error('Geen scanregels gevonden');
 if(rows.length>18000)throw Error('Voor deze eerste proef maximaal 18.000 testregels');
 const header=cellsToDict(rows[0],shared),lookup={};
 for(const [col,headerText] of Object.entries(header))lookup[String(headerText).toLowerCase().trim()]=col;
 for(const mandatory of ['scantijd','locatie','clientnummer'])if(!lookup[mandatory])throw Error('Verplichte kolom ontbreekt: '+mandatory);
 const all=[],invalid={datum:0,locatie:0,client:0};
 for(let i=1;i<rows.length;i++){
  const c=cellsToDict(rows[i],shared),site=String(c[lookup.locatie]??'').trim(),id=String(c[lookup.clientnummer]??'').trim(),date=scanDate(c[lookup.scantijd]);
  if(!date)invalid.datum++;
  if(!site)invalid.locatie++;
  if(!id)invalid.client++;
  if(!site||!id||!date)continue;
  all.push({date,site,id});
 }
 if(!all.length)throw Error('Geen bruikbare scanregels');
 const months=[...new Set(all.map(r=>r.date.slice(0,7)))].sort();
 const sites=[...new Set(all.map(r=>r.site))].sort((a,b)=>a.localeCompare(b,'nl'));
 return {all,sites,months,rawCount:rows.length-1,invalid};
}
function calculateImported(site='*'){
 const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),monthSite=new Map();
 for(const r of all){
  const k=r.site+'|'+r.date+'|'+r.id;
  if(keyset.has(k))continue;
  keyset.add(k);ids.add(r.id);
  const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
 }
 const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
 const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
 return {site,rows:all.length,visits:keyset.size,unique:ids.size,duplicates:all.length-keyset.size,months:chosen,locations,invalid:importedData.invalid};
}
function drawImportedChart(result){
 const cvs=$('chart'),ctx=cvs.getContext('2d'),w=cvs.width,h=cvs.height;
 ctx.clearRect(0,0,w,h);ctx.font='13px Segoe UI';
 const series=result.locations.slice(0,4),max=Math.max(10,...series.flatMap(x=>x.visits))*1.15;
 ctx.strokeStyle='#29405a';ctx.fillStyle='#8aa4c3';ctx.textAlign='left';
 for(let i=0;i<=4;i++){const y=25+i*45;ctx.beginPath();ctx.moveTo(44,y);ctx.lineTo(w-14,y);ctx.stroke();ctx.fillText(String(Math.round(max*(4-i)/4)),7,y+4)}
 const colors=['#3ee1b6','#ffc66a','#5b9cff','#ad88ff'];
 series.forEach((loc,li)=>{
  ctx.strokeStyle=colors[li];ctx.lineWidth=3;ctx.beginPath();
  loc.visits.forEach((v,j)=>{const x=90+j*235,y=205-(v/max)*180;j===0?ctx.moveTo(x,y):ctx.lineTo(x,y)});ctx.stroke();
  loc.visits.forEach((v,j)=>{const x=90+j*235,y=205-(v/max)*180;ctx.fillStyle=colors[li];ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()});
 });ctx.fillStyle='#a6b9d0';ctx.textAlign='center';
 result.months.forEach((m,i)=>ctx.fillText(m,90+i*235,239));
 const legend=document.querySelector('.legend');
 legend.innerHTML=series.map((loc,i)=>`<span><i style="background:${colors[i]}"></i> ${escapeHTML(loc.name)}</span>`).join('');
 document.querySelector('.chart-panel .subtle').textContent=result.months.join(' · ')+(result.locations.length>4?' · 4 van '+result.locations.length+' locaties':'');
}
function renderImported(result){
 const num=n=>n.toLocaleString('nl-NL');
 const kpis=[{label:'Bezoeken (voorlopig)',value:num(result.visits),change:'Unieke cliënt / dag / locatie'},{label:'Unieke cliëntnummers',value:num(result.unique),change:'Binnen geselecteerde periode'},{label:'Scanregels',value:num(result.rows),change:`${num(result.duplicates)} herhaalde scans`},{label:'Verificatie',value:'—',change:'Extern niet uitgevoerd'}];
 renderKPIs({kpis});drawImportedChart(result);
 $('checks').innerHTML='<div class="check-row"><span>Dubbele scans (voorlopige regel)</span><span>'+num(result.duplicates)+'</span></div><div class="check-row"><span>Unieke cliëntnummers</span><span>'+num(result.unique)+'</span></div><div class="check-row"><span>Schaduwberekening Businesscontroller</span><span>NIET AANGELEVERD</span></div>';
 document.querySelector('.verification .warning').textContent='⚠ Proefberekening. Definities en externe controle nog niet gevalideerd.';
 document.querySelector('.paper-watermark').textContent='FICTIEVE TESTGEGEVENS · NIET GEVALIDEERD';
 document.querySelector('.paper p').textContent='Lokale Excel-proef · niet voor publicatie';
 const pk=document.querySelectorAll('.paper-kpis strong');
 [num(result.visits),num(result.unique),'—'].forEach((x,i)=>{if(pk[i])pk[i].textContent=x});
 const pl=document.querySelectorAll('.paper-kpis small');if(pl[2])pl[2].textContent='Instroom niet bepaald';
}
async function startImported(){
 if(running)return;
 running=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;completed=0;clearSignal();
 $('logs').innerHTML='';$('completed').textContent='0';$('conductor').classList.remove('attention');
 $('conductor').classList.add('running');document.querySelector('.orchestra').classList.add('running');
 $('conductorStatus').textContent='Lokale Excel-proef actief';$('logStatus').textContent='Actief';
 for(const id of ids){let el=$('agent-'+id);el.classList.remove('active','done');el.querySelector('.status').textContent='STANDBY';el.querySelector('.track i').style.width='0%';el.querySelector('.percent').textContent='0%'}
 const result=calculateImported($('siteSelect').value);importedData.last=result;
 const steps=[
  {agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans aangetroffen`},
  {agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke cliëntnummers berekend`},
  {agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
  {agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
  {agent:'report',text:'Conceptpreview bijgewerkt · NIET GEVALIDEERD'}];
 try{
  renderImported(result);
  for(const step of steps){const el=$('agent-'+step.agent);signalFor(step.agent);el.classList.add('active');el.querySelector('.status').textContent='ACTIEF';$('flowState').textContent=step.text;log('Start: '+el.querySelector('h3').textContent);
   for(let p=0;p<=100;p+=5){el.querySelector('.track i').style.width=p+'%';el.querySelector('.percent').textContent=p+'%';await sleep(30)}
   clearSignal();el.classList.remove('active');el.classList.add('done');el.querySelector('.status').textContent=step.agent==='verify'?'NIET EXTERN':'GEREED';completed++;$('completed').textContent=String(completed);log(step.text);await sleep(200)
  }
  $('conductor').classList.add('attention');$('flowState').textContent='Concept · NIET GEVALIDEERD';$('conductorStatus').textContent='Proefberekening voltooid';$('logStatus').textContent='Afgerond';
 }catch(err){log('FOUT: '+err.message);$('flowState').textContent='Proefberekening mislukt';}finally{clearSignal();running=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;$('conductor').classList.remove('running');document.querySelector('.orchestra').classList.remove('running')}
}
$('scanFile').addEventListener('change',async e=>{
 importedData=null;const file=e.target.files?.[0];if(!file)return;
 const state=$('importState');state.textContent='Bestand wordt lokaal gelezen...';$('startBtn').disabled=true;
 try{
  importedData=await readExcelLocal(file);
  const sel=$('siteSelect');sel.innerHTML='<option value="*">Alle locaties</option>';
  importedData.sites.forEach(loc=>{const opt=document.createElement('option');opt.value=loc;opt.textContent=loc;sel.appendChild(opt)});
  sel.disabled=false;
  state.textContent=`${importedData.rawCount} testregels · ${importedData.sites.length} locaties`;
  $('startBtn').textContent='▶ Analyseer testbestand';
  $('flowState').textContent='Testbestand lokaal ingelezen · start de analyse';
  log('Fictief Excel-testbestand is lokaal ingelezen; nog geen berekeningen gepubliceerd');
 }catch(err){$('startBtn').textContent='▶ Start demo-analyse';state.textContent='⚠ '+err.message;$('siteSelect').disabled=true;log('Excel-proef: '+err.message)}finally{$('startBtn').disabled=false}
});
// Bestaande chat blijft demo-gericht. Voor echte Excel-proef antwoorden we uitsluitend met controleerbare berekende cijfers.
const originalReply=reply;
reply=function(q){
 if(!importedData)return originalReply(q);
 const r=importedData.last;
 if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
 if(/valid|controle|wouter|business|betrouw|verschil|akkoord/.test(q.toLowerCase()))return 'Deze Excel-proef is NIET extern gevalideerd. De schaduwberekening van de businesscontroller ontbreekt en de rekenregels moeten nog worden afgestemd.';
 if(/bezoek|uniek|scan|groei|locatie|samenvat|rapport|analyse|kwartaal|cijfer/.test(q.toLowerCase()))return `Testbestand (${r.site==='*'?'alle locaties':r.site}): ${r.rows} scanregels, ${r.visits} voorlopige bezoeken na ${r.duplicates} herhaalde scans, en ${r.unique} unieke cliëntnummers. Dit is een lokale proef zonder AI-API en zonder externe verificatie.`;
 return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
};

$('startBtn').addEventListener('click',start);$('resetBtn').addEventListener('click',reset);$('chatForm').addEventListener('submit',e=>{e.preventDefault();const q=$('chatInput').value;$('chatInput').value='';ask(q)});document.querySelectorAll('[data-q]').forEach(b=>b.addEventListener('click',()=>ask(b.dataset.q)));$('exportBtn').addEventListener('click',()=>window.print());
Promise.resolve({ok:true,json:async()=>JSON.parse(document.getElementById('orchestra-demo-data').textContent)}).then(r=>r.json()).then(d=>{demoData=d;renderKPIs(d);renderChecks(d);drawChart(d)}).catch(()=>log('Demo-gegevens konden niet worden geladen'));

</script></body></html>