ChunkWorks
Your data. Your region. Always available.
Caribbean Network — Live
3
Auto Demo starts in...
Step 1 of 4
📄
Store your data across the Caribbean
Your data is encrypted and spread across 5 locations in your region. No single location holds the whole file — not even us.
🗄️
high_value_data
Ready to store · 2.4 MB
READY
5
Locations storing your data
2
Can fail safely
0 sec
Downtime
What you see on the map
Your business — Willemstad
Dutch Caribbean datacenter 🇳🇱
Caribbean datacenter

Storing your data — active
Location offline
Under ransomware attack
Compromised — quarantined
Restored — back to full protection

Your data never leaves the Caribbean.
Dutch law applies at every NL node.
🤖
ChunkWorks AI Guide
● Gereed
Welkom bij de ChunkWorks demonstratie. Selecteer een scenario en klik op Auto Demo voor een volledige automatische rondleiding — of stap voor stap handmatig.
══════════════════════════════════════ */ var AI_SCRIPTS = { en: { outage: { 0: "Welcome to the ChunkWorks demonstration. Your data is about to be encrypted and distributed across five separate Caribbean locations. No single location holds the complete file — not even us.", 1: "ChunkWorks is splitting your file into five encrypted fragments, each going to a different location. The pieces only mean something together. Only you hold the key.", 2: "A datacenter just went completely offline. Power failure, hurricane, hardware crash — it doesn't matter. But watch: your data is still one hundred percent accessible. Four locations still hold their fragments. You only need three.", 3: "Watch self-healing in action. The remaining four locations detected the failure and are redistributing the missing fragment between themselves. No IT call. No manual action. The system is fixing itself.", 4: "Your data has been retrieved — completely intact and verified. Zero seconds of downtime. Zero bytes lost. Even while an entire datacenter went dark. This is what makes ChunkWorks unique." }, ransomware: { 0: "Now we simulate a ransomware attack — a completely different threat. Watch why this attack is completely futile against the ChunkWorks architecture.", 1: "Your data is distributed across five locations in five encrypted fragments. Without all five fragments and your private key, each piece is worthless random noise.", 2: "An attacker has compromised one location and encrypted everything on that server. But all they captured is one of five encrypted fragments. Without the other four, it is pure noise. The ransom demand is already worthless.", 3: "ChunkWorks detected the breach. The compromised fragment is being cryptographically revoked via a delete token. The attacker now has absolutely nothing. The four clean locations are rebuilding the missing fragment internally.", 4: "Data retrieved — completely clean and intact. The attacker got absolutely nothing. Zero ransom ever needs to be paid. This is the power of distributed encryption." } }, nl: { outage: { 0: "Welkom bij de ChunkWorks-demonstratie. Uw data wordt versleuteld en verspreid over vijf afzonderlijke Caribische locaties. Geen enkele locatie heeft het volledige bestand — zelfs wij niet.", 1: "ChunkWorks splitst uw bestand in vijf versleutelde fragmenten, elk naar een andere locatie. De stukken hebben alleen samen betekenis. Alleen u hebt de sleutel.", 2: "Een datacenter is volledig offline gegaan. Stroomstoring, orkaan, hardwarecrash — het maakt niet uit. Maar kijk: uw data is nog steeds honderd procent toegankelijk. Vier locaties houden hun fragmenten nog vast. U hebt er maar drie nodig.", 3: "Zelfherstel in actie. De vier resterende locaties hebben de storing gedetecteerd en herverdelen het ontbrekende fragment onderling. Geen IT-oproep. Geen handmatige actie. Het systeem lost het zelf op.", 4: "Uw data is opgehaald — volledig intact en geverifieerd. Nul seconden downtime. Nul bytes verloren. Zelfs terwijl een volledig datacenter uitviel. Dit is wat ChunkWorks uniek maakt." }, ransomware: { 0: "Nu simuleren we een ransomware-aanval — een compleet ander dreigingsscenario. Bekijk waarom deze aanval volledig zinloos is tegen de ChunkWorks-architectuur.", 1: "Uw data is verspreid over vijf locaties in vijf versleutelde fragmenten. Zonder alle vijf fragmenten en uw privésleutel is elk stuk waardeloze willekeurige ruis.", 2: "Een aanvaller heeft één locatie gecompromitteerd en alles op die server versleuteld. Maar alles wat ze hebben is één van vijf versleutelde fragmenten. Zonder de andere vier is het pure ruis. De losgeld-eis is al bij voorbaat waardeloos.", 3: "ChunkWorks heeft de aanval gedetecteerd. Het gecompromitteerde fragment wordt cryptografisch ingetrokken via een delete-token. De aanvaller heeft nu absoluut niets. De vier schone locaties herbouwen het ontbrekende fragment intern.", 4: "Data opgehaald — volledig schoon en intact. De aanvaller heeft absoluut niets gekregen. Er hoeft nooit losgeld betaald te worden. Dit is de kracht van gedistribueerde versleuteling." } }, fr: { outage: { 0: "Bienvenue dans la démonstration ChunkWorks. Vos données vont être chiffrées et distribuées sur cinq emplacements caribéens séparés. Aucun emplacement ne détient le fichier complet — pas même nous.", 1: "ChunkWorks divise votre fichier en cinq fragments chiffrés, chacun allant vers un emplacement différent. Les fragments n'ont de sens qu'ensemble. Seul vous détenez la clé.", 2: "Un datacenter vient de tomber complètement hors ligne. Coupure de courant, ouragan, panne matérielle — peu importe. Mais regardez : vos données sont toujours accessibles à cent pour cent. Quatre emplacements détiennent encore leurs fragments. Vous n'en avez besoin que de trois.", 3: "L'auto-réparation en action. Les quatre emplacements restants ont détecté la panne et redistribuent le fragment manquant entre eux. Aucun appel IT. Aucune action manuelle. Le système se répare seul.", 4: "Vos données ont été récupérées — complètement intactes et vérifiées. Zéro seconde d'arrêt. Zéro octet perdu. Même pendant qu'un datacenter entier tombait en panne. C'est ce qui rend ChunkWorks unique." }, ransomware: { 0: "Nous simulons maintenant une attaque ransomware — un scénario de menace complètement différent. Regardez pourquoi cette attaque est totalement inutile contre l'architecture ChunkWorks.", 1: "Vos données sont distribuées sur cinq emplacements en cinq fragments chiffrés. Sans les cinq fragments et votre clé privée, chaque morceau est du bruit aléatoire sans valeur.", 2: "Un attaquant a compromis un emplacement et chiffré tout sur ce serveur. Mais tout ce qu'il a capturé est un fragment chiffré sur cinq. Sans les quatre autres, c'est du bruit pur. La demande de rançon est déjà sans objet.", 3: "ChunkWorks a détecté la brèche. Le fragment compromis est révoqué cryptographiquement via un jeton de suppression. L'attaquant n'a maintenant absolument rien. Les quatre emplacements sains reconstruisent le fragment manquant.", 4: "Données récupérées — complètement propres et intactes. L'attaquant n'a absolument rien obtenu. Aucune rançon ne doit jamais être payée. C'est la puissance du chiffrement distribué." } } }; // ── STATE ── var aiSpeakEnabled = true; var autoRunning = false; var autoMode = false; // true = auto-modus actief, suppress handmatige AI triggers var autoTimer = null; function getAIScript(sc, st) { var lang = simLang in AI_SCRIPTS ? simLang : 'en'; return (AI_SCRIPTS[lang][sc] || AI_SCRIPTS['en'][sc])[st] || ''; } // ── AI BUBBLE ── function setAIMessage(text, stepNum, totalSteps) { var bubble = document.getElementById('ai-bubble'); var prog = document.getElementById('ai-progress'); var cnt = document.getElementById('ai-step-count'); bubble.classList.add('fading'); setTimeout(function() { bubble.innerHTML = '
'; bubble.classList.remove('fading'); setTimeout(function() { bubble.classList.add('fading'); setTimeout(function() { bubble.innerHTML = text; bubble.classList.remove('fading'); if (totalSteps > 0) { prog.style.width = Math.round((stepNum / totalSteps) * 100) + '%'; cnt.textContent = simLang === 'nl' ? ('Stap ' + stepNum + ' van ' + totalSteps) : simLang === 'fr' ? ('Étape ' + stepNum + ' sur ' + totalSteps) : ('Step ' + stepNum + ' of ' + totalSteps); } else { prog.style.width = '0%'; cnt.textContent = ''; } if (aiSpeakEnabled) speakText(text); }, 300); }, 900); }, 300); } // ── SPRAAK — met onend callback ── function speakText(text) { if (!window.speechSynthesis) return; window.speechSynthesis.cancel(); var utt = new SpeechSynthesisUtterance(text.replace(/<[^>]+>/g, '')); utt.lang = simLang === 'nl' ? 'nl-NL' : simLang === 'fr' ? 'fr-FR' : 'en-US'; utt.rate = 0.88; utt.pitch = 1.0; var btn = document.getElementById('ai-speak-btn'); utt.onstart = function() { btn.classList.add('speaking'); }; utt.onend = function() { btn.classList.remove('speaking'); }; utt.onerror = function() { btn.classList.remove('speaking'); }; window.speechSynthesis.speak(utt); } // speakText met callback zodra spraak klaar is function speakAndThen(text, cb) { if (!aiSpeakEnabled || !window.speechSynthesis) { setTimeout(cb, 2500); // geen spraak: vaste pauze return; } window.speechSynthesis.cancel(); var utt = new SpeechSynthesisUtterance(text.replace(/<[^>]+>/g, '')); utt.lang = simLang === 'nl' ? 'nl-NL' : simLang === 'fr' ? 'fr-FR' : 'en-US'; utt.rate = 0.88; utt.pitch = 1.0; var btn = document.getElementById('ai-speak-btn'); utt.onstart = function() { btn.classList.add('speaking'); }; utt.onend = function() { btn.classList.remove('speaking'); setTimeout(cb, 1200); }; utt.onerror = function() { btn.classList.remove('speaking'); setTimeout(cb, 800); }; window.speechSynthesis.speak(utt); // Chrome bug: speechSynthesis stopt soms na 15s — keep-alive var keepAlive = setInterval(function() { if (!window.speechSynthesis.speaking) { clearInterval(keepAlive); return; } window.speechSynthesis.pause(); window.speechSynthesis.resume(); }, 10000); utt.onend = function() { clearInterval(keepAlive); btn.classList.remove('speaking'); setTimeout(cb, 1200); }; } // ── SPEAKER TOGGLE ── document.getElementById('ai-speak-btn').addEventListener('click', function() { aiSpeakEnabled = !aiSpeakEnabled; if (!aiSpeakEnabled && window.speechSynthesis) window.speechSynthesis.cancel(); this.innerHTML = aiSpeakEnabled ? '' : ''; }); // ── HOOKS — alleen actief in handmatige modus ── var _origDispatch = dispatch; dispatch = function() { _origDispatch(); if (!autoMode) { var s = step, sc = scenario; setTimeout(function() { setAIMessage(getAIScript(sc, s), s, 4); }, 600); } }; var _origReset = doReset; doReset = function() { _origReset(); if (!autoMode) { stopAuto(); setTimeout(function() { setAIMessage(getAIScript(scenario, 0), 0, 0); }, 400); } }; // ── AUTO-PLAY CORE ── // switchScenario roept intern doReset aan — in auto-modus via _origReset function autoSwitchScenario(sc) { scenario = sc; document.getElementById('sc-outage').className = 'scenario-btn' + (sc === 'outage' ? ' active-outage' : ''); document.getElementById('sc-ransomware').className = 'scenario-btn' + (sc === 'ransomware' ? ' active-ransomware' : ''); _origReset(); // gewrapte doReset vermijden — die roept stopAuto aan } function stopAuto() { autoRunning = false; autoMode = false; clearTimeout(autoTimer); if (window.speechSynthesis) window.speechSynthesis.cancel(); var btn = document.getElementById('btn-auto'); btn.classList.remove('running'); btn.querySelector('#btn-auto-label').textContent = simLang === 'nl' ? 'Auto Demo' : simLang === 'fr' ? 'Démo Auto' : 'Auto Demo'; } function startAuto() { if (autoRunning) { stopAuto(); return; } autoMode = true; autoRunning = true; var btn = document.getElementById('btn-auto'); btn.classList.add('running'); btn.querySelector('#btn-auto-label').textContent = simLang === 'nl' ? 'Stop Auto' : simLang === 'fr' ? 'Arrêter' : 'Stop Auto'; // Reset kaart zonder stopAuto te raken autoSwitchScenario('outage'); // Stap 0 — welkomstbericht spreken, dan starten var intro = getAIScript('outage', 0); setAIMessage(intro, 0, 0); // Wacht op typing-animatie (1500ms) + spraak via speakAndThen autoTimer = setTimeout(function() { speakAndThen(intro, function() { runAutoStep(); }); }, 1500); } function runAutoStep() { if (!autoRunning) return; // Wacht als kaartanimatie nog loopt if (isBusy) { autoTimer = setTimeout(runAutoStep, 400); return; } if (step < 4) { // Volgende stap uitvoeren step++; _origDispatch(); // kaart animeren — GEEN wrapped dispatch (geen dubbele AI) var sc = scenario, s = step; var msg = getAIScript(sc, s); // Wacht 1s op kaartanimatie, dan AI bericht tonen + spreken, dan volgende stap autoTimer = setTimeout(function() { setAIMessage(msg, s, 4); autoTimer = setTimeout(function() { // Wacht op typing-animatie afgerond (1500ms) dan spreek met callback speakAndThen(msg, function() { runAutoStep(); }); }, 1500); }, 1000); } else if (scenario === 'outage') { // Outage klaar → wissel naar ransomware var bridgeMsg = simLang === 'nl' ? "Uitstekend. Datacenter uitval volledig afgehandeld — nul downtime, nul actie van u. Nu simuleren we een ransomware-aanval." : simLang === 'fr' ? "Excellent. Panne datacenter gérée — zéro temps d'arrêt, aucune action de votre part. Simulons maintenant une attaque ransomware." : "Excellent. Datacenter outage handled — zero downtime, zero action from you. Now simulating a ransomware attack."; setAIMessage(bridgeMsg, 4, 4); autoTimer = setTimeout(function() { speakAndThen(bridgeMsg, function() { autoSwitchScenario('ransomware'); var intro = getAIScript('ransomware', 0); setAIMessage(intro, 0, 0); autoTimer = setTimeout(function() { speakAndThen(intro, function() { runAutoStep(); }); }, 1500); }); }, 1500); } else { // Ransomware klaar → loop opnieuw var doneMsg = simLang === 'nl' ? "Demonstratie voltooid. Twee scenario's afgehandeld. Nul downtime. Nul dataverlies. De demonstratie begint opnieuw." : simLang === 'fr' ? "Démonstration terminée. Deux scénarios gérés. Zéro temps d'arrêt. Zéro perte. La démonstration redémarre." : "Demonstration complete. Two scenarios handled. Zero downtime. Zero data loss. Restarting the demonstration."; setAIMessage(doneMsg, 4, 4); autoTimer = setTimeout(function() { speakAndThen(doneMsg, function() { // Loop — herstart volledig autoMode = true; // zeker stellen na speakAndThen autoSwitchScenario('outage'); var intro = getAIScript('outage', 0); setAIMessage(intro, 0, 0); autoTimer = setTimeout(function() { speakAndThen(intro, function() { runAutoStep(); }); }, 1500); }); }, 1500); } } // ── AUTO KNOP ── document.getElementById('btn-auto').addEventListener('click', function() { startAuto(); }); // ── VOLLEDIG AUTO via postMessage of ?auto=true ── var autoParams = new URLSearchParams(window.location.search); if (autoParams.get('auto') === 'true') { setTimeout(triggerAutoPlay, 1200); } function triggerAutoPlay() { if (autoRunning) return; showCountdown(3, function() { startAuto(); }); } function showCountdown(seconds, cb) { var overlay = document.getElementById('auto-countdown'); var numEl = document.getElementById('cd-num'); var labelEl = document.getElementById('cd-label'); var skipBtn = document.getElementById('cd-skip'); labelEl.textContent = simLang === 'nl' ? 'Auto Demo start over...' : simLang === 'fr' ? 'La démo démarre dans...' : 'Auto Demo starts in...'; skipBtn.textContent = simLang === 'nl' ? 'Direct starten' : simLang === 'fr' ? 'Démarrer maintenant' : 'Start now'; overlay.classList.add('visible'); numEl.textContent = seconds; var remaining = seconds; var cdInterval = setInterval(function() { remaining--; if (remaining <= 0) { clearInterval(cdInterval); overlay.classList.remove('visible'); cb(); } else { numEl.textContent = remaining; } }, 1000); skipBtn.onclick = function() { clearInterval(cdInterval); overlay.classList.remove('visible'); cb(); }; } // ── postMessage handler ── window.addEventListener('message', function(e) { if (e.data && e.data.lang && I[e.data.lang]) { simLang = e.data.lang; applyStaticUI(); updateCalloutLegend(); if (!autoMode) { if (step === 0) _origReset(); setTimeout(function() { setAIMessage(getAIScript(scenario, 0), 0, 0); }, 300); } } if (e.data && e.data.resize) { map.invalidateSize(); } if (e.data && e.data.autoPlay) { triggerAutoPlay(); } }); // ── Initieel welkomstbericht (handmatige modus) ── setTimeout(function() { setAIMessage(getAIScript('outage', 0), 0, 0); }, 900); })(); outage: { 0: "Welkom. U ziet hier de ChunkWorks-kaart van het Caribisch netwerk. Uw data gaat straks verspreid worden over vijf geografisch gescheiden locaties. Klik op de knop om te starten.", 1: "ChunkWorks verdeelt uw bestand nu in vijf versleutelde fragmenten. Elk fragment gaat naar een andere locatie. Geen enkele locatie — ook wij niet — heeft het volledige bestand. Alleen u hebt de sleutel.", 2: "Een datacenter is zojuist uitgevallen. Totale stroomstoring. Maar kijk: uw data is nog steeds honderd procent toegankelijk. U heeft immers vier andere locaties die hun fragment nog steeds vasthouden. U heeft er maar drie nodig.", 3: "Nu ziet u zelfherstel in actie. De vier resterende locaties detecteerden de storing en herverdeelden het ontbrekende fragment onderling. Geen IT-call. Geen handmatige actie. Het systeem regelde het zelf.", 4: "Uw data is teruggehaald — volledig intact en geverifieerd. Nul seconden downtime. Nul bytes verloren. Zelfs terwijl een volledig datacenter uitviel. Dit is wat ChunkWorks uniek maakt." }, ransomware: { 0: "Welkom. U ziet het ChunkWorks-netwerk over het Caribisch gebied. Straks simuleren we een ransomware-aanval — en u ziet waarom die aanval volstrekt zinloos is tegen onze architectuur.", 1: "Uw data is nu verdeeld over vijf locaties in vijf versleutelde fragmenten. De fragmenten hebben alleen samen betekenis. Zonder alle vijf — en uw privésleutel — is elk fragment waardeloos.", 2: "Een aanvaller heeft één locatie gehackt en probeert te versleutelen. Wat heeft hij buitgemaakt? Slechts één van vijf versleutelde fragmenten. Zonder de andere vier is het pure ruis. De losgeld-eis is al bij voorbaat waardeloos.", 3: "ChunkWorks heeft de aanval gedetecteerd. Het gecompromitteerde fragment wordt nu cryptografisch ingetrokken via een delete-token. De aanvaller heeft letterlijk niets meer. De vier schone locaties herbouwen het fragment intern.", 4: "Data opgehaald — volledig schoon en intact. De aanvaller heeft absoluut niets gekregen. Nul losgeld hoeft ooit betaald te worden. Dit is de kracht van gedistribueerde versleuteling." } }, en: { outage: { 0: "Welcome. You're looking at the ChunkWorks Caribbean network map. Your data is about to be distributed across five geographically separate locations. Click the button to get started.", 1: "ChunkWorks is now splitting your file into five encrypted fragments — each going to a different location. No single location, not even us, holds the complete file. Only you hold the key.", 2: "A datacenter just went completely offline. But look — your data is still one hundred percent accessible. You have four other locations still holding their fragments. You only need any three to access everything.", 3: "Watch self-healing in action. The four remaining locations detected the failure and redistributed the missing fragment amongst themselves. No IT call. No manual action. The system fixed itself.", 4: "Your data has been retrieved — completely intact and verified. Zero seconds of downtime. Zero bytes lost. Even while an entire datacenter went dark. This is what makes ChunkWorks unique." }, ransomware: { 0: "Welcome. You're looking at the ChunkWorks network across the Caribbean. We're about to simulate a ransomware attack — and you'll see exactly why that attack is completely futile against our architecture.", 1: "Your data is now distributed across five locations in five encrypted fragments. The fragments only mean something together. Without all five — and your private key — each fragment is worthless noise.", 2: "An attacker has compromised one location and is encrypting everything. What did they get? One of five encrypted fragments. Without the other four, it's pure random noise. The ransom demand is already pointless.", 3: "ChunkWorks detected the breach. The compromised fragment is now being cryptographically revoked via a delete token. The attacker literally has nothing. The four clean locations are rebuilding the fragment internally.", 4: "Data retrieved — completely clean and intact. The attacker got absolutely nothing. Zero ransom ever needs to be paid. This is the power of distributed encryption." } }, fr: { outage: { 0: "Bienvenue. Vous regardez la carte du réseau ChunkWorks dans les Caraïbes. Vos données vont être distribuées sur cinq emplacements géographiquement séparés. Cliquez sur le bouton pour commencer.", 1: "ChunkWorks divise maintenant votre fichier en cinq fragments chiffrés — chacun allant vers un emplacement différent. Aucun emplacement, pas même nous, ne détient le fichier complet. Seul vous détenez la clé.", 2: "Un datacenter vient de tomber complètement hors ligne. Mais regardez — vos données sont toujours accessibles à cent pour cent. Quatre autres emplacements détiennent encore leurs fragments. Vous n'en avez besoin que de trois.", 3: "Regardez l'auto-réparation en action. Les quatre emplacements restants ont détecté la panne et redistribué le fragment manquant entre eux. Aucun appel IT. Aucune action manuelle. Le système s'est réparé seul.", 4: "Vos données ont été récupérées — complètement intactes et vérifiées. Zéro seconde d'arrêt. Zéro octet perdu. Même pendant qu'un datacenter entier tombait en panne. C'est ce qui rend ChunkWorks unique." }, ransomware: { 0: "Bienvenue. Vous regardez le réseau ChunkWorks dans les Caraïbes. Nous allons simuler une attaque ransomware — et vous verrez exactement pourquoi cette attaque est totalement inutile contre notre architecture.", 1: "Vos données sont maintenant distribuées sur cinq emplacements en cinq fragments chiffrés. Les fragments n'ont de sens qu'ensemble. Sans les cinq — et votre clé privée — chaque fragment est du bruit pur.", 2: "Un attaquant a compromis un emplacement et chiffre tout. Qu'a-t-il obtenu ? Un fragment chiffré sur cinq. Sans les quatre autres, c'est du bruit aléatoire. La demande de rançon est déjà sans objet.", 3: "ChunkWorks a détecté la brèche. Le fragment compromis est maintenant révoqué cryptographiquement via un jeton de suppression. L'attaquant n'a littéralement rien. Les quatre emplacements sains reconstruisent le fragment.", 4: "Données récupérées — complètement propres et intactes. L'attaquant n'a absolument rien obtenu. Aucune rançon ne doit jamais être payée. C'est la puissance du chiffrement distribué." } } }; // ── AI CHATBOT STATE ── var aiSpeakEnabled = true; var aiSpeaking = false; var autoRunning = false; var autoTimer = null; var AUTO_DELAY = 5500; // ms tussen stappen function getAIScript(sc, st) { var lang = simLang in AI_SCRIPTS ? simLang : 'en'; var scripts = AI_SCRIPTS[lang][sc] || AI_SCRIPTS[lang]['outage']; return scripts[st] || scripts[0]; } function setAIMessage(text, stepNum, totalSteps) { var bubble = document.getElementById('ai-bubble'); var status = document.getElementById('ai-status'); var prog = document.getElementById('ai-progress'); var cnt = document.getElementById('ai-step-count'); // Fade out → update → fade in bubble.classList.add('fading'); setTimeout(function() { // Toon typing indicator bubble.innerHTML = '
'; bubble.classList.remove('fading'); setTimeout(function() { bubble.classList.add('fading'); setTimeout(function() { bubble.innerHTML = text; bubble.classList.remove('fading'); // Progress bar if (totalSteps > 0) { var pct = Math.round((stepNum / totalSteps) * 100); prog.style.width = pct + '%'; cnt.textContent = simLang === 'nl' ? ('Stap ' + stepNum + ' van ' + totalSteps) : simLang === 'fr' ? ('Étape ' + stepNum + ' sur ' + totalSteps) : ('Step ' + stepNum + ' of ' + totalSteps); } else { prog.style.width = '0%'; cnt.textContent = ''; } // Speech if (aiSpeakEnabled) speakText(text); }, 300); }, 900); // typing duration }, 300); status.textContent = '● ' + (simLang === 'nl' ? 'Actief' : simLang === 'fr' ? 'Actif' : 'Active'); } // ── TEXT-TO-SPEECH ── function speakText(text) { if (!window.speechSynthesis) return; window.speechSynthesis.cancel(); var clean = text.replace(/<[^>]+>/g, ''); var utt = new SpeechSynthesisUtterance(clean); utt.lang = simLang === 'nl' ? 'nl-NL' : simLang === 'fr' ? 'fr-FR' : 'en-US'; utt.rate = 0.92; utt.pitch = 1.0; var btn = document.getElementById('ai-speak-btn'); utt.onstart = function() { aiSpeaking = true; btn.classList.add('speaking'); }; utt.onend = function() { aiSpeaking = false; btn.classList.remove('speaking'); }; utt.onerror = function() { aiSpeaking = false; btn.classList.remove('speaking'); }; window.speechSynthesis.speak(utt); } // Toggle speak document.getElementById('ai-speak-btn').addEventListener('click', function() { aiSpeakEnabled = !aiSpeakEnabled; if (!aiSpeakEnabled && window.speechSynthesis) window.speechSynthesis.cancel(); this.innerHTML = aiSpeakEnabled ? '' : ''; this.title = aiSpeakEnabled ? 'Spraak uitschakelen' : 'Spraak inschakelen'; }); // ── HOOK INTO EXISTING DISPATCH ── // Wrap de bestaande dispatch functie var _origDispatch = dispatch; dispatch = function() { _origDispatch(); // Trigger AI comment na korte delay (animatie loopt) var curStep = step; var curScen = scenario; setTimeout(function() { setAIMessage(getAIScript(curScen, curStep), curStep, 4); }, 600); }; // Hook reset var _origReset = doReset; doReset = function() { _origReset(); stopAuto(); setTimeout(function() { setAIMessage(getAIScript(scenario, 0), 0, 0); }, 400); }; // ── AUTO-PLAY ── function startAuto() { if (autoRunning) { stopAuto(); return; } // Gebruik _origReset i.p.v. de gewrapte doReset — // de gewrapte versie roept stopAuto() aan wat autoRunning direct weer op false zet _origReset(); autoRunning = true; var btn = document.getElementById('btn-auto'); btn.classList.add('running'); btn.querySelector('#btn-auto-label').textContent = simLang === 'nl' ? 'Stop Auto' : simLang === 'fr' ? 'Arrêter' : 'Stop Auto'; // Welkomstbericht tonen, dan na korte pauze eerste stap starten setAIMessage(getAIScript(scenario, 0), 0, 0); autoTimer = setTimeout(runAutoStep, 1800); } function stopAuto() { autoRunning = false; clearTimeout(autoTimer); var btn = document.getElementById('btn-auto'); btn.classList.remove('running'); btn.querySelector('#btn-auto-label').textContent = simLang === 'nl' ? 'Auto Demo' : simLang === 'fr' ? 'Démo Auto' : 'Auto Demo'; if (window.speechSynthesis) window.speechSynthesis.cancel(); } function waitForSpeechThenNext(cb) { if (aiSpeakEnabled && window.speechSynthesis && window.speechSynthesis.speaking) { autoTimer = setTimeout(function() { waitForSpeechThenNext(cb); }, 300); } else { autoTimer = setTimeout(cb, 1200); } } function runAutoStep() { if (!autoRunning) return; if (isBusy) { autoTimer = setTimeout(runAutoStep, 600); return; } if (step < 4) { step++; dispatch(); autoTimer = setTimeout(function() { waitForSpeechThenNext(runAutoStep); }, 2000); } else { // Scenario 1 klaar — wissel naar ransomware als we outage deden if (scenario === 'outage') { autoTimer = setTimeout(function() { if (!autoRunning) return; switchScenario('ransomware'); // Geef AI hint dat we gaan wisselen setTimeout(function() { var switchMsg = simLang === 'nl' ? "Uitstekend! Datacenter uitval: volledig afgehandeld zonder enige actie van u. Nu simuleren we een ransomware-aanval — een compleet ander dreigingsscenario." : simLang === 'fr' ? "Excellent ! Panne datacenter gérée sans aucune action de votre part. Simulons maintenant une attaque ransomware — un scénario de menace complètement différent." : "Excellent! Datacenter outage handled completely without any action from you. Now let's simulate a ransomware attack — a completely different threat scenario."; setAIMessage(switchMsg, 0, 0); autoTimer = setTimeout(function() { if (!autoRunning) return; runAutoStep(); }, AUTO_DELAY + 1000); }, 600); }, AUTO_DELAY); } else { // Beide scenario's klaar autoTimer = setTimeout(function() { if (!autoRunning) return; var doneMsg = simLang === 'nl' ? "Demonstratie voltooid. Twee scenario's — uitval en ransomware — beide volledig afgehandeld zonder menselijke tussenkomst. Nul downtime. Nul dataverlies. Dit is ChunkWorks." : simLang === 'fr' ? "Démonstration terminée. Deux scénarios — panne et ransomware — tous deux gérés sans intervention humaine. Zéro temps d'arrêt. Zéro perte de données. C'est ChunkWorks." : "Demonstration complete. Two scenarios — outage and ransomware — both handled with zero human intervention. Zero downtime. Zero data loss. This is ChunkWorks."; setAIMessage(doneMsg, 4, 4); stopAuto(); }, AUTO_DELAY); } } } document.getElementById('btn-auto').addEventListener('click', function() { startAuto(); }); // Initieel AI welkomstbericht })();