{"id":51255,"date":"2026-09-19T19:31:11","date_gmt":"2026-09-19T18:31:11","guid":{"rendered":"https:\/\/cctvacademy.be\/?page_id=51255"},"modified":"2026-09-20T11:20:47","modified_gmt":"2026-09-20T10:20:47","slug":"cctv-pacman","status":"publish","type":"page","link":"https:\/\/cctvacademy.be\/fr\/cctv-pacman\/","title":{"rendered":"CCTV Pacman"},"content":{"rendered":"\t\t<div data-elementor-type=\"masteriyo-course-archive-page\" data-elementor-id=\"51255\" class=\"elementor elementor-51255\">\n\t\t\t\t<div class=\"elementor-element elementor-element-61ff1eb e-flex e-con-boxed e-con e-parent\" data-id=\"61ff1eb\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-051539b elementor-widget elementor-widget-spacer\" data-id=\"051539b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-b5436fd e-flex e-con-boxed e-con e-parent\" data-id=\"b5436fd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9b8fe6c elementor-widget elementor-widget-button\" data-id=\"9b8fe6c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-button-wrapper\">\n\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/cctvacademy.be\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Home<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-b3d7bcf e-flex e-con-boxed e-con e-parent\" data-id=\"b3d7bcf\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bb89146 elementor-widget elementor-widget-text-editor\" data-id=\"bb89146\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><span style=\"color: #ffffff;\">Under Construction<\/span><\/p><p>\u00a0<\/p><p>\u00a0<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3df00bd e-flex e-con-boxed e-con e-parent\" data-id=\"3df00bd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1c88fcd elementor-widget elementor-widget-video\" data-id=\"1c88fcd\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;video_type&quot;:&quot;hosted&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"e-hosted-video elementor-wrapper elementor-open-inline\">\n\t\t\t\t\t<video class=\"elementor-video\" src=\"https:\/\/cctvacademy.be\/wp-content\/uploads\/2026\/09\/cctv-pacmaze-intro-movie-2-cctv-academy.mp4\" controls=\"\" preload=\"metadata\" controlsList=\"nodownload\"><\/video>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8b0f16f e-flex e-con-boxed e-con e-parent\" data-id=\"8b0f16f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b21b8b9 elementor-widget elementor-widget-spacer\" data-id=\"b21b8b9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-e4c2c16 e-flex e-con-boxed e-con e-parent\" data-id=\"e4c2c16\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c2a60cb elementor-widget elementor-widget-html\" data-id=\"c2a60cb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"nl\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\r\n<title>CCTV PAC-MAZE v0.4 \u2014 Night Watch<\/title>\r\n<style>\r\n:root{\r\n  --bg:#020609;--panel:#07131a;--panel2:#0a1d27;--line:#20d8ff;--green:#8dff75;\r\n  --warning:#ffd166;--danger:#ff4d6d;--text:#eefcff;--muted:#7da4b3;\r\n}\r\n*{box-sizing:border-box}\r\nhtml,body{margin:0;min-height:100%;background:#020609;color:var(--text);font-family:Inter,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\r\nbody{padding:16px;background:radial-gradient(circle at 20% 0,rgba(32,216,255,.08),transparent 28%),radial-gradient(circle at 90% 90%,rgba(141,255,117,.05),transparent 30%),#020609}\r\n#cctvGame{max-width:1120px;margin:auto;outline:none}\r\n.brand{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:12px}\r\n.brand h1{margin:0;font-size:clamp(26px,4.4vw,48px);line-height:.95;letter-spacing:.07em}\r\n.brand .cctv{color:#fff;text-shadow:0 0 8px rgba(255,255,255,.75),0 0 22px rgba(255,255,255,.25)}\r\n.brand .pac{color:var(--green);text-shadow:0 0 18px rgba(141,255,117,.28)}\r\n.brand .version{display:inline-block;margin-left:10px;padding:4px 7px;border:1px solid rgba(32,216,255,.28);border-radius:7px;color:var(--line);font-size:clamp(9px,1.2vw,12px);line-height:1;letter-spacing:.08em;vertical-align:middle;text-shadow:none;background:rgba(4,16,24,.8)}\r\n.brandMeta{font-size:11px;color:var(--muted);text-align:right;line-height:1.5}\r\n.layout{display:grid;grid-template-columns:minmax(0,1fr) 290px;gap:14px;align-items:start}\r\n.gamePanel,.card{background:linear-gradient(180deg,rgba(9,27,37,.96),rgba(3,12,17,.98));border:1px solid rgba(32,216,255,.24);border-radius:16px}\r\n.gamePanel{padding:10px;overflow:hidden}\r\n.hud{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:7px;margin-bottom:9px}\r\n.hudBox{min-width:0;padding:7px 8px;border:1px solid rgba(125,164,179,.17);border-radius:9px;background:#041018}\r\n.hudLabel{font-size:9px;text-transform:uppercase;letter-spacing:.13em;color:var(--muted);white-space:nowrap}\r\n.hudValue{font-weight:900;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}\r\n.meter{height:4px;background:#102934;border-radius:99px;overflow:hidden;margin-top:5px}\r\n.meter>i{display:block;height:100%;width:100%;background:linear-gradient(90deg,var(--line),var(--green));transition:width .08s linear}\r\n.goalStrip{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 9px;padding:9px 11px;border:1px solid rgba(255,209,102,.32);border-radius:10px;background:rgba(40,27,3,.46);color:#fff3c8;font-size:11px;font-weight:900;letter-spacing:.05em}.goalStrip strong{color:var(--warning);font-size:13px}.goalCount{white-space:nowrap;color:#fff;font-size:14px}.gameStage{position:relative;overflow:hidden;border:1px solid rgba(32,216,255,.32);border-radius:12px;background:#000;aspect-ratio:10\/7;touch-action:none}\r\n.gameStage:after{content:\"\";position:absolute;inset:0;z-index:4;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.017) 0,rgba(255,255,255,.017) 1px,transparent 1px,transparent 4px);mix-blend-mode:screen}\r\ncanvas{display:block;width:100%;height:100%;outline:none;background:#000}.volumeBtn{position:absolute;right:10px;top:10px;z-index:8;min-width:44px;height:38px;padding:0 10px;border:1px solid rgba(255,255,255,.24);border-radius:9px;background:rgba(2,11,16,.82);color:#fff;cursor:pointer;font-weight:900;font-size:16px;backdrop-filter:blur(4px)}.volumeBtn:focus-visible{outline:2px solid var(--line);outline-offset:2px}\r\n.overlay{position:absolute;inset:0;z-index:10;display:grid;place-items:center;padding:22px;background:linear-gradient(rgba(0,7,10,.86),rgba(0,9,13,.97));transition:opacity .2s ease}\r\n.overlay.hidden{opacity:0;pointer-events:none}\r\n.intro{width:min(510px,100%);text-align:center}\r\n.eyebrow{font-size:10px;letter-spacing:.18em;color:var(--line);text-transform:uppercase}\r\n.intro h2{font-size:clamp(31px,7vw,58px);line-height:.93;margin:8px 0 10px}.intro h2 .white{color:#fff}.intro h2 b{color:var(--green)}\r\n.intro p{max-width:470px;margin:0 auto 14px;color:#b1d1db;font-size:13px;line-height:1.55}\r\n.tips{display:grid;grid-template-columns:1fr 1fr;gap:7px;text-align:left;margin:12px 0}\r\n.tip{padding:9px;border-radius:9px;background:rgba(7,25,34,.8);border:1px solid rgba(125,164,179,.15);font-size:11px;line-height:1.35;color:#b7d4dc}.tip b{color:#fff}\r\n.startRow{display:flex;gap:8px;margin-top:10px}\r\ninput,button{font:inherit}input{min-width:0;flex:1;padding:12px;border-radius:10px;border:1px solid rgba(32,216,255,.34);background:#020b10;color:#fff;outline:none;text-transform:uppercase;font-size:16px}input:focus{border-color:var(--line);box-shadow:0 0 0 3px rgba(32,216,255,.08)}\r\n.btn{min-height:46px;border:0;border-radius:10px;padding:11px 15px;cursor:pointer;font-weight:950;letter-spacing:.06em;color:#001015;background:linear-gradient(135deg,var(--line),var(--green))}.btn:active{transform:scale(.98)}.btn.secondary{margin-top:8px;color:var(--text);background:#0a202a;border:1px solid rgba(125,164,179,.25)}\r\n.startNote{font-size:9px;letter-spacing:.12em;color:var(--muted);margin-top:10px}\r\n.side{display:flex;flex-direction:column;gap:12px}.card{padding:14px}.card h3{font-size:12px;color:var(--line);letter-spacing:.12em;text-transform:uppercase;margin:0 0 10px}\r\n.scoreTable{width:100%;border-collapse:collapse;font-size:11px}.scoreTable th{font-size:9px;color:var(--muted);text-align:left;border-bottom:1px solid rgba(125,164,179,.14);padding:0 0 6px}.scoreTable td{padding:7px 0;border-bottom:1px solid rgba(125,164,179,.07)}.scoreTable td:first-child{color:var(--warning);width:28px}.scoreTable td:last-child{text-align:right;font-weight:900}\r\n.protocolCard{padding:10px 12px}.protocolCard details{margin:0}.protocolCard summary{display:flex;align-items:center;justify-content:space-between;gap:8px;cursor:pointer;list-style:none;color:var(--line);font-size:11px;font-weight:900;letter-spacing:.11em;text-transform:uppercase}.protocolCard summary::-webkit-details-marker{display:none}.protocolCard summary:after{content:'+';display:grid;place-items:center;width:22px;height:22px;border:1px solid rgba(32,216,255,.25);border-radius:6px;color:#fff;font-size:15px}.protocolCard details[open] summary:after{content:'\u2212'}.protocolBody{padding-top:10px}.legend{display:grid;gap:7px;font-size:10px;line-height:1.35;color:#b3d2da}.legendRow{display:grid;grid-template-columns:24px 1fr;gap:6px;align-items:start}.key{display:grid;place-items:center;min-width:22px;height:22px;background:#071820;border:1px solid rgba(125,164,179,.25);border-bottom-width:3px;border-radius:5px;color:#fff;font-size:9px}.status{margin-top:9px;padding-top:8px;border-top:1px solid rgba(125,164,179,.12);font-size:10px;color:var(--muted);line-height:1.4}\r\n.touchControls{display:none;grid-template-columns:1fr 1fr;gap:12px;margin-top:10px;user-select:none;touch-action:none}.dpad{width:148px;height:148px;margin:auto;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:5px}.touchBtn{min-width:44px;min-height:44px;border-radius:11px;border:1px solid rgba(32,216,255,.28);background:#0a202b;color:#e7fbff;font-weight:900;font-size:20px}.flashWrap{display:grid;place-items:center;align-content:center;gap:8px}.flashBtn{width:108px;height:108px;border-radius:50%;font-size:12px;letter-spacing:.08em;color:#1d1400;background:radial-gradient(circle at 35% 30%,#fff,#ffe78a 22%,#ffb13b 65%,#ff5f3d);border:4px solid rgba(255,255,255,.12)}.sentryBtn{min-width:108px;padding:10px 12px;font-size:11px;letter-spacing:.09em;color:#dffff2;background:#092419;border-color:rgba(141,255,117,.42)}.sentryBtn.active{color:#00150a;background:linear-gradient(135deg,#8dff75,#20d8ff);box-shadow:0 0 18px rgba(141,255,117,.28)}\r\n.footer{text-align:center;color:#42636d;font-size:9px;letter-spacing:.11em;margin-top:9px}\r\n@media (hover:hover) and (pointer:fine){.btn:hover{filter:brightness(1.08);transform:translateY(-1px)}.touchBtn:hover{background:#10303f}}\r\n@media(max-width:900px){.layout{grid-template-columns:1fr}.side{display:grid;grid-template-columns:1fr 1fr}.touchControls{display:grid}.brandMeta{display:none}}\r\n@media(max-width:620px){body{padding:7px}.gamePanel{padding:7px}.hud{grid-template-columns:repeat(3,1fr)}.side{grid-template-columns:1fr}.tips{grid-template-columns:1fr}.startRow{flex-direction:column}.touchControls{grid-template-columns:1fr 1fr}.dpad{width:132px;height:132px}.flashBtn{width:96px;height:96px}.brand .version{margin-left:6px}}\r\n<\/style>\r\n<\/head>\r\n<body>\r\n<div id=\"cctvGame\" tabindex=\"0\">\r\n  <div class=\"brand\">\r\n    <h1><span class=\"cctv\">CCTV<\/span> <span class=\"pac\">PAC-MAZE<\/span><span class=\"version\">v0.4<\/span><\/h1>\r\n    <div class=\"brandMeta\">NIGHT WATCH \/\/ LIVE FEED<br>SECURITY MAZE CHASE<\/div>\r\n  <\/div>\r\n\r\n  <div class=\"layout\">\r\n    <section class=\"gamePanel\">\r\n      <div class=\"hud\">\r\n        <div class=\"hudBox\"><div class=\"hudLabel\">Score<\/div><div class=\"hudValue\" id=\"scoreEl\">000000<\/div><\/div>\r\n        <div class=\"hudBox\"><div class=\"hudLabel\">High<\/div><div class=\"hudValue\" id=\"highEl\">000000<\/div><\/div>\r\n        <div class=\"hudBox\"><div class=\"hudLabel\">Level<\/div><div class=\"hudValue\" id=\"levelEl\">01<\/div><\/div>\r\n        <div class=\"hudBox\"><div class=\"hudLabel\">Cams<\/div><div class=\"hudValue\" id=\"livesEl\">\ud83d\udcf9 \ud83d\udcf9 \ud83d\udcf9<\/div><\/div>\r\n        <div class=\"hudBox\"><div class=\"hudLabel\">Doel<\/div><div class=\"hudValue\" id=\"progressEl\">0 \/ 5<\/div><div class=\"meter\"><i id=\"progressFill\"><\/i><\/div><\/div>\r\n        <div class=\"hudBox\"><div class=\"hudLabel\">Flash<\/div><div class=\"hudValue\" id=\"flashText\">READY<\/div><div class=\"meter\"><i id=\"flashFill\"><\/i><\/div><\/div>\r\n      <\/div>\r\n\r\n      <div class=\"goalStrip\" id=\"goalStrip\"><span><strong>DOEL VAN HET SPEL:<\/strong> FLASH 5 INBREKERS<\/span><span class=\"goalCount\" id=\"goalCount\">0 \/ 5<\/span><\/div>\r\n\r\n      <div class=\"gameStage\" id=\"stage\">\r\n        <canvas id=\"gameCanvas\" width=\"800\" height=\"560\" aria-label=\"Interactief CCTV Pac-Maze spel\"><\/canvas>\r\n        <button type=\"button\" class=\"volumeBtn\" id=\"volumeBtn\" aria-label=\"Geluid uitschakelen\" title=\"Geluid aan of uit\">\ud83d\udd0a<\/button>\r\n\r\n        <div class=\"overlay\" id=\"overlay\">\r\n          <div class=\"intro\">\r\n            <div class=\"eyebrow\" id=\"overlayEyebrow\">SECURITY SYSTEM \/\/ STANDBY<\/div>\r\n            <h2 id=\"overlayTitle\"><span class=\"white\">CCTV<\/span> <b>NIGHT WATCH<\/b><\/h2>\r\n            <p id=\"overlayText\"><b>DOEL VAN HET SPEL: FLASH 5 INBREKERS.<\/b> Klik of tik voor automatische navigatie, of neem zelf over met de pijltjestoetsen. Houd SPATIE ingedrukt om stil te staan en te flashen; de lichtbundel stopt aan muren.<\/p>\r\n\r\n            <div class=\"tips\" id=\"introTips\">\r\n              <div class=\"tip\"><b>Doel: 5 inbrekers flashen<\/b><br>Elke nieuwe succesvolle flash telt. Bij 5\/5 is de missie voltooid en kun je opnieuw spelen.<\/div>\r\n              <div class=\"tip\"><b>Flash + jacht<\/b><br>Houd SPATIE vast: camera stopt en draait met \u2190\/\u2192. Geflashte inbrekers blijven staan; na loslaten gaat de camera ze automatisch pakken.<\/div>\r\n              <div class=\"tip\"><b>Alarm-orb \u25c9<\/b><br>Maakt inbrekers tijdelijk kwetsbaar. Raak ze dan voor combo-punten.<\/div>\r\n              <div class=\"tip\"><b>Sentry-modus<\/b><br>Druk E: stop op een blok, scan 360\u00b0 en flash automatisch zodra een inbreker in de volledige gang verschijnt.<\/div>\r\n            <\/div>\r\n\r\n            <div class=\"startRow\">\r\n              <input id=\"nameInput\" maxlength=\"12\" value=\"SECURITY\" autocomplete=\"off\" aria-label=\"Operatornaam\">\r\n              <button type=\"button\" class=\"btn\" id=\"startBtn\">\u25b6 START GAME<\/button>\r\n            <\/div>\r\n            <button type=\"button\" class=\"btn secondary\" id=\"soundBtn\">\ud83d\udd0a GELUID AAN<\/button>\r\n            <div class=\"startNote\">HET SPEL BEGINT PAS NADAT JE OP START GAME DRUKT<\/div>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"touchControls\" aria-label=\"Touchbesturing\">\r\n        <div class=\"dpad\">\r\n          <span><\/span><button type=\"button\" class=\"touchBtn\" data-dir=\"up\">\u25b2<\/button><span><\/span>\r\n          <button type=\"button\" class=\"touchBtn\" data-dir=\"left\">\u25c0<\/button><button type=\"button\" class=\"touchBtn\" data-dir=\"down\">\u25bc<\/button><button type=\"button\" class=\"touchBtn\" data-dir=\"right\">\u25b6<\/button>\r\n          <span><\/span><span><\/span><span><\/span>\r\n        <\/div>\r\n        <div class=\"flashWrap\"><button type=\"button\" class=\"touchBtn flashBtn\" id=\"flashTouch\">HOLD FLASH<\/button><button type=\"button\" class=\"touchBtn sentryBtn\" id=\"sentryTouch\">SENTRY<\/button><\/div>\r\n      <\/div>\r\n    <\/section>\r\n\r\n    <aside class=\"side\">\r\n      <section class=\"card\">\r\n        <h3>Local High Scores<\/h3>\r\n        <table class=\"scoreTable\">\r\n          <thead><tr><th>#<\/th><th>Operator<\/th><th>Score<\/th><\/tr><\/thead>\r\n          <tbody id=\"scoreTable\"><\/tbody>\r\n        <\/table>\r\n      <\/section>\r\n      <section class=\"card protocolCard\">\r\n        <details id=\"protocolDetails\">\r\n          <summary>Security Protocol<\/summary>\r\n          <div class=\"protocolBody\">\r\n            <div class=\"legend\">\r\n              <div class=\"legendRow\"><span class=\"key\">\ud83c\udfaf<\/span><span><b>Doel<\/b> \u2014 flash 5 inbrekers. Bij 5\/5 is het spel gedaan.<\/span><\/div>\r\n              <div class=\"legendRow\"><span class=\"key\">\u2423<\/span><span><b>Flash hold<\/b> \u2014 vasthouden = stilstaan + licht aan; \u2190\/\u2192 draait. Licht gaat niet door muren.<\/span><\/div>\r\n              <div class=\"legendRow\"><span class=\"key\">\u2191<\/span><span><b>Manueel<\/b> \u2014 stuur met de pijltjestoetsen.<\/span><\/div>\r\n              <div class=\"legendRow\"><span class=\"key\">\u25ce<\/span><span><b>Automatisch<\/b> \u2014 klik of tik in het gebouw voor een route.<\/span><\/div>\r\n              <div class=\"legendRow\"><span class=\"key\">E<\/span><span><b>Sentry<\/b> \u2014 stationaire 360\u00b0 bewaking.<\/span><\/div>\r\n              <div class=\"legendRow\"><span class=\"key\">P<\/span><span><b>Pauze<\/b> \u2014 M of de volumeknop schakelt geluid.<\/span><\/div>\r\n            <\/div>\r\n          <\/div>\r\n        <\/details>\r\n        <div class=\"status\" id=\"statusLine\">Wacht op operator\u2026<\/div>\r\n      <\/section>\r\n    <\/aside>\r\n  <\/div>\r\n  <div class=\"footer\">CCTV NIGHT WATCH \/\/ v0.4 \/\/ INTERACTIVE SECURITY FEED<\/div>\r\n<\/div>\r\n\r\n<script>\r\n(() => {\r\n'use strict';\r\n\r\nconst root = document.getElementById('cctvGame');\r\nconst canvas = document.getElementById('gameCanvas');\r\nconst ctx = canvas.getContext('2d');\r\nconst overlay = document.getElementById('overlay');\r\nconst overlayEyebrow = document.getElementById('overlayEyebrow');\r\nconst overlayTitle = document.getElementById('overlayTitle');\r\nconst overlayText = document.getElementById('overlayText');\r\nconst introTips = document.getElementById('introTips');\r\nconst startBtn = document.getElementById('startBtn');\r\nconst soundBtn = document.getElementById('soundBtn');\r\nconst volumeBtn = document.getElementById('volumeBtn');\r\nconst goalCount = document.getElementById('goalCount');\r\nconst nameInput = document.getElementById('nameInput');\r\nconst scoreEl = document.getElementById('scoreEl');\r\nconst highEl = document.getElementById('highEl');\r\nconst levelEl = document.getElementById('levelEl');\r\nconst livesEl = document.getElementById('livesEl');\r\nconst progressEl = document.getElementById('progressEl');\r\nconst progressFill = document.getElementById('progressFill');\r\nconst flashText = document.getElementById('flashText');\r\nconst flashFill = document.getElementById('flashFill');\r\nconst scoreTable = document.getElementById('scoreTable');\r\nconst statusLine = document.getElementById('statusLine');\r\nconst sentryTouch = document.getElementById('sentryTouch');\r\n\r\nconst VIEW_W = 800;\r\nconst VIEW_H = 560;\r\nconst TILE = 28;\r\nconst MAZE_W = 31;\r\nconst MAZE_H = 39;\r\nconst TUNNEL_ROW = 19;\r\nconst WORLD_W = MAZE_W * TILE;\r\nconst WORLD_H = MAZE_H * TILE;\r\nconst TAU = Math.PI * 2;\r\n\r\nconst DPR = Math.min(window.devicePixelRatio || 1, 2);\r\ncanvas.width = VIEW_W * DPR;\r\ncanvas.height = VIEW_H * DPR;\r\nctx.setTransform(DPR,0,0,DPR,0,0);\r\n\r\nconst DIRS = {\r\n  up:{x:0,y:-1,name:'up'},\r\n  down:{x:0,y:1,name:'down'},\r\n  left:{x:-1,y:0,name:'left'},\r\n  right:{x:1,y:0,name:'right'},\r\n  none:{x:0,y:0,name:'none'}\r\n};\r\nconst CARDINALS = [DIRS.up,DIRS.left,DIRS.down,DIRS.right];\r\n\r\nlet memoryScores = [];\r\nlet audioCtx = null;\r\n\r\nconst state = {\r\n  phase:'intro', paused:false, score:0, level:1, lives:3, name:'SECURITY',\r\n  map:[], player:null, burglars:[], dots:0, initialDots:0, collected:0,\r\n  exposed:0, exposedCombo:0, flashCooldown:0, flashFx:0, flashPulse:0, invuln:0,\r\n  bonus:null, bonusMilestones:[], banner:'', bannerTime:0, readyTimer:0,\r\n  sound:true, shake:0, chaseMode:true, modeTimer:10,\r\n  viewX:0, viewY:0, lastTime:performance.now(), levelSeed:0,\r\n  sentry:false, sentryPending:false, sentryTurnTimer:0, sentryHold:0, sentryDetected:false, sentryScanPulse:0, sentryResumeDir:null, navTarget:null,\r\n  manualMode:false, flashHeld:false, flashTurnCooldown:0, flashResumeDir:null, flashResumeDesired:null, flashHoldTagged:[false,false,false,false], huntTarget:null, flashCount:0, flashGoal:5\r\n};\r\n\r\nfunction clamp(v,a,b){ return Math.max(a,Math.min(b,v)); }\r\nfunction dist(a,b){ return Math.hypot(a.x-b.x,a.y-b.y); }\r\nfunction reverseOf(a,b){ return !!a && !!b && a.x===-b.x && a.y===-b.y; }\r\nfunction formatScore(n){ return String(Math.max(0,Math.floor(n))).padStart(6,'0'); }\r\nfunction escapeHtml(value){ return String(value).replace(\/[&<>'\"]\/g,ch=>({'&':'&amp;','<':'&lt;','>':'&gt;',\"'\":'&#39;','\"':'&quot;'}[ch])); }\r\nfunction seeded(seed){ let a=seed|0; return () => { a=(a+0x6D2B79F5)|0; let t=a; t=Math.imul(t^(t>>>15),t|1); t^=t+Math.imul(t^(t>>>7),t|61); return ((t^(t>>>14))>>>0)\/4294967296; }; }\r\nfunction shuffle(array,rnd){ for(let i=array.length-1;i>0;i--){ const j=Math.floor(rnd()*(i+1)); [array[i],array[j]]=[array[j],array[i]]; } return array; }\r\n\r\nfunction ensureAudio(){\r\n  if(!state.sound) return;\r\n  const AC = window.AudioContext || window.webkitAudioContext;\r\n  if(!AC) return;\r\n  if(!audioCtx) audioCtx = new AC();\r\n  if(audioCtx.state==='suspended') audioCtx.resume();\r\n}\r\nfunction beep(freq=440,dur=.05,type='square',vol=.02,slide=0){\r\n  if(!state.sound) return;\r\n  ensureAudio();\r\n  if(!audioCtx) return;\r\n  const o=audioCtx.createOscillator(), g=audioCtx.createGain(), t=audioCtx.currentTime;\r\n  o.type=type; o.frequency.setValueAtTime(freq,t);\r\n  if(slide) o.frequency.exponentialRampToValueAtTime(Math.max(40,freq+slide),t+dur);\r\n  g.gain.setValueAtTime(vol,t); g.gain.exponentialRampToValueAtTime(.0001,t+dur);\r\n  o.connect(g); g.connect(audioCtx.destination); o.start(t); o.stop(t+dur);\r\n}\r\nfunction sfx(type){\r\n  if(type==='dot') beep(620,.018,'square',.009,70);\r\n  if(type==='orb'){ beep(190,.11,'sawtooth',.03,520); setTimeout(()=>beep(720,.08,'square',.02,-140),70); }\r\n  if(type==='flash') beep(1050,.055,'sawtooth',.025,-650);\r\n  if(type==='stun') beep(260,.08,'square',.022,190);\r\n  if(type==='capture'){ beep(430,.07,'square',.028,320); setTimeout(()=>beep(780,.07,'square',.02,80),65); }\r\n  if(type==='hit'){ beep(160,.17,'sawtooth',.04,-90); setTimeout(()=>beep(85,.20,'square',.02,-20),100); }\r\n  if(type==='bonus'){ [820,1060,1320].forEach((f,i)=>setTimeout(()=>beep(f,.06,'triangle',.02),i*50)); }\r\n  if(type==='level'){ [440,560,680,900].forEach((f,i)=>setTimeout(()=>beep(f,.09,'square',.025,60),i*80)); }\r\n}\r\n\r\nfunction generateMaze(level){\r\n  const rnd=seeded(24681357 + level*7919);\r\n  const grid=Array.from({length:MAZE_H},()=>Array(MAZE_W).fill('#'));\r\n\r\n  const floor=(x,y)=>{ if(x>=0&&x<MAZE_W&&y>=0&&y<MAZE_H) grid[y][x]=' '; };\r\n  const room=(x,y,w,h)=>{ for(let yy=y;yy<y+h;yy++) for(let xx=x;xx<x+w;xx++) floor(xx,yy); };\r\n  const hallH=(x1,x2,y,width=1)=>{ for(let yy=y;yy<y+width;yy++) for(let x=x1;x<=x2;x++) floor(x,yy); };\r\n  const hallV=(y1,y2,x,width=1)=>{ for(let xx=x;xx<x+width;xx++) for(let y=y1;y<=y2;y++) floor(xx,y); };\r\n  const doorH=(x1,x2,y)=>hallH(Math.min(x1,x2),Math.max(x1,x2),y,1);\r\n  const doorV=(y1,y2,x)=>hallV(Math.min(y1,y2),Math.max(y1,y2),x,1);\r\n\r\n  \/\/ Kamers: bewust verschillende afmetingen zodat dit als een gebouw leest.\r\n  room(1,1,8,6);       \/\/ noordwest kantoor\r\n  room(12,1,7,5);      \/\/ noord vergaderzaal\r\n  room(22,1,8,6);      \/\/ noordoost grote ruimte\r\n  room(1,10,7,7);      \/\/ westelijke ruimte\r\n  room(12,10,7,5);     \/\/ kleine middenruimte\r\n  room(22,10,8,7);     \/\/ oostelijke ruimte\r\n  room(11,16,9,7);     \/\/ centrale hal \/ beveiligingsruimte\r\n  room(1,21,8,7);      \/\/ zuidwest grote ruimte\r\n  room(22,21,8,7);     \/\/ zuidoost grote ruimte\r\n  room(12,24,3,4);     \/\/ kleine berging\r\n  room(17,24,3,4);     \/\/ kleine technische ruimte\r\n  room(1,32,9,6);      \/\/ beneden links\r\n  room(12,31,7,7);     \/\/ beneden midden\r\n  room(22,31,8,6);     \/\/ beneden rechts\r\n\r\n  \/\/ Hoofdgangen.\r\n  hallV(1,37,10);\r\n  hallV(1,37,20);\r\n  hallH(1,29,8);\r\n  hallH(1,29,19);\r\n  hallH(1,29,29);\r\n  hallV(6,15,15);\r\n  hallV(23,30,15);\r\n\r\n  \/\/ Deuren en korte doorgangen. De muren rond de kamers blijven grotendeels dicht.\r\n  doorH(9,10,4);       doorV(7,8,4);       \/\/ NW\r\n  doorH(10,12,3);      doorH(18,20,3);     doorV(5,8,15); \/\/ noord midden\r\n  doorH(20,22,4);      doorV(7,8,26);       \/\/ NE\r\n\r\n  doorV(8,10,4);       doorH(7,10,13);      doorV(16,19,5); \/\/ west midden\r\n  doorV(8,10,15);      doorH(10,12,12);     doorH(18,20,12); doorV(14,16,15); \/\/ midden\r\n  doorV(8,10,26);      doorH(20,22,13);     doorV(16,19,26); \/\/ oost midden\r\n\r\n  doorH(9,11,19);      doorH(19,20,19);     \/\/ centrale hal\r\n\r\n  doorV(19,21,5);      doorH(8,10,24);      doorV(27,29,5); \/\/ zuidwest\r\n  doorV(19,21,26);     doorH(20,22,24);     doorV(27,29,26); \/\/ zuidoost\r\n  doorH(10,12,25);     doorV(27,29,13);     \/\/ berging\r\n  doorH(19,20,25);     doorV(27,29,18);     \/\/ techniek\r\n\r\n  doorV(29,32,5);      doorH(9,10,35);      \/\/ beneden links\r\n  doorV(29,31,15);     doorH(10,12,34);     doorH(18,20,34); \/\/ beneden midden\r\n  doorV(29,31,26);     doorH(20,22,34);     \/\/ beneden rechts\r\n\r\n  \/\/ Een paar kleine variaties per level: extra open deur, nooit een afgesloten zone.\r\n  const optionalDoors=[\r\n    [9,6],[21,6],[9,15],[21,15],[9,26],[21,26],[11,18],[19,21],[11,33],[19,36]\r\n  ];\r\n  shuffle(optionalDoors,rnd).slice(0,2+Math.min(3,Math.floor(level\/2))).forEach(([x,y])=>floor(x,y));\r\n\r\n  const cx=Math.floor(MAZE_W\/2), cy=TUNNEL_ROW;\r\n  \/\/ Centrale zone moet altijd bruikbaar zijn voor de bestaande inbrekers-spawn.\r\n  room(cx-3,cy-2,7,5);\r\n\r\n  \/\/ Alle begaanbare tegels krijgen evidence, behalve de centrale basis.\r\n  for(let y=0;y<MAZE_H;y++) for(let x=0;x<MAZE_W;x++){\r\n    if(grid[y][x]===' ') grid[y][x]='.';\r\n  }\r\n  for(let y=cy-2;y<=cy+2;y++) for(let x=cx-3;x<=cx+3;x++) grid[y][x]=' ';\r\n\r\n  function nearestFloor(tx,ty){\r\n    let best=null,bestD=Infinity;\r\n    for(let y=1;y<MAZE_H-1;y++) for(let x=1;x<MAZE_W-1;x++){\r\n      if(grid[y][x]==='#') continue;\r\n      const d=Math.abs(x-tx)+Math.abs(y-ty);\r\n      if(d<bestD){bestD=d;best={x,y};}\r\n    }\r\n    return best;\r\n  }\r\n\r\n  \/\/ Alarm-orbs verspreid door kamers en gangen.\r\n  const alarmSeeds=[[2,2],[MAZE_W-3,2],[2,MAZE_H-3],[MAZE_W-3,MAZE_H-3],[15,3],[4,13],[26,13],[5,24],[26,24],[15,34]];\r\n  const alarmCells=[];\r\n  for(const [sx,sy] of alarmSeeds){\r\n    const p=nearestFloor(sx,sy);\r\n    if(p && !alarmCells.some(a=>a.x===p.x&&a.y===p.y)){ grid[p.y][p.x]='o'; alarmCells.push(p); }\r\n  }\r\n  const extraAlarmCount=2+Math.min(6,Math.floor(level\/2));\r\n  const candidates=[];\r\n  for(let y=1;y<MAZE_H-1;y++) for(let x=1;x<MAZE_W-1;x++){\r\n    if(grid[y][x]!=='.') continue;\r\n    const dc=Math.abs(x-cx)+Math.abs(y-cy);\r\n    if(dc<6) continue;\r\n    candidates.push({x,y,noise:rnd()});\r\n  }\r\n  for(let n=0;n<extraAlarmCount && candidates.length;n++){\r\n    candidates.sort((a,b)=>{\r\n      const da=alarmCells.length?Math.min(...alarmCells.map(q=>Math.abs(a.x-q.x)+Math.abs(a.y-q.y))):99;\r\n      const db=alarmCells.length?Math.min(...alarmCells.map(q=>Math.abs(b.x-q.x)+Math.abs(b.y-q.y))):99;\r\n      return (db+b.noise*3)-(da+a.noise*3);\r\n    });\r\n    const p=candidates.shift();\r\n    if(!p) break;\r\n    grid[p.y][p.x]='o'; alarmCells.push({x:p.x,y:p.y});\r\n    for(let i=candidates.length-1;i>=0;i--){\r\n      if(Math.abs(candidates[i].x-p.x)+Math.abs(candidates[i].y-p.y)<5) candidates.splice(i,1);\r\n    }\r\n  }\r\n  return grid;\r\n}\r\n\r\nfunction isPassable(col,row){\r\n  if(row===TUNNEL_ROW && (col<0 || col>=MAZE_W)) return true;\r\n  return row>=0 && row<MAZE_H && col>=0 && col<MAZE_W && state.map[row][col]!=='#';\r\n}\r\nfunction canStep(col,row,dir){ return isPassable(col+dir.x,row+dir.y); }\r\nfunction applyWrap(actor){\r\n  if(Math.round(actor.y)!==TUNNEL_ROW) return;\r\n  if(actor.x>=MAZE_W) actor.x=0;\r\n  if(actor.x<=-1) actor.x=MAZE_W-1;\r\n}\r\nfunction nearestPassable(tx,ty){\r\n  let best={x:1,y:1}, bestD=Infinity;\r\n  for(let y=0;y<MAZE_H;y++) for(let x=0;x<MAZE_W;x++){\r\n    if(!isPassable(x,y)) continue;\r\n    const d=Math.abs(x-tx)+Math.abs(y-ty);\r\n    if(d<bestD){bestD=d;best={x,y};}\r\n  }\r\n  return best;\r\n}\r\nfunction chooseInitialDir(p){\r\n  const prefs=[DIRS.right,DIRS.up,DIRS.left,DIRS.down];\r\n  return prefs.find(d=>canStep(p.x,p.y,d)) || DIRS.none;\r\n}\r\n\r\nfunction makeBurglar(index){\r\n  const cx=Math.floor(MAZE_W\/2), cy=TUNNEL_ROW;\r\n  const homes=[{x:cx,y:cy},{x:cx-1,y:cy},{x:cx+1,y:cy},{x:cx,y:cy+1}];\r\n  const colors=['#ff4d6d','#ff9f1c','#c77dff','#35e8a3'];\r\n  const h=homes[index];\r\n  return {x:h.x,y:h.y,home:{...h},dir:index%2?DIRS.left:DIRS.right,desired:DIRS.left,color:colors[index],index,stun:0,flashed:false,respawn:0};\r\n}\r\n\r\nfunction createCameraFeet(player){\r\n  const base=[\r\n    {side:-1,phase:0,anchor:null,from:null,to:null,t:1},\r\n    {side: 1,phase:1,anchor:null,from:null,to:null,t:1},\r\n    {side:-1,phase:2,anchor:null,from:null,to:null,t:1}\r\n  ];\r\n  player.feet=base;\r\n  player.footStep=0;\r\n  player.lastFootTile=`${Math.round(player.x)},${Math.round(player.y)}`;\r\n  retargetCameraFeet(player,true);\r\n}\r\n\r\nfunction localFrameForPlayer(player){\r\n  const f=player.facing&&player.facing!==DIRS.none?player.facing:DIRS.right;\r\n  return {f,perp:{x:-f.y,y:f.x}};\r\n}\r\n\r\nfunction wallAnchorCandidates(player,side){\r\n  const {f,perp}=localFrameForPlayer(player);\r\n  const px=Math.round(player.x), py=Math.round(player.y);\r\n  const out=[];\r\n  for(let oy=-3;oy<=3;oy++) for(let ox=-3;ox<=3;ox++){\r\n    const x=px+ox,y=py+oy;\r\n    if(y<0||y>=MAZE_H||x<0||x>=MAZE_W||state.map[y][x]!=='#') continue;\r\n    const vx=x-player.x,vy=y-player.y;\r\n    const sideScore=vx*perp.x+vy*perp.y;\r\n    const forward=vx*f.x+vy*f.y;\r\n    if(side<0 && sideScore>-.15) continue;\r\n    if(side>0 && sideScore<.15) continue;\r\n    const d=Math.hypot(vx,vy);\r\n    if(d>.15&&d<3.6) out.push({x,y,score:d+Math.abs(forward)*.16-Math.abs(sideScore)*.08});\r\n  }\r\n  out.sort((a,b)=>a.score-b.score);\r\n  return out;\r\n}\r\n\r\nfunction fallbackFootAnchor(player,side,index){\r\n  const {f,perp}=localFrameForPlayer(player);\r\n  const reach=1.05+(index===2?.25:0);\r\n  return {\r\n    x:player.x+perp.x*side*reach-f.x*.15,\r\n    y:player.y+perp.y*side*reach-f.y*.15,\r\n    synthetic:true\r\n  };\r\n}\r\n\r\nfunction retargetCameraFeet(player,immediate=false){\r\n  if(!player.feet) return;\r\n  const used=new Set();\r\n  player.feet.forEach((foot,index)=>{\r\n    const preferredSide=index===2?-((player.footStep%2)?1:-1):foot.side;\r\n    let candidates=wallAnchorCandidates(player,preferredSide);\r\n    if(!candidates.length) candidates=wallAnchorCandidates(player,-preferredSide);\r\n    let next=candidates.find(c=>!used.has(`${c.x},${c.y}`));\r\n    if(next) used.add(`${next.x},${next.y}`);\r\n    else next=fallbackFootAnchor(player,preferredSide,index);\r\n    const current=foot.anchor||next;\r\n    foot.from={x:current.x,y:current.y,synthetic:current.synthetic};\r\n    foot.to={x:next.x,y:next.y,synthetic:next.synthetic};\r\n    foot.anchor=immediate?{...next}:{...current};\r\n    foot.t=immediate?1:0;\r\n  });\r\n}\r\n\r\nfunction updateCameraFeet(player,dt){\r\n  if(!player?.feet) return;\r\n  const tileKey=`${Math.round(player.x)},${Math.round(player.y)}`;\r\n  if(tileKey!==player.lastFootTile && Math.abs(player.x-Math.round(player.x))<.12 && Math.abs(player.y-Math.round(player.y))<.12){\r\n    player.lastFootTile=tileKey; player.footStep++; retargetCameraFeet(player,false);\r\n  }\r\n  player.feet.forEach((foot,index)=>{\r\n    if(foot.t>=1) return;\r\n    \/\/ Elk voetje beweegt net na elkaar: dit geeft een herkenbare driepoot-gang.\r\n    const stagger=index*.12;\r\n    foot.t=clamp(foot.t+dt*4.4-stagger*dt,0,1);\r\n    const t=foot.t<.5?2*foot.t*foot.t:1-Math.pow(-2*foot.t+2,2)\/2;\r\n    foot.anchor={x:foot.from.x+(foot.to.x-foot.from.x)*t,y:foot.from.y+(foot.to.y-foot.from.y)*t,synthetic:foot.to.synthetic};\r\n  });\r\n}\r\n\r\nfunction loadLevel(first=false){\r\n  state.levelSeed=24681357+state.level*7919;\r\n  state.map=generateMaze(state.level);\r\n  const spawn=nearestPassable(2,MAZE_H-4);\r\n  state.map[spawn.y][spawn.x]=' ';\r\n  const firstDir=chooseInitialDir(spawn);\r\n  state.player={x:spawn.x,y:spawn.y,dir:DIRS.none,desired:DIRS.none,facing:firstDir===DIRS.none?DIRS.right:firstDir,route:[]};\r\n  createCameraFeet(state.player);\r\n  state.burglars=[0,1,2,3].map(makeBurglar);\r\n  state.dots=0;\r\n  for(const row of state.map) for(const cell of row) if(cell==='.' || cell==='o') state.dots++;\r\n  state.initialDots=state.dots; state.collected=0; state.exposed=0; state.exposedCombo=0;\r\n  state.flashCooldown=first?0:.8; state.flashFx=0; state.invuln=1.5; state.bonus=null;\r\n  state.bonusMilestones=[Math.floor(state.initialDots*.30),Math.floor(state.initialDots*.67)];\r\n  state.modeTimer=18; state.chaseMode=true; state.sentry=false; state.sentryPending=false; state.sentryTurnTimer=0; state.sentryHold=0; state.sentryDetected=false; state.sentryScanPulse=0; state.sentryResumeDir=null; state.manualMode=false; state.flashHeld=false; state.flashTurnCooldown=0; state.flashResumeDir=null; state.flashResumeDesired=null; state.flashHoldTagged=[false,false,false,false]; state.huntTarget=null;\r\n  if(sentryTouch) sentryTouch.classList.remove('active');\r\n  state.banner=first?'READY!':`LEVEL ${state.level}`; state.bannerTime=1.4;\r\n  centerView(true); updateHUD();\r\n}\r\n\r\nfunction startGame(){\r\n  ensureAudio();\r\n  state.name=(nameInput.value.trim().toUpperCase()||'SECURITY').slice(0,12);\r\n  state.score=0; state.level=1; state.lives=3; state.flashCount=0; state.paused=false; state.phase='ready'; state.readyTimer=1.15;\r\n  loadLevel(true);\r\n  overlay.classList.add('hidden');\r\n  nameInput.blur(); startBtn.blur(); root.focus();\r\n  statusLine.textContent=`Operator ${state.name} actief \u2014 NIGHT WATCH gestart.`;\r\n  state.lastTime=performance.now();\r\n}\r\n\r\nfunction playerCentered(p=state.player){\r\n  return !!p && Math.abs(p.x-Math.round(p.x))<.001 && Math.abs(p.y-Math.round(p.y))<.001;\r\n}\r\n\r\nfunction directionIndex(dir){\r\n  const order=[DIRS.right,DIRS.down,DIRS.left,DIRS.up];\r\n  const i=order.findIndex(d=>d===dir || (d.x===dir?.x && d.y===dir?.y));\r\n  return i<0?0:i;\r\n}\r\n\r\nfunction activateSentryNow(){\r\n  if(!state.player) return;\r\n  const p=state.player;\r\n  p.x=Math.round(p.x); p.y=Math.round(p.y);\r\n  state.sentryResumeDir=(p.dir&&p.dir!==DIRS.none)?p.dir:(p.facing||DIRS.right);\r\n  p.dir=DIRS.none; p.desired=DIRS.none;\r\n  state.sentry=true; state.sentryPending=false; state.sentryTurnTimer=.18; state.sentryHold=0; state.sentryDetected=false; state.sentryScanPulse=0;\r\n  \/\/ Een wachtende beveiligingscamera moet vrijwel meteen paraat zijn.\r\n  state.flashCooldown=Math.min(state.flashCooldown,.75);\r\n  state.banner='SENTRY ONLINE'; state.bannerTime=1;\r\n  statusLine.textContent='SENTRY actief \u2014 360\u00b0 scan. Volledige zichtbare gang wordt bewaakt.';\r\n  if(sentryTouch) sentryTouch.classList.add('active');\r\n}\r\n\r\nfunction deactivateSentry(moveDir=null){\r\n  if(!state.player) return;\r\n  state.sentry=false; state.sentryPending=false; state.sentryDetected=false; state.sentryHold=0;\r\n  if(sentryTouch) sentryTouch.classList.remove('active');\r\n  const d=moveDir || state.sentryResumeDir || state.player.facing || DIRS.right;\r\n  state.player.desired=d; state.player.dir=d; state.player.facing=d;\r\n  statusLine.textContent=`Operator ${state.name} actief \u2014 mobiele camera.`;\r\n}\r\n\r\nfunction toggleSentry(){\r\n  if(!state.player || state.phase!=='playing' || state.paused) return;\r\n  if(state.sentry || state.sentryPending){\r\n    deactivateSentry();\r\n    state.banner='SENTRY OFF'; state.bannerTime=.7;\r\n    return;\r\n  }\r\n  if(playerCentered()) activateSentryNow();\r\n  else {\r\n    state.sentryPending=true;\r\n    state.sentryResumeDir=state.player.dir&&state.player.dir!==DIRS.none?state.player.dir:state.player.facing;\r\n    state.banner='SENTRY ARMING'; state.bannerTime=.8;\r\n    statusLine.textContent='SENTRY wordt geactiveerd op het eerstvolgende maze-blok\u2026';\r\n    if(sentryTouch) sentryTouch.classList.add('active');\r\n  }\r\n}\r\n\r\nfunction corridorRangeTiles(player=state.player,facing=player?.facing||DIRS.right){\r\n  if(!player) return 0;\r\n  const sx=Math.round(player.x), sy=Math.round(player.y);\r\n  let x=sx,y=sy,range=0;\r\n  const max=Math.max(MAZE_W,MAZE_H)+2;\r\n  for(let i=0;i<max;i++){\r\n    let nx=x+facing.x, ny=y+facing.y;\r\n    \/\/ Voor de zijtunnel scannen we tot aan de rand; we tekenen\/detecteren niet oneindig rond de wrap.\r\n    if(nx<0||nx>=MAZE_W||ny<0||ny>=MAZE_H) break;\r\n    if(!isPassable(nx,ny)) break;\r\n    range++; x=nx; y=ny;\r\n  }\r\n  return range+.45;\r\n}\r\n\r\nfunction burglarInSentryCorridor(b){\r\n  if(!state.sentry || !state.player || b.respawn>0) return false;\r\n  const p=state.player, f=p.facing||DIRS.right;\r\n  const vx=b.x-p.x, vy=b.y-p.y;\r\n  const forward=vx*f.x+vy*f.y;\r\n  const side=Math.abs(vx*(-f.y)+vy*f.x);\r\n  if(forward<=.20 || side>.42) return false;\r\n  const maxRange=corridorRangeTiles(p,f);\r\n  if(forward>maxRange+.35) return false;\r\n  return lineClear(p.x,p.y,b.x,b.y);\r\n}\r\n\r\nfunction updateSentry(dt){\r\n  if(!state.sentry || !state.player) return;\r\n  const p=state.player;\r\n  p.dir=DIRS.none; p.desired=DIRS.none;\r\n  state.sentryScanPulse+=dt;\r\n  state.sentryHold=Math.max(0,state.sentryHold-dt);\r\n\r\n  const visible=state.burglars.filter(burglarInSentryCorridor);\r\n  state.sentryDetected=visible.length>0;\r\n\r\n  if(visible.length){\r\n    state.sentryHold=Math.max(state.sentryHold,.75);\r\n    if(state.flashCooldown<=0){\r\n      state.banner='INTRUDER DETECTED'; state.bannerTime=.75;\r\n      useFlash(true);\r\n    }\r\n  }else if(state.sentryHold<=0){\r\n    state.sentryTurnTimer-=dt;\r\n    if(state.sentryTurnTimer<=0){\r\n      const order=[DIRS.right,DIRS.down,DIRS.left,DIRS.up];\r\n      p.facing=order[(directionIndex(p.facing)+1)%4];\r\n      state.sentryTurnTimer=.82;\r\n    }\r\n  }\r\n}\r\n\r\n\r\nfunction findNavigationPath(sx,sy,tx,ty){\r\n  sx=Math.round(sx); sy=Math.round(sy); tx=Math.round(tx); ty=Math.round(ty);\r\n  if(!isPassable(sx,sy) || !isPassable(tx,ty)) return [];\r\n  const size=MAZE_W*MAZE_H, prev=new Int32Array(size); prev.fill(-1);\r\n  const qx=new Int16Array(size), qy=new Int16Array(size);\r\n  const key=(x,y)=>y*MAZE_W+x;\r\n  let head=0,tail=0;\r\n  qx[tail]=sx; qy[tail]=sy; tail++; prev[key(sx,sy)]=key(sx,sy);\r\n  while(head<tail){\r\n    const x=qx[head],y=qy[head++];\r\n    if(x===tx && y===ty) break;\r\n    for(const d of CARDINALS){\r\n      const nx=x+d.x,ny=y+d.y;\r\n      if(nx<0||nx>=MAZE_W||ny<0||ny>=MAZE_H||!isPassable(nx,ny)) continue;\r\n      const nk=key(nx,ny); if(prev[nk]!==-1) continue;\r\n      prev[nk]=key(x,y); qx[tail]=nx; qy[tail]=ny; tail++;\r\n    }\r\n  }\r\n  const end=key(tx,ty); if(prev[end]===-1) return [];\r\n  const path=[]; let k=end;\r\n  while(true){\r\n    const x=k%MAZE_W,y=Math.floor(k\/MAZE_W); path.push({x,y});\r\n    if(k===prev[k]) break;\r\n    k=prev[k];\r\n  }\r\n  path.reverse(); return path;\r\n}\r\n\r\nfunction setNavigationTarget(col,row){\r\n  if(!state.player || !['ready','playing'].includes(state.phase) || state.paused) return;\r\n  const target=nearestPassable(clamp(col,0,MAZE_W-1),clamp(row,0,MAZE_H-1));\r\n  const p=state.player;\r\n  const start=nearestPassable(Math.round(p.x),Math.round(p.y));\r\n  const path=findNavigationPath(start.x,start.y,target.x,target.y);\r\n  if(!path.length){ state.banner='NO ROUTE'; state.bannerTime=.8; return; }\r\n  if(state.sentry || state.sentryPending){\r\n    state.sentry=false; state.sentryPending=false; state.sentryDetected=false; state.sentryHold=0;\r\n    if(sentryTouch) sentryTouch.classList.remove('active');\r\n  }\r\n  p.route=path;\r\n  state.manualMode=false;\r\n  state.huntTarget=null;\r\n  state.navTarget={x:target.x,y:target.y};\r\n  state.banner='ROUTE SET'; state.bannerTime=.45;\r\n  statusLine.textContent=`Nieuwe bestemming: ${target.x},${target.y} \u2014 camera navigeert automatisch.`;\r\n}\r\n\r\nfunction updatePlayerNavigation(dt){\r\n  const p=state.player;\r\n  if(!p) return;\r\n  if(!Array.isArray(p.route) || !p.route.length){\r\n    p.dir=DIRS.none; p.desired=DIRS.none; return;\r\n  }\r\n\r\n  \/\/ Verwijder elk routepunt dat werkelijk bereikt is.\r\n  while(p.route.length){\r\n    const node=p.route[0];\r\n    if(Math.abs(p.x-node.x)<.025 && Math.abs(p.y-node.y)<.025){\r\n      p.x=node.x; p.y=node.y; p.route.shift();\r\n    }else break;\r\n  }\r\n  if(!p.route.length){\r\n    p.dir=DIRS.none; p.desired=DIRS.none;\r\n    state.banner='DESTINATION'; state.bannerTime=.6;\r\n    statusLine.textContent=`Bestemming bereikt \u2014 klik ergens anders voor een nieuwe route.`;\r\n    return;\r\n  }\r\n\r\n  const next=p.route[0], dx=next.x-p.x, dy=next.y-p.y;\r\n  let d=DIRS.none;\r\n  if(Math.abs(dx)>.025) d=dx>0?DIRS.right:DIRS.left;\r\n  else if(Math.abs(dy)>.025) d=dy>0?DIRS.down:DIRS.up;\r\n  p.desired=d;\r\n  if(p.dir===DIRS.none || reverseOf(d,p.dir)){ p.dir=d; p.facing=d; }\r\n  moveActor(p,4.75,dt,true);\r\n}\r\n\r\nfunction cancelSentryForDirectControl(){\r\n  if(!state.sentry && !state.sentryPending) return;\r\n  state.sentry=false; state.sentryPending=false; state.sentryDetected=false; state.sentryHold=0;\r\n  if(sentryTouch) sentryTouch.classList.remove('active');\r\n}\r\n\r\nfunction beginManualMove(dir){\r\n  if(!state.player || !['ready','playing'].includes(state.phase) || state.paused || state.flashHeld) return;\r\n  cancelSentryForDirectControl();\r\n  const p=state.player;\r\n  state.manualMode=true;\r\n  state.huntTarget=null;\r\n  state.navTarget=null;\r\n  p.route=[];\r\n  p.desired=dir;\r\n  if(p.dir===DIRS.none || reverseOf(dir,p.dir)) p.dir=dir;\r\n  p.facing=dir;\r\n  state.banner='MANUAL'; state.bannerTime=.35;\r\n  statusLine.textContent='Manuele besturing \u2014 klik in het gebouw om automatische navigatie opnieuw te starten.';\r\n}\r\n\r\nfunction rotateFacing(step){\r\n  if(!state.player) return;\r\n  const order=[DIRS.right,DIRS.down,DIRS.left,DIRS.up];\r\n  const i=directionIndex(state.player.facing||DIRS.right);\r\n  state.player.facing=order[(i+step+4)%4];\r\n}\r\n\r\nfunction markBurglarFlashed(b){\r\n  if(!b || b.respawn>0) return false;\r\n  const first=!b.flashed;\r\n  b.flashed=true;\r\n  b.stun=0;\r\n  b.dir=DIRS.none;\r\n  b.desired=DIRS.none;\r\n  return first;\r\n}\r\n\r\nfunction registerSuccessfulFlash(){\r\n  if(state.phase!=='playing') return;\r\n  state.flashCount=Math.min(state.flashGoal,state.flashCount+1);\r\n  updateHUD();\r\n  if(state.flashCount>=state.flashGoal) missionComplete();\r\n}\r\n\r\nfunction flashedBurglars(){\r\n  return state.burglars.filter(b=>b.flashed && b.respawn<=0);\r\n}\r\n\r\nfunction setHuntRouteTo(b){\r\n  if(!state.player || !b || !b.flashed || b.respawn>0) return false;\r\n  const p=state.player;\r\n  const start=nearestPassable(Math.round(p.x),Math.round(p.y));\r\n  const target=nearestPassable(Math.round(b.x),Math.round(b.y));\r\n  const path=findNavigationPath(start.x,start.y,target.x,target.y);\r\n  if(!path.length) return false;\r\n  p.route=path;\r\n  p.dir=DIRS.none;\r\n  p.desired=DIRS.none;\r\n  state.manualMode=false;\r\n  state.huntTarget=b.index;\r\n  state.navTarget={x:target.x,y:target.y};\r\n  statusLine.textContent='Geflashte inbreker gelokaliseerd \u2014 camera gaat hem automatisch pakken.';\r\n  return true;\r\n}\r\n\r\nfunction startFlashedHunt(){\r\n  if(!state.player) return false;\r\n  const targets=flashedBurglars();\r\n  if(!targets.length){\r\n    state.huntTarget=null;\r\n    state.navTarget=null;\r\n    state.player.route=[];\r\n    state.player.dir=DIRS.none;\r\n    state.player.desired=DIRS.none;\r\n    return false;\r\n  }\r\n  targets.sort((a,b)=>shortestMazeDistance(state.player.x,state.player.y,a.x,a.y)-shortestMazeDistance(state.player.x,state.player.y,b.x,b.y));\r\n  return setHuntRouteTo(targets[0]);\r\n}\r\n\r\nfunction finishFlashedHuntIfNeeded(){\r\n  if(startFlashedHunt()) return;\r\n  state.banner='AWAITING INPUT'; state.bannerTime=.55;\r\n  statusLine.textContent='Camera staat stil \u2014 gebruik een pijltje of klik\/tik ergens in het gebouw.';\r\n}\r\n\r\nfunction beginFlashHold(){\r\n  if(!state.player || state.phase!=='playing' || state.paused || state.flashHeld) return;\r\n  cancelSentryForDirectControl();\r\n  const p=state.player;\r\n  state.flashHeld=true;\r\n  state.flashTurnCooldown=0;\r\n  state.flashResumeDir=p.dir;\r\n  state.flashResumeDesired=p.desired;\r\n  state.flashHoldTagged=[false,false,false,false];\r\n  p.dir=DIRS.none; p.desired=DIRS.none;\r\n  state.flashCooldown=0;\r\n  state.flashFx=1; state.flashPulse=0;\r\n  sfx('flash');\r\n  updateHeldFlashTargets();\r\n  state.banner='FLASH HOLD'; state.bannerTime=.55;\r\n  statusLine.textContent='FLASH actief \u2014 camera staat stil. Draai met \u2190 en \u2192; laat SPATIE los om verder te gaan.';\r\n}\r\n\r\nfunction updateHeldFlashTargets(){\r\n  if(!state.flashHeld || !state.player) return;\r\n  const p=state.player, f=p.facing||DIRS.right, maxRange=5.4;\r\n  for(const b of state.burglars){\r\n    if(b.respawn>0) continue;\r\n    const vx=b.x-p.x, vy=b.y-p.y, r=Math.hypot(vx,vy);\r\n    if(r<.01 || r>maxRange) continue;\r\n    const dot=(vx\/r)*f.x+(vy\/r)*f.y;\r\n    if(dot<=.74 || !lineClear(p.x,p.y,b.x,b.y)) continue;\r\n    const firstFlash=markBurglarFlashed(b);\r\n    if(!state.flashHoldTagged[b.index]){\r\n      state.flashHoldTagged[b.index]=true;\r\n      if(firstFlash){ state.score+=125; registerSuccessfulFlash(); }\r\n      sfx('stun'); state.shake=.08;\r\n      if(state.phase==='playing'){ state.banner='INTRUDER FLASHED'; state.bannerTime=.45; }\r\n    }\r\n  }\r\n}\r\n\r\nfunction endFlashHold(){\r\n  if(!state.flashHeld || !state.player) return;\r\n  const p=state.player;\r\n  state.flashHeld=false;\r\n  state.flashFx=.24;\r\n  state.flashCooldown=5.5;\r\n  state.flashTurnCooldown=0;\r\n\r\n  \/\/ Na het loslaten vertrekt de camera nooit opnieuw op de oude route.\r\n  \/\/ Alleen een geflashte inbreker mag automatisch een nieuwe route starten.\r\n  p.dir=DIRS.none;\r\n  p.desired=DIRS.none;\r\n  p.route=[];\r\n  state.manualMode=false;\r\n  state.navTarget=null;\r\n  state.huntTarget=null;\r\n  state.flashResumeDir=null;\r\n  state.flashResumeDesired=null;\r\n\r\n  if(flashedBurglars().length){\r\n    state.banner='AUTO HUNT'; state.bannerTime=.65;\r\n    if(!startFlashedHunt()) finishFlashedHuntIfNeeded();\r\n  }else{\r\n    state.banner='AWAITING INPUT'; state.bannerTime=.55;\r\n    statusLine.textContent='Camera staat stil \u2014 gebruik een pijltje of klik\/tik ergens in het gebouw.';\r\n  }\r\n}\r\nfunction turnDuringFlash(step){\r\n  if(!state.flashHeld || state.flashTurnCooldown>0) return;\r\n  rotateFacing(step);\r\n  state.flashTurnCooldown=.14;\r\n  updateHeldFlashTargets();\r\n}\r\n\r\nfunction setDesired(dir){\r\n  if(!state.player || !['ready','playing'].includes(state.phase) || state.paused) return;\r\n  if(state.sentry || state.sentryPending){\r\n    deactivateSentry(dir);\r\n    return;\r\n  }\r\n  state.player.desired=dir;\r\n  if(reverseOf(dir,state.player.dir)){\r\n    state.player.dir=dir; state.player.facing=dir;\r\n  }\r\n}\r\n\r\nfunction moveActor(actor,speed,dt,isPlayer=false){\r\n  if(!actor || actor.dir===DIRS.none && actor.desired===DIRS.none) return;\r\n  if(actor.desired && reverseOf(actor.desired,actor.dir)){\r\n    actor.dir=actor.desired;\r\n    if(isPlayer) actor.facing=actor.dir;\r\n  }\r\n  let remaining=speed*dt, loops=0;\r\n  while(remaining>0.00001 && loops++<6){\r\n    applyWrap(actor);\r\n    const rx=Math.round(actor.x), ry=Math.round(actor.y);\r\n    const centered=Math.abs(actor.x-rx)<0.0001 && Math.abs(actor.y-ry)<0.0001;\r\n    if(centered){\r\n      actor.x=rx; actor.y=ry;\r\n      if(actor.desired && actor.desired!==DIRS.none && canStep(rx,ry,actor.desired)) actor.dir=actor.desired;\r\n      if(!actor.dir || actor.dir===DIRS.none || !canStep(rx,ry,actor.dir)){ actor.dir=DIRS.none; break; }\r\n      if(isPlayer) actor.facing=actor.dir;\r\n    }\r\n    if(actor.dir===DIRS.none) break;\r\n    let nx=actor.x, ny=actor.y;\r\n    if(actor.dir.x>0) nx=Math.floor(actor.x+1e-7)+1;\r\n    else if(actor.dir.x<0) nx=Math.ceil(actor.x-1e-7)-1;\r\n    else if(actor.dir.y>0) ny=Math.floor(actor.y+1e-7)+1;\r\n    else ny=Math.ceil(actor.y-1e-7)-1;\r\n    let toCenter=Math.abs(nx-actor.x)+Math.abs(ny-actor.y);\r\n    if(toCenter<0.000001) toCenter=1;\r\n    const step=Math.min(remaining,toCenter);\r\n    actor.x+=actor.dir.x*step; actor.y+=actor.dir.y*step; remaining-=step;\r\n    if(step>=toCenter-0.000001){ actor.x=nx; actor.y=ny; applyWrap(actor); }\r\n  }\r\n}\r\n\r\nfunction availableDirs(b){\r\n  const x=Math.round(b.x), y=Math.round(b.y);\r\n  const all=CARDINALS.filter(d=>canStep(x,y,d));\r\n  const noReverse=all.filter(d=>!reverseOf(d,b.dir));\r\n  return noReverse.length?noReverse:all;\r\n}\r\nfunction burglarTarget(b){\r\n  const p=state.player, f=p.facing||DIRS.right, perp={x:-f.y,y:f.x};\r\n  if(!state.chaseMode){\r\n    \/\/ Ze blijven zelfs tijdens de korte patrolfase dicht in de buurt van de speler.\r\n    const offsets=[{x:-3,y:-3},{x:3,y:-3},{x:-3,y:3},{x:3,y:3}];\r\n    const o=offsets[b.index];\r\n    return nearestPassable(Math.round(p.x+o.x),Math.round(p.y+o.y));\r\n  }\r\n  if(b.index===0) return nearestPassable(Math.round(p.x),Math.round(p.y));\r\n  if(b.index===1) return nearestPassable(Math.round(p.x+f.x*4),Math.round(p.y+f.y*4));\r\n  if(b.index===2) return nearestPassable(Math.round(p.x+f.x*2+perp.x*3),Math.round(p.y+f.y*2+perp.y*3));\r\n  \/\/ De vierde inbreker probeert verder voor de camera af te snijden.\r\n  return nearestPassable(Math.round(p.x+f.x*6-perp.x*2),Math.round(p.y+f.y*6-perp.y*2));\r\n}\r\n\r\nfunction shortestMazeDistance(sx,sy,tx,ty,maxDepth=1800){\r\n  sx=Math.round(sx);sy=Math.round(sy);tx=Math.round(tx);ty=Math.round(ty);\r\n  if(sx===tx&&sy===ty) return 0;\r\n  const key=(x,y)=>y*MAZE_W+x;\r\n  const qx=new Int16Array(MAZE_W*MAZE_H), qy=new Int16Array(MAZE_W*MAZE_H), qd=new Int16Array(MAZE_W*MAZE_H);\r\n  const seen=new Uint8Array(MAZE_W*MAZE_H);\r\n  let head=0,tail=0;\r\n  if(sy<0||sy>=MAZE_H) return 9999;\r\n  sx=(sx+MAZE_W)%MAZE_W;\r\n  qx[tail]=sx;qy[tail]=sy;qd[tail]=0;tail++;seen[key(sx,sy)]=1;\r\n  while(head<tail && head<maxDepth){\r\n    const x=qx[head],y=qy[head],d=qd[head++];\r\n    for(const dir of CARDINALS){\r\n      let nx=x+dir.x,ny=y+dir.y;\r\n      if(y===TUNNEL_ROW){ if(nx<0) nx=MAZE_W-1; else if(nx>=MAZE_W) nx=0; }\r\n      if(nx<0||nx>=MAZE_W||ny<0||ny>=MAZE_H||!isPassable(nx,ny)) continue;\r\n      if(nx===tx&&ny===ty) return d+1;\r\n      const k=key(nx,ny); if(seen[k]) continue; seen[k]=1; qx[tail]=nx;qy[tail]=ny;qd[tail]=d+1;tail++;\r\n    }\r\n  }\r\n  return 9999;\r\n}\r\n\r\nfunction chooseBurglarDir(b){\r\n  const opts=availableDirs(b); if(!opts.length){ b.desired=DIRS.none; return; }\r\n  if(state.exposed>0){\r\n    opts.sort((a,c)=>{\r\n      const da=shortestMazeDistance(b.x+a.x,b.y+a.y,state.player.x,state.player.y);\r\n      const dc=shortestMazeDistance(b.x+c.x,b.y+c.y,state.player.x,state.player.y);\r\n      return dc-da;\r\n    });\r\n    b.desired=opts[Math.random()<.08?Math.floor(Math.random()*opts.length):0]; return;\r\n  }\r\n  const t=burglarTarget(b);\r\n  opts.sort((a,c)=>{\r\n    const da=shortestMazeDistance(b.x+a.x,b.y+a.y,t.x,t.y)+Math.random()*.03;\r\n    const dc=shortestMazeDistance(b.x+c.x,b.y+c.y,t.x,t.y)+Math.random()*.03;\r\n    return da-dc;\r\n  });\r\n  b.desired=opts[0];\r\n}\r\nfunction updateBurglars(dt){\r\n  for(const b of state.burglars){\r\n    if(b.respawn>0){ b.respawn-=dt; continue; }\r\n    if(b.flashed){ b.dir=DIRS.none; b.desired=DIRS.none; continue; }\r\n    if(b.stun>0){ b.stun-=dt; continue; }\r\n    const rx=Math.round(b.x), ry=Math.round(b.y);\r\n    if(Math.abs(b.x-rx)<.001 && Math.abs(b.y-ry)<.001){ b.x=rx; b.y=ry; chooseBurglarDir(b); }\r\n    const base=3.48+Math.min(1.65,(state.level-1)*.14);\r\n    moveActor(b,state.exposed>0?base*.72:base,dt,false);\r\n  }\r\n}\r\n\r\nfunction collectTile(){\r\n  const p=state.player, x=Math.round(p.x), y=Math.round(p.y);\r\n  if(Math.abs(p.x-x)>.13 || Math.abs(p.y-y)>.13) return;\r\n  const cell=state.map[y]?.[x];\r\n  if(cell==='.'){\r\n    state.map[y][x]=' '; state.score+=10; state.dots--; state.collected++; sfx('dot'); checkBonusSpawn();\r\n  }else if(cell==='o'){\r\n    state.map[y][x]=' '; state.score+=50; state.dots--; state.collected++; state.exposed=7.5; state.exposedCombo=0;\r\n    state.banner='ALARM MODE'; state.bannerTime=1; sfx('orb'); checkBonusSpawn();\r\n  }\r\n  if(state.dots<=0 && state.phase==='playing'){\r\n    state.phase='levelclear'; state.banner='SECTOR CLEARED'; state.bannerTime=2; sfx('level');\r\n    setTimeout(()=>{\r\n      if(state.phase==='levelclear'){\r\n        state.level++; state.phase='ready'; state.readyTimer=1; loadLevel(false);\r\n      }\r\n    },1450);\r\n  }\r\n}\r\n\r\nfunction checkBonusSpawn(){\r\n  if(state.bonus || !state.bonusMilestones.length || state.collected<state.bonusMilestones[0]) return;\r\n  state.bonusMilestones.shift();\r\n  const cells=[];\r\n  for(let y=1;y<MAZE_H-1;y++) for(let x=1;x<MAZE_W-1;x++){\r\n    if(isPassable(x,y) && state.map[y][x]!=='o' && Math.hypot(x-state.player.x,y-state.player.y)>7) cells.push({x,y});\r\n  }\r\n  if(cells.length){ state.bonus={...cells[Math.floor(Math.random()*cells.length)],time:8,pulse:0}; state.banner='EVIDENCE DROP'; state.bannerTime=.9; }\r\n}\r\nfunction updateBonus(dt){\r\n  if(!state.bonus) return;\r\n  state.bonus.time-=dt; state.bonus.pulse+=dt;\r\n  if(dist(state.player,state.bonus)<.55){\r\n    const value=500*state.level; state.score+=value; state.banner=`+${value} EVIDENCE`; state.bannerTime=1; sfx('bonus'); state.bonus=null; return;\r\n  }\r\n  if(state.bonus && state.bonus.time<=0) state.bonus=null;\r\n}\r\n\r\nfunction lineClear(x1,y1,x2,y2){\r\n  const d=Math.hypot(x2-x1,y2-y1), steps=Math.max(1,Math.ceil(d\/.16));\r\n  for(let i=1;i<steps;i++){\r\n    const t=i\/steps, x=x1+(x2-x1)*t, y=y1+(y2-y1)*t;\r\n    if(!isPassable(Math.round(x),Math.round(y))) return false;\r\n  }\r\n  return true;\r\n}\r\nfunction burglarVisibleInFacingCorridor(b,maxRange){\r\n  const p=state.player, f=p.facing||DIRS.right;\r\n  const vx=b.x-p.x, vy=b.y-p.y;\r\n  const forward=vx*f.x+vy*f.y;\r\n  const side=Math.abs(vx*(-f.y)+vy*f.x);\r\n  return forward>.15 && forward<=maxRange+.35 && side<.48 && lineClear(p.x,p.y,b.x,b.y);\r\n}\r\nfunction useFlash(auto=false){\r\n  if(state.phase!=='playing' || state.paused || state.flashCooldown>0) return false;\r\n  const p=state.player, fullCorridor=state.sentry || auto;\r\n  const maxRange=fullCorridor?corridorRangeTiles(p,p.facing||DIRS.right):5.4;\r\n  state.flashCooldown=fullCorridor?3.6:5.5; state.flashFx=.92; state.flashPulse=0; sfx('flash');\r\n  let hits=0;\r\n  for(const b of state.burglars){\r\n    if(b.respawn>0) continue;\r\n    let visible=false;\r\n    if(fullCorridor){\r\n      visible=burglarVisibleInFacingCorridor(b,maxRange);\r\n    }else{\r\n      const f=p.facing||DIRS.right, vx=b.x-p.x, vy=b.y-p.y, r=Math.hypot(vx,vy);\r\n      if(r>=.01 && r<=maxRange){\r\n        const dot=(vx\/r)*f.x+(vy\/r)*f.y;\r\n        visible=dot>.74 && lineClear(p.x,p.y,b.x,b.y);\r\n      }\r\n    }\r\n    if(visible){\r\n      const firstFlash=markBurglarFlashed(b); if(firstFlash){ state.score+=auto?175:125; registerSuccessfulFlash(); } hits++; sfx('stun');\r\n    }\r\n  }\r\n  if(hits){\r\n    state.banner=auto?`AUTO FLASH \u00d7${hits}`:`FLASH HIT \u00d7${hits}`; state.bannerTime=.8; state.shake=.15;\r\n    if(auto && !state.flashHeld){\r\n      state.sentry=false; state.sentryPending=false; state.sentryDetected=false; state.sentryHold=0;\r\n      if(sentryTouch) sentryTouch.classList.remove('active');\r\n      startFlashedHunt();\r\n    }\r\n  }\r\n  return true;\r\n}\r\n\r\nfunction resetPositions(){\r\n  const spawn=nearestPassable(2,MAZE_H-4), d=chooseInitialDir(spawn);\r\n  state.player={x:spawn.x,y:spawn.y,dir:DIRS.none,desired:DIRS.none,facing:d===DIRS.none?DIRS.right:d,route:[]};\r\n  createCameraFeet(state.player);\r\n  state.burglars=[0,1,2,3].map(makeBurglar); state.invuln=1.6; state.exposed=0; state.exposedCombo=0; state.sentry=false; state.sentryPending=false; state.sentryDetected=false; state.manualMode=false; state.flashHeld=false; state.flashTurnCooldown=0; state.flashResumeDir=null; state.flashResumeDesired=null; state.flashHoldTagged=[false,false,false,false]; state.huntTarget=null; if(sentryTouch)sentryTouch.classList.remove('active'); centerView(true);\r\n}\r\nfunction collisions(){\r\n  if(state.invuln>0) return;\r\n  for(const b of state.burglars){\r\n    if(b.respawn>0 || dist(state.player,b)>=.54) continue;\r\n    if(b.flashed){\r\n      const gain=250;\r\n      state.score+=gain;\r\n      b.flashed=false;\r\n      b.stun=0;\r\n      b.x=b.home.x; b.y=b.home.y; b.dir=DIRS.up; b.desired=DIRS.up; b.respawn=.85;\r\n      state.banner=`INTRUDER EATEN +${gain}`; state.bannerTime=.85; state.shake=.14; sfx('capture');\r\n      state.huntTarget=null;\r\n      state.navTarget=null;\r\n      state.player.route=[];\r\n      state.player.dir=DIRS.none;\r\n      state.player.desired=DIRS.none;\r\n      finishFlashedHuntIfNeeded();\r\n    }else if(state.exposed>0){\r\n      const gain=200*Math.pow(2,Math.min(3,state.exposedCombo++)); state.score+=gain;\r\n      b.x=b.home.x; b.y=b.home.y; b.dir=DIRS.up; b.desired=DIRS.up; b.respawn=.7; b.stun=0; b.flashed=false;\r\n      state.banner=`INTRUDER TAGGED +${gain}`; state.bannerTime=.8; state.shake=.14; sfx('capture');\r\n    }else if(b.stun<=0){ loseLife(); }\r\n    break;\r\n  }\r\n}\r\nfunction loseLife(){\r\n  state.lives--; state.shake=.4; sfx('hit'); updateHUD();\r\n  if(state.lives<=0){ gameOver(); return; }\r\n  state.phase='hit'; state.banner='CAMERA OFFLINE'; state.bannerTime=1.1;\r\n  setTimeout(()=>{\r\n    if(state.phase==='hit'){\r\n      resetPositions(); state.phase='ready'; state.readyTimer=.9; state.banner='BACKUP CAMERA'; state.bannerTime=.9;\r\n    }\r\n  },850);\r\n}\r\nfunction missionComplete(){\r\n  if(state.phase!=='playing') return;\r\n  state.phase='complete';\r\n  state.flashHeld=false; state.flashFx=0;\r\n  state.sentry=false; state.sentryPending=false; state.sentryDetected=false;\r\n  state.huntTarget=null; state.navTarget=null;\r\n  if(state.player){ state.player.route=[]; state.player.dir=DIRS.none; state.player.desired=DIRS.none; }\r\n  saveScore(state.name,state.score,state.level); renderScores(); updateHUD();\r\n  sfx('level');\r\n  overlayEyebrow.textContent='MISSION COMPLETE \/\/ 5 \/ 5';\r\n  overlayTitle.innerHTML='<span class=\"white\">DOEL<\/span> <b>BEHAALD<\/b>';\r\n  overlayText.innerHTML=`Je hebt <b>${state.flashGoal} inbrekers geflasht<\/b>. De bewakingsronde is voltooid.`;\r\n  introTips.style.display='none'; startBtn.textContent='\u21bb OPNIEUW SPELEN'; overlay.classList.remove('hidden');\r\n  statusLine.textContent=`Missie voltooid \u2014 ${state.flashCount}\/${state.flashGoal} inbrekers geflasht.`;\r\n}\r\n\r\nfunction gameOver(){\r\n  state.phase='gameover'; saveScore(state.name,state.score,state.level); renderScores(); updateHUD();\r\n  overlayEyebrow.textContent='SHIFT TERMINATED';\r\n  overlayTitle.innerHTML='<span class=\"white\">CCTV<\/span> <b>LOCKDOWN<\/b>';\r\n  overlayText.innerHTML=`Operator <b>${escapeHtml(state.name)}<\/b> behaalde <b>${formatScore(state.score)}<\/b> punten en bereikte level <b>${state.level}<\/b>.`;\r\n  introTips.style.display='none'; startBtn.textContent='\u21bb OPNIEUW SPELEN'; overlay.classList.remove('hidden');\r\n  statusLine.textContent=`Shift be\u00ebindigd \u2014 ${formatScore(state.score)} punten.`;\r\n}\r\n\r\nfunction centerView(immediate=false,dt=.016){\r\n  if(!state.player) return;\r\n  const px=(state.player.x+.5)*TILE, py=(state.player.y+.5)*TILE;\r\n  const tx=clamp(px-VIEW_W\/2,0,Math.max(0,WORLD_W-VIEW_W));\r\n  const ty=clamp(py-VIEW_H\/2,0,Math.max(0,WORLD_H-VIEW_H));\r\n  if(immediate){ state.viewX=tx; state.viewY=ty; return; }\r\n  const k=1-Math.pow(.0008,dt);\r\n  state.viewX+=(tx-state.viewX)*k; state.viewY+=(ty-state.viewY)*k;\r\n}\r\n\r\nfunction updateHUD(){\r\n  scoreEl.textContent=formatScore(state.score);\r\n  const scores=getScores(); highEl.textContent=formatScore(Math.max(state.score,scores[0]?.score||0));\r\n  levelEl.textContent=String(state.level).padStart(2,'0'); livesEl.textContent='\ud83d\udcf9 '.repeat(Math.max(0,state.lives)).trim()||'\u2014';\r\n  const progress=clamp((state.flashCount\/state.flashGoal)*100,0,100);\r\n  progressEl.textContent=`${state.flashCount} \/ ${state.flashGoal}`; progressFill.style.width=`${progress}%`;\r\n  if(goalCount) goalCount.textContent=`${state.flashCount} \/ ${state.flashGoal}`;\r\n  flashText.textContent=state.flashHeld?'HELD':(state.flashFx>0?'ACTIVE':(state.sentry?(state.sentryDetected?'TARGET':'SCAN'):(state.flashCooldown<=0?'READY':`${state.flashCooldown.toFixed(1)}s`)));\r\n  const flashBase=state.sentry?3.6:5.5; flashFill.style.width=`${clamp((1-state.flashCooldown\/flashBase)*100,0,100)}%`;\r\n}\r\n\r\nfunction update(dt){\r\n  if(state.phase==='ready'){\r\n    state.readyTimer-=dt; state.bannerTime=Math.max(state.bannerTime,Math.min(1,state.readyTimer));\r\n    centerView(false,dt);\r\n    if(state.readyTimer<=0){ state.phase='playing'; state.banner=''; state.bannerTime=0; }\r\n    return;\r\n  }\r\n  if(state.phase!=='playing' || state.paused){ centerView(false,dt); return; }\r\n  state.invuln=Math.max(0,state.invuln-dt); state.exposed=Math.max(0,state.exposed-dt);\r\n  state.flashTurnCooldown=Math.max(0,state.flashTurnCooldown-dt);\r\n  if(state.flashHeld){\r\n    state.flashCooldown=0; state.flashFx=1; state.flashPulse+=dt; updateHeldFlashTargets();\r\n  }else{\r\n    state.flashCooldown=Math.max(0,state.flashCooldown-dt); state.flashFx=Math.max(0,state.flashFx-dt); if(state.flashFx>0) state.flashPulse+=dt;\r\n  }\r\n  state.shake=Math.max(0,state.shake-dt); state.bannerTime=Math.max(0,state.bannerTime-dt);\r\n  state.modeTimer-=dt;\r\n  if(state.modeTimer<=0){ state.chaseMode=!state.chaseMode; state.modeTimer=state.chaseMode?18:2.5; }\r\n  if(state.flashHeld){\r\n    state.player.dir=DIRS.none; state.player.desired=DIRS.none;\r\n  }else if(state.sentryPending){\r\n    if(state.manualMode) moveActor(state.player,4.75,dt,true); else updatePlayerNavigation(dt);\r\n    if(playerCentered()) activateSentryNow();\r\n  }else if(state.sentry){\r\n    updateSentry(dt);\r\n  }else if(state.manualMode){\r\n    moveActor(state.player,4.75,dt,true);\r\n  }else{\r\n    updatePlayerNavigation(dt);\r\n  }\r\n  updateCameraFeet(state.player,dt); updateBurglars(dt); if(!state.sentry) collectTile(); updateBonus(dt); collisions(); centerView(false,dt); updateHUD();\r\n}\r\n\r\nfunction rr(x,y,w,h,r,fill=true,stroke=false){\r\n  r=Math.min(r,w\/2,h\/2); ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); if(fill)ctx.fill(); if(stroke)ctx.stroke();\r\n}\r\nfunction worldToPx(x,y){ return {x:(x+.5)*TILE,y:(y+.5)*TILE}; }\r\nfunction drawMaze(){\r\n  const minX=Math.max(0,Math.floor(state.viewX\/TILE)-1), maxX=Math.min(MAZE_W-1,Math.ceil((state.viewX+VIEW_W)\/TILE)+1);\r\n  const minY=Math.max(0,Math.floor(state.viewY\/TILE)-1), maxY=Math.min(MAZE_H-1,Math.ceil((state.viewY+VIEW_H)\/TILE)+1);\r\n  for(let y=minY;y<=maxY;y++) for(let x=minX;x<=maxX;x++) if(state.map[y][x]==='#'){\r\n    const px=x*TILE, py=y*TILE;\r\n    ctx.fillStyle='#06131f'; ctx.fillRect(px+1,py+1,TILE-2,TILE-2);\r\n    ctx.strokeStyle='rgba(54,129,255,.78)'; ctx.lineWidth=1.6; ctx.strokeRect(px+3,py+3,TILE-6,TILE-6);\r\n    ctx.strokeStyle='rgba(32,216,255,.12)'; ctx.lineWidth=.8; ctx.strokeRect(px+7,py+7,TILE-14,TILE-14);\r\n  }\r\n}\r\nfunction drawDots(){\r\n  const now=performance.now()\/1000;\r\n  const minX=Math.max(0,Math.floor(state.viewX\/TILE)-1), maxX=Math.min(MAZE_W-1,Math.ceil((state.viewX+VIEW_W)\/TILE)+1);\r\n  const minY=Math.max(0,Math.floor(state.viewY\/TILE)-1), maxY=Math.min(MAZE_H-1,Math.ceil((state.viewY+VIEW_H)\/TILE)+1);\r\n  for(let y=minY;y<=maxY;y++) for(let x=minX;x<=maxX;x++){\r\n    const c=state.map[y][x], px=(x+.5)*TILE, py=(y+.5)*TILE;\r\n    if(c==='.'){\r\n      ctx.beginPath(); ctx.fillStyle='rgba(232,251,255,.88)'; ctx.arc(px,py,2.25,0,TAU); ctx.fill();\r\n    }else if(c==='o'){\r\n      const r=6.5+Math.sin(now*5+x+y)*1.1; ctx.beginPath(); ctx.shadowColor='#ffd166'; ctx.shadowBlur=13; ctx.fillStyle='#ffd166'; ctx.arc(px,py,r,0,TAU); ctx.fill(); ctx.shadowBlur=0;\r\n    }\r\n  }\r\n}\r\nfunction facingAngle(f){ if(f===DIRS.down)return Math.PI\/2;if(f===DIRS.left)return Math.PI;if(f===DIRS.up)return-Math.PI\/2;return 0; }\r\nfunction drawCameraFeet(p){\r\n  if(!p.feet) return;\r\n  const body=worldToPx(p.x,p.y);\r\n  const {f}=localFrameForPlayer(p);\r\n  const attachOffsets=[-6,6,0];\r\n  p.feet.forEach((foot,index)=>{\r\n    if(!foot.anchor) return;\r\n    const target=worldToPx(foot.anchor.x,foot.anchor.y);\r\n    const attach={x:body.x-f.y*attachOffsets[index]-f.x*4,y:body.y+f.x*attachOffsets[index]-f.y*4};\r\n    const dx=target.x-attach.x,dy=target.y-attach.y;\r\n    const len=Math.hypot(dx,dy)||1;\r\n    const nx=-dy\/len,ny=dx\/len;\r\n    const lift=(foot.t<1?Math.sin(foot.t*Math.PI)*8:0)*(index===1?-1:1);\r\n    const joint={x:(attach.x+target.x)\/2+nx*(8+lift),y:(attach.y+target.y)\/2+ny*(8+lift)};\r\n\r\n    ctx.save();\r\n    ctx.lineCap='round';ctx.lineJoin='round';\r\n    ctx.shadowColor='rgba(141,255,117,.45)';ctx.shadowBlur=6;\r\n    ctx.strokeStyle='rgba(188,255,176,.92)';ctx.lineWidth=3.2;\r\n    ctx.beginPath();ctx.moveTo(attach.x,attach.y);ctx.lineTo(joint.x,joint.y);ctx.lineTo(target.x,target.y);ctx.stroke();\r\n    ctx.shadowBlur=0;ctx.strokeStyle='#173e1e';ctx.lineWidth=1.1;ctx.stroke();\r\n\r\n    \/\/ Het \"klikkende\" voetje landt zichtbaar op een ander muurblokje.\r\n    const landing=foot.t<1?1-Math.sin(foot.t*Math.PI)*.35:1;\r\n    ctx.translate(target.x,target.y);ctx.rotate(Math.atan2(dy,dx));ctx.scale(landing,landing);\r\n    ctx.fillStyle=foot.anchor.synthetic?'#84c77b':'#d9ffd2';\r\n    ctx.strokeStyle='#5bea50';ctx.lineWidth=1.3;\r\n    rr(-4.8,-3.2,9.6,6.4,2.4,true,true);\r\n    ctx.beginPath();ctx.fillStyle='#061209';ctx.arc(0,0,1.5,0,TAU);ctx.fill();\r\n    ctx.restore();\r\n  });\r\n}\r\n\r\n\r\nfunction drawNavigationTarget(){\r\n  if(!state.navTarget) return;\r\n  const p=worldToPx(state.navTarget.x,state.navTarget.y);\r\n  const pulse=1+Math.sin(performance.now()\/180)*.12;\r\n  ctx.save(); ctx.translate(p.x,p.y); ctx.scale(pulse,pulse);\r\n  ctx.strokeStyle='rgba(32,216,255,.92)'; ctx.lineWidth=1.6;\r\n  ctx.beginPath(); ctx.arc(0,0,8,0,TAU); ctx.stroke();\r\n  ctx.beginPath(); ctx.moveTo(-11,0);ctx.lineTo(-5,0);ctx.moveTo(11,0);ctx.lineTo(5,0);ctx.moveTo(0,-11);ctx.lineTo(0,-5);ctx.moveTo(0,11);ctx.lineTo(0,5);ctx.stroke();\r\n  ctx.restore();\r\n}\r\n\r\nfunction drawCamera(p){\r\n  \/\/ Eerst de drie armen tekenen zodat ze achter de camerabehuizing liggen.\r\n  drawCameraFeet(p);\r\n  const pos=worldToPx(p.x,p.y); ctx.save(); ctx.translate(pos.x,pos.y); ctx.rotate(facingAngle(p.facing));\r\n  if(state.invuln>0 && Math.floor(performance.now()\/85)%2===0) ctx.globalAlpha=.32;\r\n  const flashBoost=state.flashFx>0?22:12;\r\n  ctx.shadowColor=state.flashFx>0?'#fff6bf':'#8dff75'; ctx.shadowBlur=flashBoost; ctx.fillStyle='#d8ffd0'; rr(-10,-8,17,16,4,true,false);\r\n  ctx.fillStyle='#75ee65'; ctx.beginPath(); ctx.moveTo(7,-6);ctx.lineTo(15,-10);ctx.lineTo(15,10);ctx.lineTo(7,6);ctx.closePath();ctx.fill();\r\n  ctx.fillStyle='#041a08';ctx.beginPath();ctx.arc(-2,0,4.3,0,TAU);ctx.fill();\r\n  ctx.fillStyle=state.flashFx>0?'#fffbdc':'#fff';ctx.shadowColor='#fff7b0';ctx.shadowBlur=state.flashFx>0?18:0;ctx.beginPath();ctx.arc(-3.3,-1.4,state.flashFx>0?2.2:1.3,0,TAU);ctx.fill();\r\n  ctx.restore();\r\n}\r\nfunction drawBurglar(b){\r\n  const pos=worldToPx(b.x,b.y), frightened=state.exposed>0, stunned=b.stun>0 || b.flashed;\r\n  let body=b.color;\r\n  if(frightened){ const flashing=state.exposed<2 && Math.floor(performance.now()\/140)%2===0; body=flashing?'#f2fbff':'#184fd5'; }\r\n  ctx.save();ctx.translate(pos.x,pos.y);if(stunned){ctx.rotate(Math.sin(performance.now()\/38)*.08);ctx.globalAlpha=.78;}\r\n  ctx.shadowColor=body;ctx.shadowBlur=frightened?16:8;ctx.fillStyle=body;rr(-9,-4,18,17,6,true,false);ctx.beginPath();ctx.arc(0,-6,7.5,Math.PI,TAU);ctx.fill();ctx.shadowBlur=0;\r\n  ctx.fillStyle='#101114';ctx.fillRect(-8,-8,16,5);ctx.fillStyle='#f7ffff';ctx.fillRect(-5,-7,3,2);ctx.fillRect(2,-7,3,2);ctx.fillStyle='#001018';ctx.fillRect(-4,-6,1,1);ctx.fillRect(3,-6,1,1);\r\n  ctx.strokeStyle='#111';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(-6,12);ctx.lineTo(-9,16);ctx.moveTo(6,12);ctx.lineTo(9,16);ctx.stroke();\r\n  if(stunned){ctx.fillStyle='#ffd166';ctx.font='bold 10px monospace';ctx.textAlign='center';ctx.fillText('\u2726',-8,-15);ctx.fillText('\u2726',8,-18);if(b.flashed){ctx.font='bold 8px monospace';ctx.fillText('FLASHED',0,25);}}ctx.restore();\r\n}\r\nfunction drawBonus(){\r\n  if(!state.bonus) return; const p=worldToPx(state.bonus.x,state.bonus.y); const s=1+Math.sin(state.bonus.pulse*8)*.08;\r\n  ctx.save();ctx.translate(p.x,p.y);ctx.scale(s,s);ctx.shadowColor='#ffd166';ctx.shadowBlur=16;ctx.fillStyle='#ffd166';rr(-9,-7,18,15,3,true,false);ctx.shadowBlur=0;ctx.fillStyle='#3b2b02';ctx.fillRect(-4,-10,8,4);ctx.fillStyle='#fff7cc';ctx.font='bold 10px monospace';ctx.textAlign='center';ctx.fillText('E',0,4);ctx.restore();\r\n}\r\nfunction raycastFlashTiles(x,y,angle,maxTiles){\r\n  const step=.045, dx=Math.cos(angle), dy=Math.sin(angle);\r\n  for(let d=.10;d<=maxTiles;d+=step){\r\n    const tx=Math.round(x+dx*d), ty=Math.round(y+dy*d);\r\n    if(!isPassable(tx,ty)) return Math.max(.10,d-step*1.4);\r\n  }\r\n  return maxTiles;\r\n}\r\n\r\nfunction drawCone(){\r\n  if(!state.player) return;\r\n  const p=worldToPx(state.player.x,state.player.y), f=state.player.facing||DIRS.right, angle=Math.atan2(f.y,f.x);\r\n  const active=state.flashFx>0;\r\n  const sentry=state.sentry;\r\n  const pulse=active?.90+.10*Math.sin(state.flashPulse*32):1;\r\n  const corridorTiles=sentry?corridorRangeTiles(state.player,f):0;\r\n  const maxTiles=sentry?Math.max(.8,corridorTiles):(active?7.2:5.5), len=TILE*maxTiles, spread=active?(sentry?.22:.43):(sentry?.085:.30);\r\n  const halfAngle=Math.atan(spread), rayCount=active?23:15, rays=[];\r\n  for(let i=0;i<rayCount;i++){\r\n    const rel=-halfAngle+(halfAngle*2*i)\/(rayCount-1);\r\n    rays.push({rel,reach:raycastFlashTiles(state.player.x,state.player.y,angle+rel,maxTiles)});\r\n  }\r\n  const centerReach=raycastFlashTiles(state.player.x,state.player.y,angle,maxTiles);\r\n  ctx.save();ctx.translate(p.x,p.y);ctx.rotate(angle);\r\n  if(active) ctx.globalCompositeOperation='screen';\r\n\r\n  const g=ctx.createLinearGradient(0,0,len,0);\r\n  g.addColorStop(0,active?`rgba(255,255,238,${.96*pulse})`:(sentry?(state.sentryDetected?'rgba(255,110,90,.24)':'rgba(141,255,117,.16)'):'rgba(255,246,185,.045)'));\r\n  g.addColorStop(.18,active?`rgba(255,246,174,${.70*pulse})`:(sentry?(state.sentryDetected?'rgba(255,80,70,.12)':'rgba(32,216,255,.07)'):'rgba(255,246,185,.028)'));\r\n  g.addColorStop(1,'rgba(255,246,185,0)');\r\n  ctx.fillStyle=g;ctx.beginPath();ctx.moveTo(8,0);\r\n  for(const ray of rays){const d=ray.reach*TILE;ctx.lineTo(Math.cos(ray.rel)*d,Math.sin(ray.rel)*d);}\r\n  ctx.closePath();ctx.fill();\r\n\r\n  if(active){\r\n    \/\/ Felle witte kern van de lamp, eveneens begrensd door de eerstvolgende muur.\r\n    const coreLen=Math.min(len*.86,centerReach*TILE);\r\n    const core=ctx.createLinearGradient(0,0,Math.max(10,coreLen),0);core.addColorStop(0,`rgba(255,255,255,${.98*pulse})`);core.addColorStop(.35,`rgba(255,252,210,${.48*pulse})`);core.addColorStop(1,'rgba(255,255,255,0)');\r\n    ctx.fillStyle=core;ctx.shadowColor='#fff7b0';ctx.shadowBlur=24;ctx.beginPath();ctx.moveTo(9,-4);ctx.lineTo(coreLen,-Math.min(coreLen*.075,9));ctx.lineTo(coreLen,Math.min(coreLen*.075,9));ctx.lineTo(9,4);ctx.closePath();ctx.fill();\r\n    ctx.shadowBlur=0;\r\n    \/\/ Halo rond de lens gedurende de volledige activatie.\r\n    const halo=ctx.createRadialGradient(10,0,0,10,0,TILE*2.2);halo.addColorStop(0,`rgba(255,255,245,${.88*pulse})`);halo.addColorStop(.25,`rgba(255,239,150,${.32*pulse})`);halo.addColorStop(1,'rgba(255,230,120,0)');ctx.fillStyle=halo;ctx.beginPath();ctx.arc(10,0,TILE*2.2,0,TAU);ctx.fill();\r\n  }\r\n  ctx.restore();\r\n}\r\n\r\nfunction drawFlashScreenBloom(){\r\n  if(state.flashFx<=0 || !state.player) return;\r\n  const p={x:(state.player.x+.5)*TILE-state.viewX,y:(state.player.y+.5)*TILE-state.viewY};\r\n  const strength=clamp(state.flashFx\/.82,0,1);\r\n  const glow=ctx.createRadialGradient(p.x,p.y,10,p.x,p.y,260);\r\n  glow.addColorStop(0,`rgba(255,250,210,${.16*strength})`);glow.addColorStop(1,'rgba(255,250,210,0)');\r\n  ctx.save();ctx.globalCompositeOperation='screen';ctx.fillStyle=glow;ctx.fillRect(0,0,VIEW_W,VIEW_H);ctx.restore();\r\n}\r\nfunction drawScreenHUD(){\r\n  ctx.save();\r\n  ctx.fillStyle='rgba(0,10,14,.40)';ctx.fillRect(12,12,176,38);ctx.strokeStyle='rgba(32,216,255,.35)';ctx.strokeRect(12.5,12.5,176,38);\r\n  ctx.fillStyle='#ff4d6d';ctx.beginPath();ctx.arc(27,31,4,0,TAU);ctx.fill();\r\n  ctx.fillStyle='#dff8ff';ctx.font='bold 12px monospace';ctx.textBaseline='middle';ctx.fillText('REC  CAM-07',39,31);\r\n  ctx.textAlign='right';ctx.fillStyle='rgba(223,248,255,.75)';ctx.font='10px monospace';ctx.fillText(`SECTOR ${String(state.level).padStart(2,'0')}`,VIEW_W-16,27);\r\n  ctx.fillText(state.exposed>0?`ALARM ${state.exposed.toFixed(1)}s`:(state.sentry?(state.sentryDetected?'SENTRY \u2022 TARGET':'SENTRY \u2022 SCANNING'):'STATUS CLEAR'),VIEW_W-16,43);\r\n  ctx.restore();\r\n}\r\nfunction drawBanner(){\r\n  if(state.bannerTime<=0 || !state.banner) return; const a=clamp(state.bannerTime*2,0,1);\r\n  ctx.save();ctx.globalAlpha=a;ctx.fillStyle='rgba(0,8,12,.84)';rr(VIEW_W\/2-150,VIEW_H\/2-27,300,54,8,true,false);ctx.strokeStyle='rgba(32,216,255,.45)';ctx.lineWidth=1;rr(VIEW_W\/2-150,VIEW_H\/2-27,300,54,8,false,true);ctx.fillStyle='#eefcff';ctx.font='bold 18px monospace';ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(state.banner,VIEW_W\/2,VIEW_H\/2);ctx.restore();\r\n}\r\nfunction draw(){\r\n  ctx.save();\r\n  ctx.setTransform(DPR,0,0,DPR,0,0);\r\n  ctx.clearRect(0,0,VIEW_W,VIEW_H);\r\n  const bg=ctx.createRadialGradient(VIEW_W*.5,VIEW_H*.48,20,VIEW_W*.5,VIEW_H*.48,VIEW_H*.8);bg.addColorStop(0,'#06141d');bg.addColorStop(1,'#010407');ctx.fillStyle=bg;ctx.fillRect(0,0,VIEW_W,VIEW_H);\r\n  if(state.shake>0){const m=state.shake*8;ctx.translate((Math.random()-.5)*m,(Math.random()-.5)*m);}\r\n  ctx.save();ctx.translate(-state.viewX,-state.viewY);drawMaze();drawDots();drawBonus();drawNavigationTarget();drawCone();for(const b of state.burglars)if(b.respawn<=0)drawBurglar(b);if(state.player)drawCamera(state.player);ctx.restore();\r\n  drawFlashScreenBloom();drawScreenHUD();drawBanner();ctx.restore();\r\n}\r\n\r\nfunction getScores(){\r\n  try{ const v=JSON.parse(localStorage.getItem('cctvPacMazeInteractiveV1')||'[]'); return Array.isArray(v)?v:[]; }catch(e){ return memoryScores; }\r\n}\r\nfunction saveScore(name,score,level){\r\n  const scores=getScores().slice(); scores.push({name:(name||'SECURITY').slice(0,12),score:Math.floor(score),level}); scores.sort((a,b)=>b.score-a.score); const out=scores.slice(0,8); memoryScores=out;\r\n  try{localStorage.setItem('cctvPacMazeInteractiveV1',JSON.stringify(out));}catch(e){}\r\n}\r\nfunction renderScores(){\r\n  const scores=getScores(); scoreTable.innerHTML=''; const rows=scores.length?scores:Array.from({length:5},()=>({name:'---',score:0}));\r\n  rows.slice(0,8).forEach((s,i)=>{const tr=document.createElement('tr');tr.innerHTML=`<td>${i+1}<\/td><td>${escapeHtml(s.name||'---')}<\/td><td>${formatScore(s.score||0)}<\/td>`;scoreTable.appendChild(tr);});\r\n  highEl.textContent=formatScore(Math.max(state.score,scores[0]?.score||0));\r\n}\r\n\r\nfunction togglePause(){\r\n  if(state.phase!=='playing') return; state.paused=!state.paused; state.banner=state.paused?'PAUSED':'RESUMED';state.bannerTime=.8;statusLine.textContent=state.paused?'Pauze \u2014 druk P om verder te gaan.':`Operator ${state.name} actief \u2014 feed live.`;\r\n}\r\nfunction toggleSound(){\r\n  state.sound=!state.sound;\r\n  soundBtn.textContent=state.sound?'\ud83d\udd0a GELUID AAN':'\ud83d\udd07 GELUID UIT';\r\n  if(volumeBtn){volumeBtn.textContent=state.sound?'\ud83d\udd0a':'\ud83d\udd07';volumeBtn.setAttribute('aria-label',state.sound?'Geluid uitschakelen':'Geluid inschakelen');}\r\n  if(state.sound)ensureAudio();\r\n}\r\n\r\nroot.addEventListener('keydown',event=>{\r\n  const editable=event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target.isContentEditable;\r\n  if(editable) return;\r\n  const arrows=['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'];\r\n  const codes=['Space','KeyE','KeyP','KeyM',...arrows];\r\n  if(['ready','playing'].includes(state.phase) && codes.includes(event.code)) event.preventDefault();\r\n\r\n  if(event.code==='Space'){\r\n    if(!event.repeat) beginFlashHold();\r\n    return;\r\n  }\r\n\r\n  if(state.flashHeld){\r\n    if(event.code==='ArrowLeft') turnDuringFlash(-1);\r\n    else if(event.code==='ArrowRight') turnDuringFlash(1);\r\n    return;\r\n  }\r\n\r\n  const dirByCode={ArrowUp:DIRS.up,ArrowDown:DIRS.down,ArrowLeft:DIRS.left,ArrowRight:DIRS.right};\r\n  if(dirByCode[event.code]) beginManualMove(dirByCode[event.code]);\r\n  else if(event.code==='KeyE'&&!event.repeat) toggleSentry();\r\n  else if(event.code==='KeyP'&&!event.repeat) togglePause();\r\n  else if(event.code==='KeyM'&&!event.repeat) toggleSound();\r\n},{passive:false});\r\n\r\nroot.addEventListener('keyup',event=>{\r\n  if(event.code==='Space'){\r\n    event.preventDefault();\r\n    endFlashHold();\r\n  }\r\n},{passive:false});\r\n\r\nwindow.addEventListener('blur',()=>{ if(state.flashHeld) endFlashHold(); });\r\n\r\nstartBtn.addEventListener('click',()=>{\r\n  introTips.style.display='grid'; overlayEyebrow.textContent='SECURITY SYSTEM \/\/ ONLINE'; overlayTitle.innerHTML='<span class=\"white\">CCTV<\/span> <b>NIGHT WATCH<\/b>';\r\n  overlayText.textContent='DOEL VAN HET SPEL: flash 5 inbrekers. Klik of tik voor automatische navigatie, of gebruik de pijltjestoetsen. Houd SPATIE vast om stil te staan en te flashen; het licht stopt aan muren. Bij 5\/5 is de missie voltooid.'; startBtn.textContent='\u25b6 START GAME'; startGame();\r\n});\r\nsoundBtn.addEventListener('click',toggleSound);\r\nif(volumeBtn) volumeBtn.addEventListener('click',e=>{e.stopPropagation();toggleSound();root.focus();});\r\n\r\nconst flashTouch=document.getElementById('flashTouch');\r\nflashTouch.addEventListener('pointerdown',e=>{e.preventDefault();beginFlashHold();flashTouch.setPointerCapture?.(e.pointerId);root.focus();},{passive:false});\r\nfor(const ev of ['pointerup','pointercancel','lostpointercapture']) flashTouch.addEventListener(ev,e=>{e.preventDefault();endFlashHold();},{passive:false});\r\n\r\ndocument.querySelectorAll('[data-dir]').forEach(btn=>{\r\n  btn.addEventListener('pointerdown',e=>{\r\n    e.preventDefault();\r\n    const dir=DIRS[btn.dataset.dir];\r\n    if(state.flashHeld){\r\n      if(dir===DIRS.left) turnDuringFlash(-1);\r\n      else if(dir===DIRS.right) turnDuringFlash(1);\r\n    }else if(dir) beginManualMove(dir);\r\n    root.focus();\r\n  },{passive:false});\r\n});\r\nif(sentryTouch) sentryTouch.addEventListener('pointerdown',e=>{e.preventDefault();toggleSentry();root.focus();},{passive:false});\r\n\r\nconst stage=document.getElementById('stage');\r\ncanvas.addEventListener('pointerdown',e=>{\r\n  if(!['ready','playing'].includes(state.phase) || state.paused || state.flashHeld) return;\r\n  e.preventDefault();\r\n  const rect=canvas.getBoundingClientRect();\r\n  const sx=(e.clientX-rect.left)*(VIEW_W\/rect.width);\r\n  const sy=(e.clientY-rect.top)*(VIEW_H\/rect.height);\r\n  const col=Math.floor((sx+state.viewX)\/TILE);\r\n  const row=Math.floor((sy+state.viewY)\/TILE);\r\n  setNavigationTarget(col,row);\r\n  root.focus();\r\n},{passive:false});\r\n\r\nfunction loop(now){\r\n  const dt=Math.min(.034,Math.max(0,(now-state.lastTime)\/1000||0));state.lastTime=now;update(dt);draw();requestAnimationFrame(loop);\r\n}\r\n\r\n\/\/ Stilstaand voorbeeld achter het startscherm.\r\nstate.map=generateMaze(1);\r\nconst previewSpawn=nearestPassable(2,MAZE_H-4);state.map[previewSpawn.y][previewSpawn.x]=' ';\r\nconst previewDir=chooseInitialDir(previewSpawn);state.player={x:previewSpawn.x,y:previewSpawn.y,dir:DIRS.none,desired:DIRS.none,facing:previewDir,route:[]};createCameraFeet(state.player);state.burglars=[0,1,2,3].map(makeBurglar);state.initialDots=1;state.dots=1;centerView(true);renderScores();updateHUD();draw();requestAnimationFrame(loop);\r\n})();\r\n<\/script>\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-b262009 e-flex e-con-boxed e-con e-parent\" data-id=\"b262009\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4a4e74b elementor-widget elementor-widget-spacer\" data-id=\"4a4e74b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Home Under Construction \u00a0 \u00a0 https:\/\/cctvacademy.be\/wp-content\/uploads\/2026\/09\/cctv-pacmaze-intro-movie-2-cctv-academy.mp4 CCTV PAC-MAZE v0.4 \u2014 Night Watch CCTV PAC-MAZEv0.4 NIGHT WATCH \/\/ LIVE FEEDSECURITY MAZE CHASE Score000000 High000000 Level01 Cams\ud83d\udcf9 \ud83d\udcf9 \ud83d\udcf9 Doel0 \/ 5 FlashREADY DOEL VAN HET SPEL: FLASH 5 INBREKERS0 \/ 5 \ud83d\udd0a SECURITY SYSTEM \/\/ STANDBY CCTV NIGHT WATCH DOEL VAN HET SPEL: FLASH 5 INBREKERS. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"_acf_changed":false,"zakra_page_container_layout":"customizer","zakra_page_sidebar_layout":"customizer","zakra_remove_content_margin":false,"zakra_sidebar":"customizer","zakra_transparent_header":"customizer","zakra_logo":0,"zakra_main_header_style":"default","zakra_menu_item_color":"","zakra_menu_item_hover_color":"","zakra_menu_item_active_color":"","zakra_menu_active_style":"","zakra_page_header":true,"h5ap_radio_sources":[],"_glsr_average":0,"_glsr_ranking":0,"_glsr_reviews":0,"footnotes":""},"class_list":["post-51255","page","type-page","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>CCTV Pacman - RAS\/AIS CCTV Academy<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cctvacademy.be\/fr\/cctv-pacman\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"CCTV Pacman - RAS\/AIS CCTV Academy\" \/>\n<meta property=\"og:description\" content=\"Home Under Construction \u00a0 \u00a0 https:\/\/cctvacademy.be\/wp-content\/uploads\/2026\/09\/cctv-pacmaze-intro-movie-2-cctv-academy.mp4 CCTV PAC-MAZE v0.4 \u2014 Night Watch CCTV PAC-MAZEv0.4 NIGHT WATCH \/\/ LIVE FEEDSECURITY MAZE CHASE Score000000 High000000 Level01 Cams\ud83d\udcf9 \ud83d\udcf9 \ud83d\udcf9 Doel0 \/ 5 FlashREADY DOEL VAN HET SPEL: FLASH 5 INBREKERS0 \/ 5 \ud83d\udd0a SECURITY SYSTEM \/\/ STANDBY CCTV NIGHT WATCH DOEL VAN HET SPEL: FLASH 5 INBREKERS. [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cctvacademy.be\/fr\/cctv-pacman\/\" \/>\n<meta property=\"og:site_name\" content=\"RAS\/AIS CCTV Academy\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/profile.php?id=61576769152561\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-20T10:20:47+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/cctvacademy.be\\\/cctv-pacman\\\/\",\"url\":\"https:\\\/\\\/cctvacademy.be\\\/cctv-pacman\\\/\",\"name\":\"CCTV Pacman - RAS\\\/AIS CCTV Academy\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/cctvacademy.be\\\/#website\"},\"datePublished\":\"2026-09-19T18:31:11+00:00\",\"dateModified\":\"2026-09-20T10:20:47+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/cctvacademy.be\\\/cctv-pacman\\\/#breadcrumb\"},\"inLanguage\":\"fr-BE\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/cctvacademy.be\\\/cctv-pacman\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/cctvacademy.be\\\/cctv-pacman\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/cctvacademy.be\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"CCTV Pacman\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/cctvacademy.be\\\/#website\",\"url\":\"https:\\\/\\\/cctvacademy.be\\\/\",\"name\":\"CCTV Academy\",\"description\":\"CCTV Opleidingen en CCTV Micro Learning\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/cctvacademy.be\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-BE\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"CCTV Pacman - RAS\/AIS CCTV Academy","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cctvacademy.be\/fr\/cctv-pacman\/","og_locale":"fr_FR","og_type":"article","og_title":"CCTV Pacman - RAS\/AIS CCTV Academy","og_description":"Home Under Construction \u00a0 \u00a0 https:\/\/cctvacademy.be\/wp-content\/uploads\/2026\/09\/cctv-pacmaze-intro-movie-2-cctv-academy.mp4 CCTV PAC-MAZE v0.4 \u2014 Night Watch CCTV PAC-MAZEv0.4 NIGHT WATCH \/\/ LIVE FEEDSECURITY MAZE CHASE Score000000 High000000 Level01 Cams\ud83d\udcf9 \ud83d\udcf9 \ud83d\udcf9 Doel0 \/ 5 FlashREADY DOEL VAN HET SPEL: FLASH 5 INBREKERS0 \/ 5 \ud83d\udd0a SECURITY SYSTEM \/\/ STANDBY CCTV NIGHT WATCH DOEL VAN HET SPEL: FLASH 5 INBREKERS. [&hellip;]","og_url":"https:\/\/cctvacademy.be\/fr\/cctv-pacman\/","og_site_name":"RAS\/AIS CCTV Academy","article_publisher":"https:\/\/www.facebook.com\/profile.php?id=61576769152561","article_modified_time":"2026-09-20T10:20:47+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/cctvacademy.be\/cctv-pacman\/","url":"https:\/\/cctvacademy.be\/cctv-pacman\/","name":"CCTV Pacman - RAS\/AIS CCTV Academy","isPartOf":{"@id":"https:\/\/cctvacademy.be\/#website"},"datePublished":"2026-09-19T18:31:11+00:00","dateModified":"2026-09-20T10:20:47+00:00","breadcrumb":{"@id":"https:\/\/cctvacademy.be\/cctv-pacman\/#breadcrumb"},"inLanguage":"fr-BE","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cctvacademy.be\/cctv-pacman\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/cctvacademy.be\/cctv-pacman\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cctvacademy.be\/"},{"@type":"ListItem","position":2,"name":"CCTV Pacman"}]},{"@type":"WebSite","@id":"https:\/\/cctvacademy.be\/#website","url":"https:\/\/cctvacademy.be\/","name":"CCTV Academy","description":"Formation \u00e0 la vid\u00e9osurveillance et micro-apprentissage de la vid\u00e9osurveillance","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cctvacademy.be\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-BE"}]}},"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/pages\/51255","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/comments?post=51255"}],"version-history":[{"count":75,"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/pages\/51255\/revisions"}],"predecessor-version":[{"id":51345,"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/pages\/51255\/revisions\/51345"}],"wp:attachment":[{"href":"https:\/\/cctvacademy.be\/fr\/wp-json\/wp\/v2\/media?parent=51255"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}