<?php
declare(strict_types=1);
require __DIR__ . '/bootstrap.php';

if (!is_authenticated()) {
    $error = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $submittedToken = (string)($_POST['csrf_token'] ?? '');
        $username = trim((string)($_POST['username'] ?? ''));
        $password = (string)($_POST['password'] ?? '');
        $now = time();
        $lockedUntil = (int)($_SESSION['login_locked_until'] ?? 0);

        if (!hash_equals(csrf_token(), $submittedToken)) {
            $error = 'De sessie is verlopen. Vernieuw de pagina en probeer opnieuw.';
        } elseif ($lockedUntil > $now) {
            $error = 'Te veel mislukte pogingen. Probeer het over enkele minuten opnieuw.';
        } else {
            $validUser = hash_equals((string)$config['username'], $username);
            $validPassword = password_verify($password, (string)$config['password_hash']);
            if ($validUser && $validPassword) {
                session_regenerate_id(true);
                $_SESSION['authenticated'] = true;
                $_SESSION['username'] = $config['username'];
                $_SESSION['login_failures'] = 0;
                $_SESSION['login_locked_until'] = 0;
                $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
                header('Location: index.php');
                exit;
            }
            $failures = (int)($_SESSION['login_failures'] ?? 0) + 1;
            $_SESSION['login_failures'] = $failures;
            if ($failures >= 5) {
                $_SESSION['login_locked_until'] = $now + 300;
                $_SESSION['login_failures'] = 0;
            }
            usleep(350000);
            $error = 'Gebruikersnaam of wachtwoord is niet correct.';
        }
    }
    render_login_page($error);
    exit;
}
?>
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>CRIO Next — portfolio en projectregie</title>
<style>
:root{--ink:#161b22;--muted:#68707d;--paper:#fff;--bg:#f3f4f6;--line:#dfe2e7;--accent:#2359d1;--soft:#edf2ff;--ok:#1f7a55;--warn:#a66b00;--danger:#a44343;--shadow:0 16px 45px rgba(20,28,45,.09)}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;background:var(--bg);color:var(--ink);line-height:1.5}button,input,select,textarea{font:inherit}button{cursor:pointer}a{color:inherit}.shell{width:min(1560px,calc(100% - 32px));margin:auto}
header{background:#111722;color:#fff;padding:28px 0 24px;border-bottom:4px solid #89a7ff}.topline{display:flex;align-items:flex-start;justify-content:space-between;gap:24px}.brand{display:flex;gap:16px;align-items:center}.mark{width:52px;height:52px;border:1px solid #536073;border-radius:15px;display:grid;place-items:center;font-weight:950;letter-spacing:-.08em;background:#1c2533}.eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.15em;color:#aebbd0;font-weight:850}.brand h1{margin:2px 0 4px;font-size:clamp(34px,4.3vw,62px);line-height:.95;letter-spacing:-.055em}.brand p{margin:0;color:#c8d0dc;max-width:800px}.header-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}.btn{border:0;border-radius:10px;padding:10px 13px;background:var(--accent);color:white;font-weight:800;font-size:13px}.btn.light{background:#fff;color:#172033}.btn.ghost{background:#252f3d;color:#fff;border:1px solid #3b4657}.btn.soft{background:var(--soft);color:#234aa3}.btn.neutral{background:#eef0f3;color:#2f3743}.btn.danger{background:#f7e9e9;color:#8d3434}.btn.small{padding:7px 9px;font-size:12px}.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:22px}.stat{background:#1c2532;border:1px solid #303b4b;border-radius:14px;padding:13px 14px}.stat b{display:block;font-size:26px;letter-spacing:-.04em}.stat span{display:block;font-size:11px;color:#aebbd0;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
nav{position:sticky;top:0;z-index:1000;background:rgba(255,255,255,.95);backdrop-filter:blur(14px);border-bottom:1px solid var(--line)}nav .shell{display:flex;gap:5px;padding:9px 0;overflow:auto}nav a{text-decoration:none;white-space:nowrap;padding:9px 11px;border-radius:9px;font-size:13px;font-weight:850}nav a:hover{background:#edf0f5}.count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 5px;border-radius:20px;background:#dfe8ff;color:#214aa6;margin-left:4px;font-size:11px}
main section{padding:46px 0}.section-head{display:flex;justify-content:space-between;align-items:end;gap:20px;flex-wrap:wrap;margin-bottom:18px}.section-head h2{font-size:clamp(28px,3vw,42px);letter-spacing:-.04em;margin:0}.section-head p{margin:4px 0 0;color:var(--muted);max-width:860px}.card{background:var(--paper);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow)}.notice{background:#fff9e8;border:1px solid #ecdcae;border-radius:14px;padding:14px 16px;color:#6f5614}.notice b{color:#473504}.toolbar{display:flex;gap:9px;align-items:center;flex-wrap:wrap;padding:13px;margin-bottom:15px}.search{flex:1;min-width:240px}input,select,textarea{border:1px solid #cfd4dc;border-radius:10px;padding:10px 11px;background:#fff;color:var(--ink)}textarea{resize:vertical}.view-switch{display:flex;background:#eef0f3;border-radius:10px;padding:3px}.view-switch button{border:0;background:transparent;padding:7px 9px;border-radius:7px;font-weight:800;color:#5e6672}.view-switch button.active{background:white;color:#172033;box-shadow:0 2px 8px rgba(0,0,0,.08)}
.focus-grid{display:grid;grid-template-columns:1.3fr .7fr;gap:15px}.focus-list{padding:18px}.focus-row{display:grid;grid-template-columns:36px minmax(180px,.8fr) minmax(220px,1.35fr) 110px;gap:12px;align-items:center;padding:12px 0;border-bottom:1px solid #edf0f3}.focus-row:last-child{border:0}.focus-num{width:29px;height:29px;border-radius:9px;background:#111722;color:white;display:grid;place-items:center;font-weight:900;font-size:12px}.focus-row h3{margin:0;font-size:15px}.focus-row p{margin:0;color:var(--muted);font-size:13px}.priority-pill,.status-pill,.cluster-pill{display:inline-flex;align-items:center;border-radius:999px;padding:5px 8px;font-weight:850;font-size:10px}.priority-pill{background:#fff0d4;color:#805400}.priority-pill.zeer{background:#f9e4e4;color:#8d3434}.status-pill{background:#e9f2ff;color:#24559a}.status-pill.gepland{background:#f0ecff;color:#5b3ca5}.status-pill.concept{background:#f1f3f5;color:#535b66}.status-pill.ontwikkeling{background:#e9f2ff;color:#24559a}.status-pill.actief{background:#e5f6ec;color:#23623e}.status-pill.review{background:#fff2cf;color:#7c5900}.status-pill.wacht{background:#fff0e4;color:#8b4a13}.status-pill.gepauzeerd{background:#f3e8e8;color:#7b3c3c}.status-pill.herstarten{background:#fce9df;color:#91451d}.status-pill.terugzoeken{background:#eceff3;color:#53606f}.status-pill.afgerond{background:#e3f5ed;color:#176044}.status-pill.gearchiveerd{background:#e8e9eb;color:#555b63}.cluster-pill{background:#eef0f3;color:#4e5661}.weekbox{padding:18px}.weekbox h3{margin-top:0}.weekbox textarea{width:100%;min-height:105px}.weekbox .save-state{font-size:12px;color:var(--ok);font-weight:800;min-height:18px;margin-top:7px}
.ecosystem{display:grid;grid-template-columns:repeat(4,1fr);gap:13px}.cluster-card{padding:17px;position:relative;overflow:hidden}.cluster-card:before{content:"";position:absolute;left:0;top:0;bottom:0;width:5px;background:var(--cluster)}.cluster-card h3{margin:0 0 5px;font-size:17px}.cluster-card p{margin:0 0 14px;color:var(--muted);font-size:13px;min-height:40px}.cluster-card .big{font-size:30px;font-weight:950;letter-spacing:-.05em}.cluster-card small{color:var(--muted)}
.project-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:13px}.project-card{padding:16px;display:flex;flex-direction:column;gap:9px;min-height:278px}.project-card.focus{outline:2px solid #b9caff;outline-offset:-2px}.project-title{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.project-title h3{margin:0;font-size:18px;letter-spacing:-.02em}.domain{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:#52606e;background:#f2f4f6;border-radius:7px;padding:4px 6px;display:inline-block}.project-card p{margin:0;color:#59616d;font-size:13px}.next{margin-top:auto;background:#f7f8fa;border-left:3px solid var(--accent);padding:10px 11px;border-radius:7px;font-size:12px}.actions{display:flex;gap:7px;flex-wrap:wrap}.hidden{display:none!important}
.table-wrap{overflow:auto}.project-table{width:100%;border-collapse:collapse;background:white}.project-table th,.project-table td{padding:11px 12px;text-align:left;border-bottom:1px solid #edf0f3;font-size:13px}.project-table th{position:sticky;top:0;background:#f7f8fa;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#616a76}.project-table tr:hover td{background:#fafbfc}
.update-layout{display:grid;grid-template-columns:.85fr 1.15fr;gap:15px}.panel{padding:18px}.panel h3{margin-top:0}.form{display:grid;gap:10px}.form textarea{min-height:120px}.timeline{display:grid;gap:9px}.timeline-item{border:1px solid var(--line);border-radius:12px;padding:12px 13px}.timeline-item .date{font-size:11px;color:var(--muted);font-weight:800}.timeline-item h4{margin:2px 0 3px}.timeline-item p{margin:0;color:#59616d;font-size:13px}
.discuss-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:13px}.discuss-card{padding:17px}.discuss-card.done{opacity:.52}.discuss-card h3{margin:0 0 8px}.decision{background:#f6f7f9;border-left:4px solid #9aa5b4;padding:11px;border-radius:8px;font-size:13px}.discuss-card p{color:#59616d;font-size:13px}
.data-grid{display:grid;grid-template-columns:1fr 1fr;gap:15px}.data-card{padding:18px}.data-card h3{margin-top:0}.data-card p{color:var(--muted);font-size:13px}.file-input{display:none}
.modal{position:fixed;inset:0;background:rgba(9,14,22,.72);z-index:2000;display:grid;place-items:center;padding:18px}.modal-card{width:min(720px,100%);max-height:92vh;overflow:auto;background:white;border-radius:20px;padding:21px}.modal-card h2{margin-top:0}.modal-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.modal-grid .full{grid-column:1/-1}.modal-grid label{display:grid;gap:4px;font-size:12px;font-weight:850;color:#4e5763}.modal-grid textarea{min-height:82px}.modal-footer{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:16px}
.user-chip,.sync-chip{display:inline-flex;align-items:center;border-radius:999px;padding:9px 11px;font-size:12px;font-weight:800}.user-chip{background:#252f3d;border:1px solid #3b4657;color:#fff}.sync-chip{background:#173f31;border:1px solid #2c6a53;color:#d9f7e9}.sync-chip.saving{background:#5b4311;border-color:#886521;color:#fff2c7}.sync-chip.error{background:#5a2525;border-color:#8c3d3d;color:#ffe3e3}.logout-form{margin:0;display:inline-flex}footer{padding:30px 0 50px;color:var(--muted);font-size:12px}.footer-line{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}.exclusion{font-weight:850;color:#4c5562}
@media(max-width:1200px){.stats{grid-template-columns:repeat(3,1fr)}.ecosystem{grid-template-columns:repeat(2,1fr)}.project-grid{grid-template-columns:repeat(2,1fr)}.focus-grid,.update-layout{grid-template-columns:1fr}}
@media(max-width:720px){.shell{width:min(100% - 18px,1560px)}header{padding-top:20px}.topline{display:block}.header-actions{justify-content:flex-start;margin-top:18px}.stats,.ecosystem,.project-grid,.discuss-grid,.data-grid{grid-template-columns:1fr}.focus-row{grid-template-columns:32px 1fr}.focus-row p,.focus-row .priority-pill{grid-column:2}.modal-grid{grid-template-columns:1fr}.modal-grid .full{grid-column:auto}.project-table{min-width:850px}}
@media print{nav,.header-actions,.toolbar,.actions,.weekbox button,#data{display:none!important}body{background:white}.card{box-shadow:none}main section{padding:25px 0}.project-grid{grid-template-columns:repeat(2,1fr)}}
</style>
</head>
<body>
<header><div class="shell"><div class="topline"><div class="brand"><div class="mark">C:N</div><div><div class="eyebrow">Persoonlijke projectregie</div><h1>CRIO Next</h1><p>Één werkdashboard voor het CRIO-, Businessbookers-, Recreatief-, media- en domeinportfolio. Montferlandse projecten blijven bewust in Montferland Next.</p></div></div><div class="header-actions"><span class="user-chip">Ingelogd als <b><?= htmlspecialchars($_SESSION['username'], ENT_QUOTES, 'UTF-8') ?></b></span><span id="syncStatus" class="sync-chip">Serveropslag actief</span><button class="btn ghost" onclick="window.print()">Print overzicht</button><button class="btn light" onclick="openNewProject()">+ Nieuw project</button><form method="post" action="logout.php" class="logout-form"><input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') ?>"><button class="btn ghost" type="submit">Uitloggen</button></form></div></div><div class="stats"><div class="stat"><b id="statProjects">0</b><span>projecten</span></div><div class="stat"><b id="statActive">0</b><span>actief / review</span></div><div class="stat"><b id="statBuild">0</b><span>in ontwikkeling</span></div><div class="stat"><b id="statFocus">0</b><span>weekfocus</span></div><div class="stat"><b id="statDiscuss">0</b><span>besluiten open</span></div><div class="stat"><b id="statClusters">0</b><span>portfoliolijnen</span></div></div></div></header>
<nav><div class="shell"><a href="#focus">Focus</a><a href="#ecosysteem">Portfolio</a><a href="#projecten">Projecten</a><a href="#updates">Updates</a><a href="#bespreken">Bespreken <span id="navDiscuss" class="count">0</span></a><a href="#data">Beheer</a></div></nav>
<main>
<section id="focus"><div class="shell"><div class="section-head"><div><h2>Weekfocus</h2><p>De projecten die nu de meeste aandacht vragen, met steeds één concrete volgende actie.</p></div></div><div class="notice" style="margin-bottom:15px"><b>Scheiding is actief:</b> deze omgeving bevat geen Montferland-, dorpen-, Kasteelstad- of overige SMM-projecten. Die blijven in de aparte Montferland Next-tool.</div><div class="focus-grid"><div id="focusList" class="card focus-list"></div><div class="card weekbox"><h3>Werknotitie van deze week</h3><textarea id="weekNote" placeholder="Wat moet deze week echt af? Welke afhankelijkheid of afspraak mag niet verdwijnen?"></textarea><div class="actions" style="margin-top:9px"><button class="btn" id="saveWeekNote">Bewaar notitie</button><button class="btn neutral" id="clearWeekNote">Wis</button></div><div id="weekSaveState" class="save-state"></div></div></div></div></section>
<section id="ecosysteem"><div class="shell"><div class="section-head"><div><h2>Portfolio in acht lijnen</h2><p>CRIO Next groepeert de projecten op rol en markt, zodat overlap, prioriteit en onderlinge versterking direct zichtbaar worden.</p></div></div><div id="clusterGrid" class="ecosystem"></div></div></section>
<section id="projecten"><div class="shell"><div class="section-head"><div><h2>Alle niet-Montferlandse projecten</h2><p>De startinventaris is samengesteld uit de projecten die in deze accountcontext bekend zijn. Aanpassingen worden centraal op de server opgeslagen en zijn daardoor op meerdere apparaten beschikbaar.</p></div><button class="btn" onclick="openNewProject()">+ Project toevoegen</button></div><div class="card toolbar"><input id="search" class="search" placeholder="Zoek op project, domein, actie of notitie…"><select id="clusterFilter"><option value="">Alle portfoliolijnen</option></select><select id="statusFilter"><option value="">Alle statussen</option></select><select id="priorityFilter"><option value="">Alle prioriteiten</option><option>Zeer hoog</option><option>Hoog</option><option>Midden</option><option>Laag</option></select><select id="focusFilter"><option value="">Alles</option><option value="focus">Alleen weekfocus</option></select><div class="view-switch"><button id="gridViewBtn" class="active">Kaarten</button><button id="tableViewBtn">Tabel</button></div></div><div id="projectGrid" class="project-grid"></div><div id="tableWrap" class="card table-wrap hidden"><table class="project-table"><thead><tr><th>Project</th><th>Portfoliolijn</th><th>Status</th><th>Prioriteit</th><th>Fase</th><th>Vervolgactie</th><th></th></tr></thead><tbody id="projectTableBody"></tbody></table></div></div></section>
<section id="updates"><div class="shell"><div class="section-head"><div><h2>Updates en voortgang</h2><p>Leg een correctie, resultaat, blokkade of volgende stap vast. De historie wordt centraal in CRIO Next bewaard.</p></div></div><div class="update-layout"><div class="card panel"><h3>Nieuwe update</h3><div class="form"><select id="updateProject"></select><select id="updateType"><option>Voortgang</option><option>Besluit nodig</option><option>Blokkade</option><option>Klaar voor review</option><option>Afgerond</option><option>Nieuwe actie</option></select><textarea id="updateText" placeholder="Beschrijf kort wat veranderd is en wat de volgende stap wordt."></textarea><button class="btn" id="addUpdate">Update vastleggen</button></div></div><div class="card panel"><h3>Recente historie</h3><div id="timeline" class="timeline"></div></div></div></div></section>
<section id="bespreken"><div class="shell"><div class="section-head"><div><h2>Besluiten die niet mogen blijven hangen</h2><p>Zet keuzes hier apart neer. Na besluit kan het punt worden gemarkeerd als besproken.</p></div><button class="btn soft" id="newDiscussion">+ Bespreekpunt</button></div><div id="discussionGrid" class="discuss-grid"></div></div></section>
<section id="data"><div class="shell"><div class="section-head"><div><h2>Beheer en overdracht</h2><p>CRIO Next bewaart jouw gegevens centraal op de server. Maak daarnaast regelmatig een losse back-up voor extra zekerheid.</p></div></div><div class="data-grid"><div class="card data-card"><h3>Back-up</h3><p>Exporteer alle projecten, updates, bespreekpunten en jouw weeknotitie als JSON-bestand.</p><div class="actions"><button class="btn" id="exportData">Exporteer CRIO Next</button><label class="btn neutral" for="importFile">Importeer back-up</label><input id="importFile" class="file-input" type="file" accept="application/json"></div></div><div class="card data-card"><h3>Herstel startinventaris</h3><p>Gebruik dit alleen wanneer je alle opgeslagen aanpassingen wilt wissen en terug wilt naar de meegeleverde projectlijst.</p><button class="btn danger" id="resetData">Herstel beginstand</button></div></div></div></section>
</main>
<footer><div class="shell footer-line"><span>CRIO Next · persoonlijke projectregie · beveiligd en centraal opgeslagen</span><span class="exclusion">Montferland-projecten uitgesloten — beheer via Montferland Next</span></div></footer>

<div id="projectModal" class="modal hidden"><div class="modal-card"><h2 id="modalTitle">Project</h2><div class="modal-grid"><label>Projectnaam<input id="fName"></label><label>Domein<input id="fDomain" placeholder="voorbeeld.nl"></label><label>Portfoliolijn<select id="fCluster"></select></label><label>Status<select id="fStatus"><option>Gepland</option><option>Concept</option><option>In ontwikkeling</option><option>Actief</option><option>Klaar voor review</option><option>Wacht op input</option><option>Gepauzeerd</option><option>Te herstarten</option><option>Terugzoeken</option><option>Afgerond</option><option>Gearchiveerd</option></select></label><label>Prioriteit<select id="fPriority"><option>Zeer hoog</option><option>Hoog</option><option>Midden</option><option>Laag</option></select></label><label>Fase<input id="fPhase"></label><label class="full">Volgende actie<textarea id="fNext"></textarea></label><label class="full">Notitie<textarea id="fNote"></textarea></label><label class="full" style="display:flex;grid-template-columns:auto 1fr;align-items:center"><input id="fFocus" type="checkbox" style="width:auto"> Opnemen in weekfocus</label></div><div class="modal-footer"><button id="deleteProject" class="btn danger">Verwijder</button><div class="actions"><button class="btn neutral" onclick="closeModal('projectModal')">Annuleer</button><button id="saveProject" class="btn">Bewaar project</button></div></div></div></div>
<div id="discussionModal" class="modal hidden"><div class="modal-card"><h2>Nieuw bespreekpunt</h2><div class="modal-grid"><label class="full">Titel<input id="dTitle"></label><label>Project<input id="dProject"></label><label class="full">Aanleiding<textarea id="dContext"></textarea></label><label class="full">Welk besluit is nodig?<textarea id="dDecision"></textarea></label></div><div class="modal-footer"><span></span><div class="actions"><button class="btn neutral" onclick="closeModal('discussionModal')">Annuleer</button><button id="saveDiscussion" class="btn">Bewaar bespreekpunt</button></div></div></div></div>
<script>
const INITIAL_PROJECTS = [{"id": "C001", "name": "CRIO website", "domain": "crio.nl", "cluster": "CRIO & organisatie", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Design & positionering", "next": "Minimalistische eindversie afronden en technische master vastleggen.", "note": "Terug naar het eerste CRIO-design; subtiele hero, geen grote blauwe vlakken.", "focus": true}, {"id": "C002", "name": "CRIO Next", "domain": "", "cluster": "CRIO & organisatie", "status": "Actief", "priority": "Zeer hoog", "phase": "Werkdashboard", "next": "Projectinventaris aanvullen en wekelijks als centrale werkmaster gebruiken.", "note": "Persoonlijk dashboard voor alle niet-Montferlandse projecten.", "focus": true}, {"id": "B001", "name": "Businessbookers", "domain": "businessbookers.nl", "cluster": "MICE & Businessbookers", "status": "Actief", "priority": "Zeer hoog", "phase": "Platform & portfolio", "next": "Portfolio, MICE-propositie en koppeling met doelgroepportals verder versterken.", "note": "Hoofdplatform en zakelijke autoriteit.", "focus": true}, {"id": "B002", "name": "Bestemmingsontwikkeling", "domain": "bestemmingsontwikkeling.nl", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Zakelijke positionering", "next": "Site zakelijker maken en cases als MICE- en gebiedsontwikkeling presenteren.", "note": "Onderdeel van Nederland op 1-positionering."}, {"id": "B003", "name": "Bestemmingsmanagement", "domain": "bestemmingsmanagement.nl", "cluster": "MICE & Businessbookers", "status": "Te herstarten", "priority": "Hoog", "phase": "Conceptkeuze", "next": "Besluiten: bestaande module losmaken of opnieuw opbouwen als zelfstandig CRIO-project.", "note": "Niet meer vermengen met Montferland Next."}, {"id": "B004", "name": "Directiesecretaresse", "domain": "directiesecretaresse.nl", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Doelgroepportal", "next": "Kennisbank, locaties, inspiratie en leadroute compleet maken.", "note": "LinkedIn-doelgroep en zakelijke arrangementen verbinden."}, {"id": "B005", "name": "Personeelsvereniging", "domain": "personeelsvereniging.nl", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Doelgroepportal", "next": "PV-kennisbank en uitjesstructuur verdiepen; filters en aanvraagroute testen.", "note": "Moet uitgroeien tot complete kennis- en boekingsomgeving."}, {"id": "B006", "name": "PA", "domain": "pa.nl", "cluster": "MICE & Businessbookers", "status": "Concept", "priority": "Midden", "phase": "Doelgroepportal", "next": "Positionering en inhoudsarchitectuur koppelen aan Businessbookers.", "note": "Professionele assistenten als specifieke doelgroep."}, {"id": "B007", "name": "PO", "domain": "po.nl", "cluster": "MICE & Businessbookers", "status": "Concept", "priority": "Midden", "phase": "Doelgroepportal", "next": "HR/People & Organisation-kennisbank en zakelijke formats uitwerken.", "note": "Kennis, inspiratie en aanvragen combineren."}, {"id": "B008", "name": "Congres", "domain": "congres.nl", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Zeer hoog", "phase": "Platform & kennisbank", "next": "Locatieverhalen, plaatsen, thema’s, congresmatcher en kennisbank afronden.", "note": "Belangrijk autoriteitsdomein binnen MICE.", "focus": true}, {"id": "B009", "name": "Congrescentrum", "domain": "congrescentrum.nl", "cluster": "MICE & Businessbookers", "status": "Concept", "priority": "Midden", "phase": "Locatieplatform", "next": "Rol ten opzichte van congres.nl en Businessbookers exact afbakenen.", "note": "SEO-/AI-ingang voor congrescentra."}, {"id": "B010", "name": "Congres aan Zee", "domain": "congresaanzee.nl", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Premium locatieconcept", "next": "Zuiderduin- en Hoge Duin-beelden scherp verwerken en eindpresentatie afronden.", "note": "High-end positionering voor zakelijke bijeenkomsten aan zee."}, {"id": "B011", "name": "Convention Desk", "domain": "", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Partnerconcept", "next": "Propositie, locaties en leadverdeling uitwerken tot verkoopbaar model.", "note": "Zakelijk bureau-/deskconcept voor congres- en meetingvraagstukken."}, {"id": "B012", "name": "Feest", "domain": "feest.nl", "cluster": "MICE & Businessbookers", "status": "Actief", "priority": "Hoog", "phase": "Uitjes & arrangementen", "next": "Zakelijke groepsproducten en regionale landingspagina’s actualiseren.", "note": "Belangrijke arrangementenbron."}, {"id": "B013", "name": "Heisessie", "domain": "heisessie.nl", "cluster": "MICE & Businessbookers", "status": "Concept", "priority": "Midden", "phase": "Thematisch MICE-platform", "next": "Formules, locaties en inspiratiepagina’s structureren.", "note": "Heldere zakelijke niche met sterke zoekintentie."}, {"id": "B014", "name": "Veluwse Poort", "domain": "", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Gebieds- en locatieconcept", "next": "Patrick-lijst, arrangementen en zakelijke site tot één eindpresentatie brengen.", "note": "Dieren als poort naar de Veluwe."}, {"id": "B015", "name": "Hart van Noord-Holland", "domain": "", "cluster": "MICE & Businessbookers", "status": "In ontwikkeling", "priority": "Zeer hoog", "phase": "Founding-partnermodel", "next": "Presentatie voor Richard en locaties afronden met één founding-partnerpakket.", "note": "Richard trekt de kar; locaties helpen oprichten en krijgen marktpositie, leads en boekingen.", "focus": true}, {"id": "G001", "name": "Golfweekend", "domain": "golfweekend.nl", "cluster": "Golf & leisure", "status": "Actief", "priority": "Zeer hoog", "phase": "Platform & omzet", "next": "Nieuwe arrangementen, partnerrelaties en distributie structureel blijven uitbouwen.", "note": "Kernplatform van Recreatief.nl BV.", "focus": true}, {"id": "G002", "name": "Marketing Golfweekend", "domain": "marketing.golfweekend.nl", "cluster": "Golf & leisure", "status": "Klaar voor review", "priority": "Hoog", "phase": "Partnerpropositie", "next": "Met Andy controleren en pakketten definitief maken.", "note": "Marketingpakketten, magazine en bereik zichtbaar maken.", "focus": true}, {"id": "G003", "name": "Golftoernooi", "domain": "golftoernooi.nl", "cluster": "Golf & leisure", "status": "In ontwikkeling", "priority": "Hoog", "phase": "B2B toernooiplatform", "next": "Vesper-microsite, klantpagina’s en uitnodigingstool afronden.", "note": "Bedrijven vanaf circa 40 deelnemers."}, {"id": "G004", "name": "Vriendenweekend", "domain": "vriendenweekend.nl", "cluster": "Golf & leisure", "status": "In ontwikkeling", "priority": "Midden", "phase": "Groepsplatform", "next": "Versie 1 als basis nemen; datumprikker, album en contentzoekfunctie goed testen.", "note": "Niet overontwerpen; gebruiksvriendelijkheid voorop."}, {"id": "G005", "name": "Mannenweekend", "domain": "mannenweekend.nl", "cluster": "Golf & leisure", "status": "Concept", "priority": "Laag", "phase": "Doelgroepcampagne", "next": "Bepalen of dit een zelfstandig platform of alleen distributiekanaal wordt.", "note": "Kan arrangementen van Recreatief.nl ontsluiten."}, {"id": "G006", "name": "Vriendinnenweekend", "domain": "vriendinnenweekend.nl", "cluster": "Golf & leisure", "status": "Concept", "priority": "Laag", "phase": "Doelgroepcampagne", "next": "Bepalen of zelfstandig platform voldoende onderscheid krijgt.", "note": "Koppeling met wellness, stad en culinair."}, {"id": "G007", "name": "Teamuitje in de Achterhoek", "domain": "teamuitjeindeachterhoek.nl", "cluster": "Golf & leisure", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Regionale groepsportal", "next": "Actief aanbod verdiepen, hoofdbeelden controleren en preview/ZIP afronden.", "note": "Zakelijke en informele teamuitjes in de Achterhoek."}, {"id": "G008", "name": "Vergaderen in de Achterhoek", "domain": "vergaderenindeachterhoek.nl", "cluster": "Golf & leisure", "status": "Actief", "priority": "Midden", "phase": "Regionale MICE-portal", "next": "Inhoud en locaties actueel houden; koppelen aan teamuitje en Businessbookers.", "note": "Regionale zakelijke ingang."}, {"id": "P001", "name": "Web.nl", "domain": "web.nl", "cluster": "Platform & portfolio", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Netwerkindex", "next": "Enjoy, ZakelijkUit en Vergaderen indexeren en sitemap-import testen.", "note": "Overkoepelende toegang tot het netwerk."}, {"id": "P002", "name": "Gelderland op 1", "domain": "gelderlandop1.nl", "cluster": "Platform & portfolio", "status": "Actief", "priority": "Hoog", "phase": "Provinciale autoriteit", "next": "Projectportfolio, cases en regionale campagnes verder zichtbaar maken.", "note": "Provinciale campagne en bewijs van ontwikkelkracht."}, {"id": "P003", "name": "Achterhoek Marketing", "domain": "achterhoekmarketing.nl", "cluster": "Platform & portfolio", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Regionale autoriteit", "next": "Zakelijke en toeristische projecten als portfolio structureren.", "note": "Onderdeel van bredere Gelderland op 1-positionering."}, {"id": "P004", "name": "Veluwe Marketing", "domain": "veluwemarketing.nl", "cluster": "Platform & portfolio", "status": "Terugzoeken", "priority": "Hoog", "phase": "Projectinventaris", "next": "Laatste werkversie lokaliseren, beoordelen en opnieuw als master opslaan.", "note": "Projectstatus en bestanden moeten opnieuw centraal worden vastgelegd."}, {"id": "P005", "name": "Nederland op 1", "domain": "nederlandop1.nl", "cluster": "Platform & portfolio", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Landelijke autoriteit", "next": "Merkarchitectuur, portfolio en rol van regionale platforms uitwerken.", "note": "Landelijke zichtbaarheid van eigen ontwikkelnetwerk."}, {"id": "D001", "name": "Strand", "domain": "strand.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Nationaal bestemmingsportal", "next": "Eindstructuur, bestemmingen en commerciële/toeristische balans afronden.", "note": "Premium exact-match domein."}, {"id": "D002", "name": "Hanzesteden", "domain": "hanzesteden.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Stedenplatform", "next": "Doesburg, Emmerich, Eltenberg en drie stadsmicrosites consistent koppelen.", "note": "Grensoverschrijdende Hanzebeleving."}, {"id": "D003", "name": "Ontdek Zwolle", "domain": "ontdekzwolle.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Stadsmicrosite", "next": "Uitbouwen tot niveau van beste Ontdek-site; beeld en doorklikroutes testen.", "note": "Satelliet van Hanzesteden.nl."}, {"id": "D004", "name": "Ontdek Harderwijk", "domain": "ontdekharderwijk.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Stadsmicrosite", "next": "Uitbouwen met hotspots, horeca, water, cultuur en arrangementen.", "note": "Satelliet van Hanzesteden.nl."}, {"id": "D005", "name": "Ontdek Zutphen", "domain": "ontdekzutphen.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Stadsmicrosite", "next": "Uitbouwen met erfgoed, winkels, gastronomie en arrangementen.", "note": "Satelliet van Hanzesteden.nl."}, {"id": "D006", "name": "Bodensee", "domain": "bodensee.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Internationaal bestemmingsportal", "next": "Bestemmingen, routes, verblijf en inspiratie tot volwaardig platform uitbouwen.", "note": "Visuele kwaliteitsreferentie voor andere bestemmingssites."}, {"id": "D007", "name": "Ontdek Utrecht", "domain": "ontdekutrecht.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Stad & provincie", "next": "OntdekAzewijn-stijl vasthouden; steden, dorpen, hotspots, cultuur en restaurants verdiepen.", "note": "Menu en merknaam moeten direct goed leesbaar zijn."}, {"id": "D008", "name": "Beleef de Veluwezoom", "domain": "beleefdeveluwezoom.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Regioplatform", "next": "Website een niveau dieper uitwerken, dorpen met beeld toevoegen en contact opnemen.", "note": "Beelden uit eigen kanalen en toegestane bronnen vastleggen."}, {"id": "D009", "name": "Paradijs", "domain": "paradijs.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Wereldwijde inspiratie", "next": "Puur toeristisch houden; beeldbronnen transparant maken en specialisten subtiel tippen.", "note": "Geen primair reisverkoopplatform."}, {"id": "D010", "name": "Relaxen", "domain": "relaxen.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Wellness & ontprikkelen", "next": "Beeldrechten controleren en arrangementen van Verwendag/Verwenweekend goed integreren.", "note": "Breder dan reizen of sauna: rust, slaap, mindfulness en me-time."}, {"id": "D011", "name": "Superdeluxe", "domain": "superdeluxe.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Premium lifestyle", "next": "Beelden vanuit arrangementen invullen en high-end segmenten als één merk presenteren.", "note": "Zakelijke exclusiviteit, escapes en gastronomie."}, {"id": "D012", "name": "Pitch & Putt", "domain": "pitchandputt.nl", "cluster": "Bestemmingen & steden", "status": "In ontwikkeling", "priority": "Midden", "phase": "Activiteitenportal", "next": "Werkende preview herstellen, aanbod en autoriteit verdiepen en overnamepotentieel zichtbaar maken.", "note": "Doel: aantrekkelijk portaal dat een organisatie later wil overnemen."}, {"id": "I001", "name": "Tuinbouwer", "domain": "tuinbouwer.nl", "cluster": "Innovatie & sectorportals", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Internationaal sectorportal", "next": "Versie 2 verder verdiepen: zoekfunctie, doorkliklagen en meertaligheid volledig werkend maken.", "note": "NL/EN/DE/ES; hoogwaardige content zonder betaald businessmodel."}, {"id": "I002", "name": "Nedersaksen", "domain": "nedersaksen.nl", "cluster": "Innovatie & sectorportals", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Cultuur & economie", "next": "Zakelijke wereld, toeristische hotspots en bedrijven verdiepen; Achterhoek meenemen.", "note": "NL/DE/Nedersaksisch; geen shop."}, {"id": "I003", "name": "TechValley", "domain": "techvalley.nl", "cluster": "Innovatie & sectorportals", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Tech-ecosysteem", "next": "Positieve eindgebruikerssite afronden in NL/EN/DE met preview en webbouwerpakket.", "note": "Sovereign Tech-Twin als kernconcept; eigen idee en merk."}, {"id": "E001", "name": "Erfgoed Boven Amsterdam", "domain": "erfgoedbovenamsterdam.nl", "cluster": "Erfgoed & internationaal", "status": "In ontwikkeling", "priority": "Hoog", "phase": "Nederlandse hoofdsite", "next": "Volledig klikbare preview met beelden en moderne erfgoedpresentatie afronden.", "note": "Groen/goud/licht, inhoud uit bestaand platform."}, {"id": "E002", "name": "Erfgoed Boven Amsterdam – Duitsland", "domain": "", "cluster": "Erfgoed & internationaal", "status": "In ontwikkeling", "priority": "Midden", "phase": "Duitse touroperators", "next": "Duitstalige B2B-site met programma’s, groepsinformatie en beelden afronden.", "note": "Gericht op Duitse touroperators."}, {"id": "E003", "name": "Erfgoed Boven Amsterdam – UK", "domain": "", "cluster": "Erfgoed & internationaal", "status": "In ontwikkeling", "priority": "Midden", "phase": "Britse touroperators", "next": "Engelstalige B2B-versie met sterke beelden en boekbare groepslogica afronden.", "note": "Gericht op de specifieke touroperatormarkt in Engeland."}, {"id": "M001", "name": "Plusser", "domain": "plusser.nl", "cluster": "Media & senioren", "status": "In ontwikkeling", "priority": "Midden", "phase": "Seniorenplatform", "next": "Adverteerderspagina, bereik en koppelingen met seniorenbeurs/health afronden.", "note": "Onderdeel van het senioren- en 50-plusnetwerk."}, {"id": "M002", "name": "Zicht op Media", "domain": "zichtopmedia.nl", "cluster": "Media & senioren", "status": "In ontwikkeling", "priority": "Midden", "phase": "Media- en verkoopsite", "next": "Portfolio, bereik en proposities voor adverteerders helder presenteren.", "note": "Meer dan 100.000 social bereik zichtbaar maken."}, {"id": "M003", "name": "Senioren", "domain": "senioren.nl", "cluster": "Media & senioren", "status": "Concept", "priority": "Midden", "phase": "Doelgroepplatform", "next": "Rol, contentstructuur en koppeling met Plusser bepalen.", "note": "Autoriteitsdomein voor brede seniorendoelgroep."}, {"id": "M004", "name": "Seniorenbeurs", "domain": "seniorenbeurs.nl", "cluster": "Media & senioren", "status": "Concept", "priority": "Laag", "phase": "Event & content", "next": "Bepalen hoe events, partners en bereik in het netwerk landen.", "note": "Koppeling met Plusser en Zicht op Media."}, {"id": "M005", "name": "Health", "domain": "health.nl", "cluster": "Media & senioren", "status": "Concept", "priority": "Laag", "phase": "Gezondheid & lifestyle", "next": "Onderscheidende redactionele positie en doelgroepkoppeling bepalen.", "note": "Potentiële verbreding van het seniorennetwerk."}];
const INITIAL_UPDATES = [{"date": "2026-07-27", "project": "CRIO Next", "type": "Start", "text": "Afzonderlijke tool opgezet voor alle niet-Montferlandse projecten."}, {"date": "2026-07-27", "project": "Bestemmingsmanagement", "type": "Besluit nodig", "text": "Kiezen tussen losmaken van bestaande module of opnieuw beginnen."}, {"date": "2026-07-27", "project": "Marketing Golfweekend", "type": "Volgende stap", "text": "Versie staat klaar voor controle met Andy."}, {"date": "2026-07-27", "project": "Veluwe Marketing", "type": "Zoekactie", "text": "Laatste masterbestand en projectlocatie opnieuw vastleggen."}];
const INITIAL_DISCUSSIONS = [{"id": "D1", "title": "Bestemmingsmanagement opnieuw opbouwen?", "project": "Bestemmingsmanagement", "context": "De huidige versie lijkt verweven met de Montferland-werkomgeving.", "decision": "Losmaken en opschonen, of volledig opnieuw beginnen als zelfstandig zakelijk platform?", "done": false}, {"id": "D2", "title": "Rol van PA.nl en PO.nl", "project": "PA / PO", "context": "Beide domeinen kunnen als eigen doelgroepportal naast Directiesecretaresse en Personeelsvereniging functioneren.", "decision": "Nu ontwikkelen, parkeren, of eerst vanuit Businessbookers als rubriek testen?", "done": false}, {"id": "D3", "title": "Satellietsites Hanzesteden", "project": "Hanzesteden", "context": "Ontdek Zwolle, Harderwijk en Zutphen zijn gestart als kleinere varianten.", "decision": "Alle drie volledig uitbouwen of één schaalbaar sjabloon als vaste standaard maken?", "done": false}, {"id": "D4", "title": "Seniorennetwerk architectuur", "project": "Plusser / Senioren / Zicht op Media", "context": "Meerdere merken raken dezelfde doelgroep en commerciële propositie.", "decision": "Eén hoofdmerk kiezen en de overige domeinen als kanalen of labels positioneren?", "done": false}];
const STATUS_OPTIONS = ['Gepland','Concept','In ontwikkeling','Actief','Klaar voor review','Wacht op input','Gepauzeerd','Te herstarten','Terugzoeken','Afgerond','Gearchiveerd'];
const CLUSTERS = [{"name": "MICE & Businessbookers", "purpose": "Zakelijke bijeenkomsten, doelgroepen, locaties en gebiedsontwikkeling", "accent": "#2359d1"}, {"name": "Golf & leisure", "purpose": "Arrangementen, groepsreizen, communities en partneromzet", "accent": "#2c7a5d"}, {"name": "Bestemmingen & steden", "purpose": "Bestemmingsportalen, inspiratie en exact-match domeinen", "accent": "#b45a28"}, {"name": "Platform & portfolio", "purpose": "Autoriteitssites en samenhang van het totale netwerk", "accent": "#6746a5"}, {"name": "Innovatie & sectorportals", "purpose": "Toekomstgerichte portals voor sectoren en ecosystemen", "accent": "#117b8a"}, {"name": "Erfgoed & internationaal", "purpose": "Erfgoedconcepten en touroperatorgerichte varianten", "accent": "#8b6b28"}, {"name": "Media & senioren", "purpose": "Bereik, doelgroepmedia en adverteerdersproposities", "accent": "#a23c61"}, {"name": "CRIO & organisatie", "purpose": "Eigen studio, werkwijze en centrale regie", "accent": "#242a35"}];
const API_URL='api.php';
const CSRF_TOKEN=<?= json_encode(csrf_token(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
let state={projects:clone(INITIAL_PROJECTS),updates:clone(INITIAL_UPDATES),discussions:clone(INITIAL_DISCUSSIONS),weekNote:''};let editingId=null;let view='grid';let saveTimer=null;
function clone(x){return JSON.parse(JSON.stringify(x))}
function setSyncStatus(text,mode='ok'){const el=document.getElementById('syncStatus');if(!el)return;el.textContent=text;el.classList.toggle('saving',mode==='saving');el.classList.toggle('error',mode==='error')}
async function loadState(){setSyncStatus('Gegevens laden…','saving');try{const r=await fetch(API_URL,{credentials:'same-origin',headers:{'Accept':'application/json'}});if(r.status===401){location.reload();return}if(!r.ok)throw new Error('Laden mislukt');const data=await r.json();if(!Array.isArray(data.projects))throw new Error('Ongeldige gegevens');state={projects:data.projects,updates:Array.isArray(data.updates)?data.updates:[],discussions:Array.isArray(data.discussions)?data.discussions:[],weekNote:typeof data.weekNote==='string'?data.weekNote:''};setSyncStatus('Opgeslagen op server');renderAll()}catch(e){console.error(e);setSyncStatus('Serveropslag niet bereikbaar','error');renderAll();alert('CRIO Next kon de centrale gegevens niet laden. De beginstand wordt getoond, maar wijzigingen zijn pas veilig zodra de serververbinding hersteld is.')}}
async function persistState(){setSyncStatus('Opslaan…','saving');try{const r=await fetch(API_URL,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','Accept':'application/json','X-CSRF-Token':CSRF_TOKEN},body:JSON.stringify(state)});if(r.status===401){location.reload();return}const result=await r.json().catch(()=>({}));if(!r.ok||!result.ok)throw new Error(result.error||'Opslaan mislukt');setSyncStatus('Opgeslagen op server')}catch(e){console.error(e);setSyncStatus('Opslaan mislukt','error');alert('De wijziging kon niet op de server worden opgeslagen. Exporteer voor de zekerheid een back-up en controleer de installatie.')}}
function saveState(){renderAll();clearTimeout(saveTimer);saveTimer=setTimeout(persistState,180)}
function esc(s=''){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function uid(prefix){return prefix+Date.now().toString(36)+Math.random().toString(36).slice(2,6)}
function projectByName(name){return state.projects.find(p=>p.name===name)}
function statusClass(s){const v=(s||'').toLowerCase();if(v==='gepland')return 'gepland';if(v==='concept')return 'concept';if(v.includes('ontwikkeling'))return 'ontwikkeling';if(v==='actief')return 'actief';if(v.includes('review'))return 'review';if(v.includes('wacht'))return 'wacht';if(v.includes('gepauzeerd'))return 'gepauzeerd';if(v.includes('herstarten'))return 'herstarten';if(v.includes('terugzoeken'))return 'terugzoeken';if(v.includes('afgerond'))return 'afgerond';if(v.includes('gearchiveerd'))return 'gearchiveerd';return 'ontwikkeling'}
function priorityClass(p){return p==='Zeer hoog'?'zeer':''}
function filteredProjects(){const q=search.value.trim().toLowerCase(),c=clusterFilter.value,s=statusFilter.value,p=priorityFilter.value,f=focusFilter.value;return state.projects.filter(x=>(!q||[x.name,x.domain,x.cluster,x.status,x.phase,x.next,x.note].join(' ').toLowerCase().includes(q))&&(!c||x.cluster===c)&&(!s||x.status===s)&&(!p||x.priority===p)&&(!f||x.focus))}
function renderStats(){statProjects.textContent=state.projects.length;statActive.textContent=state.projects.filter(p=>/Actief|review/i.test(p.status)).length;statBuild.textContent=state.projects.filter(p=>/ontwikkeling|herstarten|terugzoeken/i.test(p.status)).length;statFocus.textContent=state.projects.filter(p=>p.focus).length;const open=state.discussions.filter(d=>!d.done).length;statDiscuss.textContent=open;navDiscuss.textContent=open;statClusters.textContent=new Set(state.projects.map(p=>p.cluster)).size}
function renderFocus(){const arr=state.projects.filter(p=>p.focus).sort((a,b)=>['Zeer hoog','Hoog','Midden','Laag'].indexOf(a.priority)-['Zeer hoog','Hoog','Midden','Laag'].indexOf(b.priority));focusList.innerHTML=arr.length?arr.map((p,i)=>`<div class="focus-row"><div class="focus-num">${i+1}</div><div><h3>${esc(p.name)}</h3><span class="cluster-pill">${esc(p.cluster)}</span></div><p>${esc(p.next)}</p><span class="priority-pill ${priorityClass(p.priority)}">${esc(p.priority)}</span></div>`).join(''):'<div class="notice">Nog geen projecten in de weekfocus.</div>'}
function renderClusters(){clusterGrid.innerHTML=CLUSTERS.map(c=>{const arr=state.projects.filter(p=>p.cluster===c.name);const focus=arr.filter(p=>p.focus).length;return`<article class="card cluster-card" style="--cluster:${c.accent}"><h3>${esc(c.name)}</h3><p>${esc(c.purpose)}</p><div class="big">${arr.length}</div><small>${focus} in weekfocus</small></article>`}).join('')}
function card(p){return`<article class="card project-card ${p.focus?'focus':''}"><div class="project-title"><div><h3>${esc(p.name)}</h3>${p.domain?`<span class="domain">${esc(p.domain)}</span>`:''}</div><button class="btn neutral small" onclick="editProject('${p.id}')">Bewerk</button></div><div><span class="cluster-pill">${esc(p.cluster)}</span> <span class="status-pill ${statusClass(p.status)}">${esc(p.status)}</span> <span class="priority-pill ${priorityClass(p.priority)}">${esc(p.priority)}</span></div><p><b>${esc(p.phase)}</b></p><p>${esc(p.note)}</p><div class="next"><b>Volgende actie</b><br>${esc(p.next)}</div><div class="actions"><button class="btn soft small" onclick="toggleFocus('${p.id}')">${p.focus?'Uit focus':'In focus'}</button><button class="btn neutral small" onclick="quickUpdate('${p.id}')">Update</button></div></article>`}
function renderProjects(){const arr=filteredProjects();projectGrid.innerHTML=arr.map(card).join('');projectTableBody.innerHTML=arr.map(p=>`<tr><td><b>${esc(p.name)}</b><br><span class="domain">${esc(p.domain||'—')}</span></td><td>${esc(p.cluster)}</td><td><span class="status-pill ${statusClass(p.status)}">${esc(p.status)}</span></td><td>${esc(p.priority)}</td><td>${esc(p.phase)}</td><td>${esc(p.next)}</td><td><button class="btn neutral small" onclick="editProject('${p.id}')">Bewerk</button></td></tr>`).join('');projectGrid.classList.toggle('hidden',view!=='grid');tableWrap.classList.toggle('hidden',view!=='table')}
function renderUpdateSelectors(){const current=updateProject.value;updateProject.innerHTML=state.projects.slice().sort((a,b)=>a.name.localeCompare(b.name)).map(p=>`<option value="${p.id}">${esc(p.name)}${p.domain?' · '+esc(p.domain):''}</option>`).join('');if(state.projects.some(p=>p.id===current))updateProject.value=current}
function renderTimeline(){timeline.innerHTML=state.updates.slice().reverse().slice(0,12).map(u=>`<article class="timeline-item"><div class="date">${esc(u.date)} · ${esc(u.type)}</div><h4>${esc(u.project)}</h4><p>${esc(u.text)}</p></article>`).join('')||'<div class="notice">Nog geen updates.</div>'}
function renderDiscussions(){discussionGrid.innerHTML=state.discussions.map((d,i)=>`<article class="card discuss-card ${d.done?'done':''}"><h3>${esc(d.title)}</h3><span class="cluster-pill">${esc(d.project)}</span><p>${esc(d.context)}</p><div class="decision"><b>Besluit nodig</b><br>${esc(d.decision)}</div><div class="actions" style="margin-top:12px"><button class="btn ${d.done?'neutral':'soft'} small" onclick="toggleDiscussion(${i})">${d.done?'Heropen':'Markeer besproken'}</button><button class="btn danger small" onclick="deleteDiscussion(${i})">Verwijder</button></div></article>`).join('')||'<div class="notice">Geen openstaande bespreekpunten.</div>'}
function renderAll(){renderStats();renderFocus();renderClusters();renderProjects();renderUpdateSelectors();renderTimeline();renderDiscussions();weekNote.value=state.weekNote||''}
function populateFilters(){CLUSTERS.forEach(c=>{clusterFilter.add(new Option(c.name,c.name));fCluster.add(new Option(c.name,c.name))});[...new Set([...STATUS_OPTIONS,...state.projects.map(p=>p.status).filter(Boolean)])].forEach(s=>statusFilter.add(new Option(s,s)))}
function openNewProject(){editingId=null;modalTitle.textContent='Nieuw project';deleteProject.classList.add('hidden');['fName','fDomain','fPhase','fNext','fNote'].forEach(id=>document.getElementById(id).value='');fCluster.value=CLUSTERS[0].name;fStatus.value='Gepland';fPriority.value='Midden';fFocus.checked=false;projectModal.classList.remove('hidden')}
function editProject(id){const p=state.projects.find(x=>x.id===id);if(!p)return;editingId=id;modalTitle.textContent=p.name;deleteProject.classList.remove('hidden');fName.value=p.name;fDomain.value=p.domain;fCluster.value=p.cluster;fStatus.value=p.status;fPriority.value=p.priority;fPhase.value=p.phase;fNext.value=p.next;fNote.value=p.note;fFocus.checked=!!p.focus;projectModal.classList.remove('hidden')}
function closeModal(id){document.getElementById(id).classList.add('hidden')}
function toggleFocus(id){const p=state.projects.find(x=>x.id===id);p.focus=!p.focus;saveState()}
function quickUpdate(id){updateProject.value=id;location.hash='updates';updateText.focus()}
function toggleDiscussion(i){state.discussions[i].done=!state.discussions[i].done;saveState()}
function deleteDiscussion(i){if(confirm('Dit bespreekpunt verwijderen?')){state.discussions.splice(i,1);saveState()}}
[search,clusterFilter,statusFilter,priorityFilter,focusFilter].forEach(el=>el.addEventListener('input',renderProjects));
gridViewBtn.onclick=()=>{view='grid';gridViewBtn.classList.add('active');tableViewBtn.classList.remove('active');renderProjects()};tableViewBtn.onclick=()=>{view='table';tableViewBtn.classList.add('active');gridViewBtn.classList.remove('active');renderProjects()};
saveWeekNote.onclick=()=>{state.weekNote=weekNote.value;saveState();weekSaveState.textContent='Notitie wordt centraal bewaard.';setTimeout(()=>weekSaveState.textContent='',1800)};clearWeekNote.onclick=()=>{weekNote.value='';state.weekNote='';saveState();weekSaveState.textContent='Notitie gewist.';};
saveProject.onclick=()=>{const data={id:editingId||uid('P'),name:fName.value.trim(),domain:fDomain.value.trim(),cluster:fCluster.value,status:fStatus.value||'Gepland',priority:fPriority.value,phase:fPhase.value.trim(),next:fNext.value.trim(),note:fNote.value.trim(),focus:fFocus.checked};if(!data.name)return alert('Vul een projectnaam in.');if(editingId){const i=state.projects.findIndex(p=>p.id===editingId);state.projects[i]=data}else state.projects.unshift(data);closeModal('projectModal');saveState()};
deleteProject.onclick=()=>{if(!editingId||!confirm('Project definitief uit CRIO Next verwijderen?'))return;state.projects=state.projects.filter(p=>p.id!==editingId);closeModal('projectModal');saveState()};
addUpdate.onclick=()=>{const p=state.projects.find(x=>x.id===updateProject.value),text=updateText.value.trim();if(!p||!text)return alert('Kies een project en schrijf een update.');state.updates.push({date:new Date().toLocaleDateString('nl-NL',{year:'numeric',month:'2-digit',day:'2-digit'}),project:p.name,type:updateType.value,text});if(updateType.value==='Afgerond')p.status='Afgerond';if(updateType.value==='Klaar voor review')p.status='Klaar voor review';updateText.value='';saveState()};
newDiscussion.onclick=()=>{dTitle.value='';dProject.value='';dContext.value='';dDecision.value='';discussionModal.classList.remove('hidden')};
saveDiscussion.onclick=()=>{if(!dTitle.value.trim()||!dDecision.value.trim())return alert('Vul minimaal titel en besluitvraag in.');state.discussions.unshift({id:uid('D'),title:dTitle.value.trim(),project:dProject.value.trim()||'Algemeen',context:dContext.value.trim(),decision:dDecision.value.trim(),done:false});closeModal('discussionModal');saveState()};
exportData.onclick=()=>{const blob=new Blob([JSON.stringify(state,null,2)],{type:'application/json'}),a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='CRIO-Next-backup-'+new Date().toISOString().slice(0,10)+'.json';a.click();URL.revokeObjectURL(a.href)};
importFile.onchange=e=>{const f=e.target.files[0];if(!f)return;const r=new FileReader();r.onload=()=>{try{const data=JSON.parse(r.result);if(!Array.isArray(data.projects))throw new Error();state=data;saveState();alert('Back-up geïmporteerd.')}catch(err){alert('Dit is geen geldige CRIO Next-back-up.')}};r.readAsText(f)};
resetData.onclick=()=>{if(!confirm('Alle lokale wijzigingen wissen en de beginstand herstellen?'))return;state={projects:clone(INITIAL_PROJECTS),updates:clone(INITIAL_UPDATES),discussions:clone(INITIAL_DISCUSSIONS),weekNote:''};saveState()};
document.querySelectorAll('.modal').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.add('hidden')}));
populateFilters();loadState();
</script>
</body></html>