const { useState, useEffect, useMemo, useCallback, Fragment } = React;

/* ============ HUISSTIJL ============ */
const NAVY="#0E1F3A", BRAND_ACCENT="#2563EB", ACCENT_DK="#1D4ED8";
const TARGET=4;

/* small helper: pick language string */
const L = (obj,lang)=> (obj==null?"":(typeof obj==="string"?obj:(obj[lang]??obj.nl??obj.en??"")));

/* ============ UI-TEKSTEN (nl/en) ============ */
const T = {
  pickTitle:{nl:"Resilience Maturity Matrix",en:"Resilience Maturity Matrix"},
  pickSub:{nl:"Kies welk model u wilt gebruiken. U kunt later altijd wisselen.",en:"Choose which model to use. You can switch at any time."},
  pickStart:{nl:"Start deze matrix",en:"Start this matrix"},
  pickFoot:{nl:"Self-assessment op basis van ISO-richtlijnen. Niveaus zijn richtinggevend en vervangen geen formele audit.",en:"Self-assessment based on ISO guidelines. Levels are indicative and do not replace a formal audit."},
  savedCount:{nl:"opgeslagen",en:"saved"},
  sector:{nl:"Sector",en:"Sector"},
  sectorPh:{nl:"Kies een sector\u2026",en:"Select a sector\u2026"},
  orgSize:{nl:"Organisatiegrootte",en:"Organisation size"},
  orgSizePh:{nl:"Aantal medewerkers\u2026",en:"Number of employees\u2026"},
  anonNote:{nl:"Anoniem: bij opslaan worden alleen uw scores, sector en organisatiegrootte anoniem meegenomen in de wereldwijde benchmark. Namen blijven in uw browser.",en:"Anonymous: when you save, only your scores, sector and organisation size are added to the global benchmark. Names stay in your browser."},
  tabAssess:{nl:"Assessment",en:"Assessment"},
  tabReport:{nl:"Rapport",en:"Report"},
  tabPlan:{nl:"Verbeterplan",en:"Improvement plan"},
  tabSaved:{nl:"Opgeslagen",en:"Saved"},
  tabCompare:{nl:"Vergelijken",en:"Compare"},
  otherModel:{nl:"Ander model",en:"Other model"},
  newAssessment:{nl:"Nieuwe assessment",en:"New assessment"},
  save:{nl:"Opslaan",en:"Save"},
  saved:{nl:"Opgeslagen",en:"Saved"},
  print:{nl:"PDF / Afdrukken",en:"PDF / Print"},
  printReport:{nl:"PDF rapport",en:"PDF report"},
  printPlan:{nl:"PDF verbeterplan",en:"PDF plan"},
  footer:{nl:"Self-assessment op basis van",en:"Self-assessment based on"},
  footerEnd:{nl:". Niveaus en verwijzingen zijn richtinggevend en vervangen geen formele audit.",en:". Levels and references are indicative and do not replace a formal audit."},
  guide:{nl:"Handleiding",en:"How to use"},
  guideIntro1:{nl:"Met deze tool brengt u in kaart hoe volwassen het",en:"This tool maps how mature your"},
  guideIntro2cm:{nl:"crisismanagement",en:"crisis management"},
  guideIntro2bcm:{nl:"business continuity management",en:"business continuity management"},
  guideIntro3:{nl:"van uw organisatie is, getoetst aan",en:"is, assessed against"},
  guideIntro4:{nl:"U scoort",en:"You score"},
  guideDomainOne:{nl:"domein",en:"domain"},
  guideDomainMany:{nl:"domeinen",en:"domains"},
  guideScale:{nl:"op een schaal van 1 (initieel) tot 5 (optimaal).",en:"on a scale from 1 (initial) to 5 (optimal)."},
  step1:{nl:"Vul de organisatiegegevens in en kies een ambitieniveau dat past bij uw sector en risicoprofiel.",en:"Enter the organisation details and choose an ambition level that fits your sector and risk profile."},
  step2:{nl:"Klik per domein op de omschrijving die het best past. Uw keuze kleurt mee.",en:"For each domain, click the description that fits best. Your choice is highlighted."},
  step3:{nl:"Sla op. Daarna kunt u in de andere tabbladen het rapport en het verbeterplan uitdraaien.",en:"Save. Then you can produce the report and improvement plan in the other tabs."},
  step4:{nl:"Herhaal de meting later en gebruik 'Vergelijken' om groei zichtbaar te maken.",en:"Repeat the assessment later and use 'Compare' to visualise progress."},
  guideTarget1:{nl:"Het streefniveau volgens",en:"The target level per"},
  guideTarget2:{nl:"is doorgaans",en:"is typically"},
  guideTarget3:{nl:"niveau 4 (Gevestigd)",en:"level 4 (Established)"},
  guideTarget4:{nl:". Niveau 5 is best-in-class.",en:". Level 5 is best-in-class."},
  dataAmbition:{nl:"Gegevens & ambitie",en:"Details & ambition"},
  org:{nl:"Organisatie",en:"Organisation"},
  orgPh:{nl:"Naam van de organisatie",en:"Name of the organisation"},
  date:{nl:"Datum",en:"Date"},
  assessName:{nl:"Naam / moment van assessment",en:"Assessment name / moment"},
  assessNamePh:{nl:'Bijv. "Q2 2026 – nulmeting"',en:'E.g. "Q2 2026 – baseline"'},
  ambition:{nl:"Ambitieniveau",en:"Ambition level"},
  domainsFilled:{nl:"domeinen ingevuld",en:"domains completed"},
  ofWord:{nl:"van",en:"of"},
  saveShowMatrix:{nl:"Opslaan & matrix tonen",en:"Save & show matrix"},
  introHint1:{nl:"Vul de gegevens in en klik op",en:"Enter the details and click"},
  introHint2:{nl:"U gaat dan naar een eigen",en:"You will go to a dedicated"},
  introHintMatrix:{nl:"matrixpagina",en:"matrix page"},
  introHint3:{nl:"waar u de",en:"where you score the"},
  introHint4:{nl:"scoort — en die u daar opnieuw kunt opslaan.",en:"— and which you can save again there."},
  directMatrix:{nl:"Direct naar de matrix",en:"Go straight to the matrix"},
  backToData:{nl:"Terug naar gegevens",en:"Back to details"},
  unnamed:{nl:"Naamloze assessment",en:"Untitled assessment"},
  orgUnknown:{nl:"organisatie onbekend",en:"organisation unknown"},
  matrixHint:{nl:"Klik in elke rij op de omschrijving die het best past. Nogmaals klikken wist de keuze. Op smalle schermen schuift de tabel horizontaal.",en:"In each row, click the description that fits best. Clicking again clears it. On narrow screens the table scrolls horizontally."},
  domain:{nl:"Domein",en:"Domain"},
  doneFillSave:{nl:"Klaar met invullen? Sla de assessment op om het rapport en verbeterplan te kunnen uitdraaien.",en:"Done? Save the assessment to produce the report and improvement plan."},
  reportEmpty:{nl:"Vul eerst minimaal één domein in op het tabblad",en:"First complete at least one domain on the"},
  reportTitle:{nl:"Volwassenheidsrapport",en:"Maturity report"},
  ambitionLevel:{nl:"Ambitieniveau",en:"Ambition level"},
  mgmtSummary:{nl:"Managementsamenvatting",en:"Executive summary"},
  priorities:{nl:"Prioriteiten (zwakste domeinen)",en:"Priorities (weakest domains)"},
  strengths:{nl:"Sterkste punten",en:"Strengths"},
  profile:{nl:"Volwassenheidsprofiel",en:"Maturity profile"},
  profileSub1:{nl:"Spinnenweb met per domein de gemeten score (blauw vlak) tegenover uw ambitieniveau",en:"Radar showing the measured score per domain (blue area) against your ambition level"},
  profileSub2:{nl:"(donkere stippellijn).",en:"(dark dashed line)."},
  avgScore:{nl:"Gemiddelde score",en:"Average score"},
  totalScore:{nl:"Totaalscore",en:"Total score"},
  ratedDomains:{nl:"Beoordeelde domeinen",en:"Domains rated"},
  ambitionShort:{nl:"Ambitie",en:"Ambition"},
  measuredLevel:{nl:"Gemeten niveau",en:"Measured level"},
  perDomainScore:{nl:"Score per domein",en:"Score per domain"},
  perDomainSub:{nl:"Het streepje markeert uw ambitieniveau",en:"The marker indicates your ambition level"},
  avgPerSection:{nl:"Gemiddelde per sectie",en:"Average per section"},
  resultPerDomain:{nl:"Resultaat per domein",en:"Result per domain"},
  belowAmbition:{nl:"onder ambitie",en:"below ambition"},
  chosenLevel:{nl:"Gekozen niveau",en:"Chosen level"},
  improveAction:{nl:"Verbeteractie → niveau",en:"Improvement action → level"},
  highestReached:{nl:"Hoogste niveau bereikt — borg en deel best practices.",en:"Highest level reached — embed and share best practices."},
  planTitle:{nl:"Verbeterplan & routekaart",en:"Improvement plan & roadmap"},
  heatmapTitle:{nl:"Actie-prioriteitenmatrix",en:"Action priority matrix"},
  heatmapSub:{nl:"Acties geplaatst op een indicatieve tijdlijn (X: wat eerst, wat later) en prioriteit (Y: hoog bovenaan). Linksboven = nu oppakken, hoge prioriteit. Klik een actie voor details.",en:"Actions placed on an indicative timeline (X: what first, what later) and priority (Y: high at the top). Top-left = act now, high priority. Click an action for details."},
  axisTime:{nl:"Tijdlijn (indicatief) →",en:"Timeline (indicative) →"},
  axisPriority:{nl:"Prioriteit →",en:"Priority →"},
  timeNow:{nl:"Nu",en:"Now"},
  timeSoon:{nl:"Binnenkort",en:"Soon"},
  timeLater:{nl:"Later",en:"Later"},
  timeNowSub:{nl:"0–3 mnd",en:"0–3 mo"},
  timeSoonSub:{nl:"3–9 mnd",en:"3–9 mo"},
  timeLaterSub:{nl:"9–18 mnd",en:"9–18 mo"},
  prioHigh:{nl:"Hoog",en:"High"},
  prioMed:{nl:"Midden",en:"Medium"},
  prioLow:{nl:"Laag",en:"Low"},
  heatNotRated:{nl:"Niet beoordeeld",en:"Not rated"},
  heatAtAmbition:{nl:"Op/boven ambitie",en:"At/above ambition"},
  firstActionLabel:{nl:"Eerste actie:",en:"First action:"},
  emptyCell:{nl:"—",en:"—"},
  planBasedOn:{nl:"Gebaseerd op:",en:"Based on:"},
  currentAssessment:{nl:"huidige assessment",en:"current assessment"},
  ambitionLevelWord:{nl:"ambitieniveau",en:"ambition level"},
  print2:{nl:"Afdrukken",en:"Print"},
  domainsBelowAmbition:{nl:"Domeinen onder ambitie",en:"Domains below ambition"},
  stepsToLevel:{nl:"Verbeterstappen tot niveau",en:"Improvement steps to level"},
  prioActionsDone:{nl:"Prioriteitsacties afgerond",en:"Priority actions done"},
  toRate:{nl:"Nog te beoordelen",en:"Still to rate"},
  beginHere:{nl:"Begin hier — de 3 belangrijkste eerste acties",en:"Start here — the 3 most important first actions"},
  beginHereSub:{nl:"De zwakste domeinen ten opzichte van uw ambitie.",en:"The weakest domains relative to your ambition."},
  allAtAmbition1:{nl:"Alle beoordeelde domeinen zijn op of boven uw ambitieniveau",en:"All rated domains are at or above your ambition level"},
  allAtAmbition2:{nl:"Mooi resultaat. Houd het op peil via oefeningen en herbeoordeling — of verhoog uw ambitie.",en:"Great result. Maintain it through exercises and re-assessment — or raise your ambition."},
  startHereCard:{nl:"Start hier",en:"Start here"},
  thenCard:{nl:"Daarna",en:"Then"},
  stepsLeft1:{nl:"Nog",en:""},
  stepsLeft2:{nl:"stappen tot ambitie",en:"steps to ambition remaining"},
  rateFirst:{nl:"Eerst beoordelen",en:"Rate first"},
  rateFirstSub:{nl:"Deze domeinen zijn nog niet gescoord.",en:"These domains have not been scored yet."},
  planEmpty:{nl:"Vul eerst (een deel van) de assessment in.",en:"First complete (part of) the assessment."},
  phase1Title:{nl:"Fase 1 — Direct starten (fundament)",en:"Phase 1 — Start now (foundation)"},
  phase1Desc:{nl:"Domeinen op niveau 1–2: het hoogste risico.",en:"Domains at level 1–2: the highest risk."},
  phase2Title:{nl:"Fase 2 — Doorpakken naar ambitie",en:"Phase 2 — Push to ambition"},
  phase2Desc:{nl:"Domeinen op niveau 3: nog stappen naar uw ambitieniveau.",en:"Domains at level 3: steps remaining to your ambition."},
  phase3Title:{nl:"Fase 3 — Laatste stap & borgen",en:"Phase 3 — Final step & embed"},
  phase3Desc:{nl:"Domeinen vlak onder de ambitie: afronden en verankeren.",en:"Domains just below ambition: finish and embed."},
  horizon1:{nl:"0–3 maanden",en:"0–3 months"},
  horizon2:{nl:"3–9 maanden",en:"3–9 months"},
  horizon3:{nl:"9–18 maanden",en:"9–18 months"},
  doneWord:{nl:"afgerond",en:"done"},
  savedEmpty:{nl:"Nog geen opgeslagen assessments. Vul de gegevens in en klik op Opslaan.",en:"No saved assessments yet. Enter the details and click Save."},
  selectToCompare:{nl:"Selecteer 2 of meer assessments om te vergelijken.",en:"Select 2 or more assessments to compare."},
  compareSelected:{nl:"Vergelijk geselecteerde",en:"Compare selected"},
  open:{nl:"Openen",en:"Open"},
  rename:{nl:"Hernoemen",en:"Rename"},
  remove:{nl:"Verwijderen",en:"Remove"},
  removeQ:{nl:"Verwijderen?",en:"Remove?"},
  yes:{nl:"Ja",en:"Yes"},
  no:{nl:"Nee",en:"No"},
  noScores:{nl:"geen scores",en:"no scores"},
  compareEmpty:{nl:"Selecteer 2 of meer assessments op het tabblad",en:"Select 2 or more assessments on the"},
  avgOverTime:{nl:"Gemiddelden — voortgang over tijd",en:"Averages — progress over time"},
  vsPrev:{nl:"t.o.v. vorige",en:"vs previous"},
  scoresPerDomain:{nl:"Scores per domein",en:"Scores per domain"},
  deltaExpl:{nl:"Verandering = laatste t.o.v. eerste.",en:"Change = latest vs first."},
  levelInitial:{nl:"Initieel",en:"Initial"},
  levelRepeatable:{nl:"Herhaalbaar",en:"Repeatable"},
  levelDefined:{nl:"Gedefinieerd",en:"Defined"},
  levelEstablished:{nl:"Gevestigd",en:"Established"},
  levelOptimal:{nl:"Optimaal",en:"Optimal"},
  isoStreef:{nl:"streefniveau",en:"target level"},
  passendVoor:{nl:"Wat dit niveau inhoudt",en:"What this level means"},
  sectors:{nl:"Afweging: ",en:"Consideration: "},
  lockTitle:{nl:"Toegang",en:"Access"},
  lockSub:{nl:"Voer het wachtwoord in om de matrix te openen.",en:"Enter the password to open the matrix."},
  lockPlaceholder:{nl:"Wachtwoord",en:"Password"},
  lockButton:{nl:"Openen",en:"Unlock"},
  lockError:{nl:"Onjuist wachtwoord. Probeer het opnieuw.",en:"Incorrect password. Please try again."},
  highRisk:{nl:"hoog risico",en:"high risk"},
  basis:{nl:"basisniveau",en:"basic level"},
  sufficient:{nl:"voldoende",en:"sufficient"},
  recommended:{nl:"aanbevolen conform ISO 22361",en:"recommended per ISO 22361"},
  bestInClass:{nl:"best-in-class",en:"best-in-class"},
};

const LEVEL_LABELS = {
  1:{nl:"Initieel",en:"Initial"},2:{nl:"Herhaalbaar",en:"Repeatable"},3:{nl:"Gedefinieerd",en:"Defined"},4:{nl:"Gevestigd",en:"Established"},5:{nl:"Optimaal",en:"Optimal"}
};
const LEVEL_META = {
  1:{text:"text-red-700",bg:"bg-red-50",border:"border-red-300",chip:"bg-red-100 text-red-800",hex:"#dc2626"},
  2:{text:"text-orange-700",bg:"bg-orange-50",border:"border-orange-300",chip:"bg-orange-100 text-orange-800",hex:"#ea580c"},
  3:{text:"text-amber-700",bg:"bg-amber-50",border:"border-amber-300",chip:"bg-amber-100 text-amber-800",hex:"#d97706"},
  4:{text:"text-teal-700",bg:"bg-teal-50",border:"border-teal-300",chip:"bg-teal-100 text-teal-800",hex:"#0d9488"},
  5:{text:"text-emerald-700",bg:"bg-emerald-50",border:"border-emerald-300",chip:"bg-emerald-100 text-emerald-800",hex:"#059669"},
};

const MATURITY_EXPLAINER = {
  1:{headline:{nl:"Ad hoc en persoonsafhankelijk",en:"Ad hoc and person-dependent"},desc:{nl:"Crisismanagement bestaat nog niet als proces. Er wordt pas gehandeld wanneer een crisis zich al voltrekt, en de aanpak leunt volledig op individuele medewerkers. Er is geen vastgelegd beleid, geen rolverdeling en geen documentatie.",en:"Crisis management does not yet exist as a process. Action is only taken once a crisis is already unfolding, relying entirely on individuals. There is no documented policy, role allocation or documentation."},signals:{nl:["Geen vastgelegd beleid of plan","Reactief: handelen pas als het misgaat","Sterk afhankelijk van enkele personen","Onvoorspelbaar en niet herhaalbaar"],en:["No documented policy or plan","Reactive: act only when things go wrong","Heavily dependent on a few people","Unpredictable and not repeatable"]}},
  2:{headline:{nl:"Basis aanwezig, nog niet geborgd",en:"Basics in place, not yet embedded"},desc:{nl:"Er zijn eerste afspraken en enkele sleutelpersonen met kennis. Een vergelijkbare crisis kan vergelijkbaar worden aangepakt, maar veel gebeurt informeel. Vastlegging is beperkt en niet organisatiebreed gedragen.",en:"There are initial agreements and a few knowledgeable key people. A similar crisis can be handled similarly, but much remains informal. Documentation is limited and not organisation-wide."},signals:{nl:["Eerste afspraken en basisdocumenten","Kennis bij enkele sleutelpersonen","Informele werkwijze, beperkt vastgelegd","Nog geen structurele middelen of training"],en:["Initial agreements and basic documents","Knowledge held by a few key people","Informal way of working, lightly documented","No structural resources or training yet"]}},
  3:{headline:{nl:"Geformaliseerd en organisatiebreed",en:"Formalised and organisation-wide"},desc:{nl:"Beleid, rollen, plannen en procedures zijn formeel vastgelegd en organisatiebreed bekend. Een herkenbaar proces met eigenaarschap en managementgoedkeuring — maar nog niet structureel beoefend of aantoonbaar verbeterd.",en:"Policy, roles, plans and procedures are formally documented and known across the organisation. A recognisable process with ownership and management approval — but not yet structurally exercised or demonstrably improved."},signals:{nl:["Formeel beleid, rollen en plannen","Organisatiebreed bekend en gedragen","Managementgoedkeuring aanwezig","Nog beperkt beoefend en getoetst"],en:["Formal policy, roles and plans","Known and supported organisation-wide","Management approval in place","Still limited exercising and testing"]}},
  4:{headline:{nl:"Geborgd, beoefend en aantoonbaar",en:"Embedded, exercised and demonstrable"},desc:{nl:"Crisismanagement is verankerd. Plannen worden regelmatig geoefend en geactualiseerd, het team functioneert aantoonbaar onder druk en er wordt gestuurd op KPI's en review. Het door ISO aanbevolen streefniveau.",en:"Crisis management is embedded. Plans are regularly exercised and updated, the team demonstrably performs under pressure, and KPIs and reviews drive it. The ISO-recommended target level."},signals:{nl:["Regelmatig oefenen en actualiseren","Aantoonbaar functionerend team onder druk","Sturing op KPI's en periodieke review","Bestand tegen personeelswisselingen"],en:["Regular exercising and updating","Team demonstrably performs under pressure","Driven by KPIs and periodic review","Resilient to staff changes"]}},
  5:{headline:{nl:"Continu lerend en cultuurgedragen",en:"Continuously learning, culture-driven"},desc:{nl:"Crisismanagement zit in het DNA van de organisatie. Er wordt continu en data-gedreven verbeterd, plannen bewegen mee met nieuwe signalen, en de organisatie is normstellend voor de sector.",en:"Crisis management is part of the organisation's DNA. Improvement is continuous and data-driven, plans evolve with new signals, and the organisation sets the standard for its sector."},signals:{nl:["Continue, data-gedreven verbetering","Verankerd in cultuur en besluitvorming","Proactief en voorspellend","Normstellend voor keten en sector"],en:["Continuous, data-driven improvement","Embedded in culture and decision-making","Proactive and predictive","Sets the standard for sector and chain"]}},
};

const AMBITION_LEVELS = {
  1:{desc:{nl:"…een laag risicoprofiel hebben en waar uitval beperkte gevolgen heeft. Een crisis is zeldzaam en de impact klein, dus zware voorbereiding staat niet in verhouding tot het risico. Doorgaans een tijdelijk startpunt.",en:"…have a low risk profile where disruption has limited consequences. A crisis is rare and the impact small, so heavy preparation would be disproportionate. Usually a temporary starting point."},examples:{nl:"kleine MKB-bedrijven, lokale dienstverleners en starters zonder wettelijke continuïteitseisen.",en:"small businesses, local service providers and start-ups without statutory continuity requirements."}},
  2:{desc:{nl:"…een beperkte maatschappelijke rol hebben en waar een verstoring vervelend maar overkomelijk is. De afhankelijkheid is niet kritiek, dus basisafspraken en enkele sleutelpersonen volstaan.",en:"…have a limited societal role where disruption is inconvenient but manageable. Dependency is not critical, so basic agreements and a few key people suffice."},examples:{nl:"middelgrote MKB, retail en niet-kritische dienstverlening.",en:"mid-sized businesses, retail and non-critical services."}},
  3:{desc:{nl:"…klantverplichtingen, reputatierisico of toezicht kennen, waardoor improviseren te risicovol is. Bij uitval ontstaan reële gevolgen, dus beleid, rollen en plannen moeten formeel vastliggen. Minimaal verwacht niveau voor grotere organisaties.",en:"…have customer obligations, reputational risk or oversight, making improvisation too risky. Disruption causes real consequences, so policy, roles and plans must be formalised. The minimum expected level for larger organisations."},examples:{nl:"grotere bedrijven, zorginstellingen, onderwijs en gemeenten.",en:"larger companies, healthcare institutions, education and municipalities."}},
  4:{desc:{nl:"…een maatschappelijke of ketenrol vervullen waarbij uitval anderen direct raakt. Plannen moeten beoefend, geactualiseerd en aantoonbaar werkend zijn, ook als sleutelpersonen wegvallen. Het door ISO aanbevolen streefniveau.",en:"…fulfil a societal or supply-chain role where disruption directly affects others. Plans must be exercised, updated and demonstrably effective, even if key people leave. The ISO-recommended target level."},examples:{nl:"financiële instellingen, energie, telecom, ziekenhuizen, overheid en andere vitale sectoren.",en:"financial institutions, energy, telecom, hospitals, government and other critical sectors."}},
  5:{desc:{nl:"…een rol vervullen waar falen onaanvaardbaar is of die normstellend wil zijn. Een verstoring kan grote maatschappelijke gevolgen hebben, dus voorbereiding is proactief, data-gedreven en doorlopend lerend.",en:"…fulfil a role where failure is unacceptable or that aims to set the standard. Disruption can have major societal consequences, so preparation is proactive, data-driven and continuously learning."},examples:{nl:"nationale vitale infrastructuur, luchtvaart, nucleaire sector, systeembanken en crisis-georiënteerde overheden.",en:"national critical infrastructure, aviation, nuclear, systemic banks and crisis-oriented governments."}},
};

function maturityVerdict(avg,lang){
  if(avg==null) return null;
  const a=Math.round(avg*10)/10;
  const mk=(lvl,subKey,emoji)=>({label:L(LEVEL_LABELS[lvl],lang),sub:L(T[subKey],lang),emoji,...LEVEL_META[lvl]});
  if(a<2) return mk(1,"highRisk","🔴");
  if(a<3) return mk(2,"basis","🟠");
  if(a<4) return mk(3,"sufficient","🟡");
  if(a<5) return mk(4,"recommended","🟢");
  return mk(5,"bestInClass","✅");
}

/* ============ MODEL 22361 — domeinen (tweetalig) ============ */
const SECTIONS_22361 = [
 {id:"A",title:{nl:"Framework & Governance",en:"Framework & Governance"},domains:[
  {id:"A1",code:"A1",name:{nl:"Governance & beleid",en:"Governance & policy"},iso:"§4.5.2, §5.2.2",
   levels:[{nl:"Geen crisismanagementbeleid. Ad hoc aanpak bij incidenten.",en:"No crisis management policy. Ad hoc approach to incidents."},{nl:"Basisafspraken / beperkt beleid; weinig middelen beschikbaar.",en:"Basic agreements / limited policy; few resources available."},{nl:"Formeel crisismanagementbeleid + doelstellingen; managementgoedkeuring aanwezig.",en:"Formal crisis management policy + objectives; management approval in place."},{nl:"Actief toezicht door top management; KPI's en review-ritme geborgd. Budget structureel toegewezen.",en:"Active oversight by top management; KPIs and review cadence embedded. Structural budget allocated."},{nl:"Data-gedreven governance; continue evaluatie en aanpassing; crisismanagement in DNA organisatie.",en:"Data-driven governance; continuous evaluation and adjustment; crisis management in the organisation's DNA."}],
   firstSteps:[{nl:"Beleg crisismanagement bij één bestuurlijk eigenaar en zet een eerste beleidsuitgangspunt op papier (1 A4).",en:"Assign crisis management to one executive owner and put a first policy principle on paper (1 page)."},{nl:"Laat het beleid formeel vaststellen door de directie en koppel er meetbare doelen aan.",en:"Have the policy formally approved by the board and attach measurable goals."},{nl:"Stel KPI's en een vast review-ritme in en reserveer structureel budget.",en:"Set KPIs and a fixed review cadence and reserve structural budget."},{nl:"Voer periodieke bestuurlijke evaluatie in op basis van data en oefenresultaten.",en:"Introduce periodic board-level evaluation based on data and exercise results."}]},
  {id:"A2",code:"A2",name:{nl:"Rollen & verantwoordelijkheden (incl. CMT-structuur)",en:"Roles & responsibilities (incl. CMT structure)"},iso:"§5.2.3, §5.3.4.5",
   levels:[{nl:"Onduidelijk wie wat doet. Geen formele CMT.",en:"Unclear who does what. No formal crisis team."},{nl:"Woordvoerder / contactpersoon bekend; back-up beperkt. CMT informeel.",en:"Spokesperson / contact known; limited back-up. Crisis team informal."},{nl:"RACI-matrix aanwezig; CMT formeel benoemd met vervangers en akkoordlijn.",en:"RACI matrix present; crisis team formally appointed with deputies and approval line."},{nl:"Getrainde CMT-pool; opschalingsrooster; primair & alternatief per rol.",en:"Trained crisis-team pool; escalation roster; primary & alternate per role."},{nl:"Schaalbare CMT incl. externe experts; continue coaching; formele organisatiestructuur.",en:"Scalable crisis team incl. external experts; continuous coaching; formal structure."}],
   firstSteps:[{nl:"Wijs nu een crisismanager en een woordvoerder aan, mét naam en telefoonnummer.",en:"Appoint a crisis manager and spokesperson now, with name and phone number."},{nl:"Maak een RACI-matrix en benoem het CMT formeel, inclusief vervangers.",en:"Create a RACI matrix and formally appoint the crisis team, including deputies."},{nl:"Train de CMT-pool en stel een opschalingsrooster op met primair én alternatief per rol.",en:"Train the team pool and set up an escalation roster with primary and alternate per role."},{nl:"Voeg externe experts toe aan de pool en organiseer doorlopende coaching.",en:"Add external experts to the pool and arrange continuous coaching."}]},
  {id:"A3",code:"A3",name:{nl:"Samenwerking & afstemming crisisorganisatie",en:"Collaboration & alignment of the crisis organisation"},iso:"§5.2.3, §5.3.4.5, §6.3, §7.2",
   levels:[{nl:"Onderdelen werken los van elkaar; geen afstemming tussen strategisch, tactisch en operationeel niveau.",en:"Units work in isolation; no alignment between strategic, tactical and operational levels."},{nl:"Incidentele afstemming; koppelvlakken en escalatielijn tussen niveaus niet vastgelegd.",en:"Occasional alignment; interfaces and escalation lines between levels not documented."},{nl:"Samenwerkingsstructuur beschreven: koppelvlakken, mandaten en escalatielijnen vastgelegd; afstemming met externe partners benoemd.",en:"Collaboration structure described: interfaces, mandates and escalation lines documented; alignment with external partners defined."},{nl:"Samenwerking beoefend over niveaus én met ketenpartners; rolverdeling per scenariotype uitgewerkt.",en:"Collaboration exercised across levels and with chain partners; role allocation worked out per scenario type."},{nl:"Naadloze, ingespeelde samenwerking; dynamisch op- en afschalen per scenario; continue afstemming met externe keten.",en:"Seamless, well-rehearsed collaboration; dynamic scaling per scenario; continuous alignment with the external chain."}],
   firstSteps:[{nl:"Teken in één schema hoe strategisch, tactisch en operationeel niveau zich tot elkaar verhouden.",en:"Draw, in one diagram, how the strategic, tactical and operational levels relate."},{nl:"Leg koppelvlakken, mandaten en escalatielijnen tussen de niveaus schriftelijk vast.",en:"Document the interfaces, mandates and escalation lines between the levels."},{nl:"Beoefen de samenwerking over de niveaus heen, samen met je belangrijkste ketenpartners.",en:"Exercise collaboration across levels, together with your key chain partners."},{nl:"Werk per scenariotype de rolverdeling uit en evalueer de samenwerking structureel.",en:"Work out role allocation per scenario type and evaluate collaboration structurally."}]},
  {id:"A4",code:"A4",name:{nl:"Cultuur & competenties",en:"Culture & competencies"},iso:"§5.2.4, §5.2.5",
   levels:[{nl:"Geen bewustzijn; crisismanagement niet erkend als prioriteit.",en:"No awareness; crisis management not recognised as a priority."},{nl:"Beperkt bewustzijn; enkele sleutelpersonen beschikken over kennis.",en:"Limited awareness; a few key people hold knowledge."},{nl:"Organisatiebrede bewustzijnscampagne; competentieprofielen voor crisisrollen beschreven.",en:"Organisation-wide awareness campaign; competency profiles for crisis roles described."},{nl:"Crisismanagement geïntegreerd in onboarding en beoordelingscyclus; actieve risicobewustzijnscultuur.",en:"Crisis management integrated into onboarding and appraisal cycle; active risk-aware culture."},{nl:"Crisismanagement onderdeel van organisatiecultuur en -waarden; continue kennisdeling.",en:"Crisis management part of culture and values; continuous knowledge sharing."}],
   firstSteps:[{nl:"Benoem crisismanagement één keer expliciet in een teamoverleg of nieuwsbrief om bewustzijn te starten.",en:"Mention crisis management explicitly once in a team meeting or newsletter to start awareness."},{nl:"Beschrijf competentieprofielen voor de crisisrollen en start een bewustzijnscampagne.",en:"Describe competency profiles for the crisis roles and start an awareness campaign."},{nl:"Neem crisismanagement op in onboarding en de beoordelingscyclus.",en:"Include crisis management in onboarding and the appraisal cycle."},{nl:"Borg continue kennisdeling en maak risicobewustzijn onderdeel van de waarden.",en:"Embed continuous knowledge sharing and make risk-awareness part of the values."}]},
 ]},
 {id:"B",title:{nl:"Anticipatie & Voorbereiding",en:"Anticipation & Preparation"},domains:[
  {id:"B1",code:"B1",name:{nl:"Anticipatie & crisisherkenning",en:"Anticipation & crisis detection"},iso:"§5.3.1, §5.3.2",
   levels:[{nl:"Geen horizon scanning. Crises worden pas herkend wanneer ze al spelen.",en:"No horizon scanning. Crises are only recognised once underway."},{nl:"Ad hoc monitoring; beperkte risicoherkenning; geen formeel escalatieproces.",en:"Ad hoc monitoring; limited risk detection; no formal escalation process."},{nl:"Systematisch horizon scanning-proces; formele risicobeoordeling; escalatieprocedure beschreven.",en:"Systematic horizon-scanning process; formal risk assessment; escalation procedure described."},{nl:"Geïntegreerde early warning-systemen; doorlopende risicobeoordeling; tijdige escalatie.",en:"Integrated early-warning systems; ongoing risk assessment; timely escalation."},{nl:"Geavanceerde predictieve analyses; real-time crisisherkenning; proactief crisispreventie.",en:"Advanced predictive analytics; real-time detection; proactive prevention."}],
   firstSteps:[{nl:"Maak een korte lijst van signalen die op een dreigende crisis wijzen en wie ze in de gaten houdt.",en:"Make a short list of signals indicating an emerging crisis and who watches them."},{nl:"Richt een systematisch horizon scanning-proces in en beschrijf een escalatieprocedure.",en:"Set up a systematic horizon-scanning process and describe an escalation procedure."},{nl:"Koppel early warning aan doorlopende risicobeoordeling met tijdige escalatie.",en:"Link early warning to ongoing risk assessment with timely escalation."},{nl:"Voeg predictieve analyses en real-time herkenning toe.",en:"Add predictive analytics and real-time detection."}]},
  {id:"B2",code:"B2",name:{nl:"Preventie & mitigatie",en:"Prevention & mitigation"},iso:"§5.3.3",
   levels:[{nl:"Geen preventie- of mitigatiemaatregelen; uitsluitend reactief.",en:"No prevention or mitigation measures; purely reactive."},{nl:"Enkele losse maatregelen; niet gekoppeld aan risico's.",en:"A few isolated measures; not linked to risks."},{nl:"Maatregelen vastgelegd en gekoppeld aan risicobeoordeling; organisatorische, technische en menselijke barrières.",en:"Measures documented and linked to risk assessment; organisational, technical and human barriers."},{nl:"Preventie geïntegreerd met ERM/BCM; maatregelen periodiek getoetst; restrisico bewaakt.",en:"Prevention integrated with ERM/BCM; measures periodically tested; residual risk monitored."},{nl:"Proactieve, data-gedreven preventie; continue optimalisatie; verankerd in investeringskeuzes.",en:"Proactive, data-driven prevention; continuous optimisation; embedded in investment choices."}],
   firstSteps:[{nl:"Inventariseer voor je top-3 risico's welke maatregelen je nu al hebt — en waar gaten zitten.",en:"For your top-3 risks, inventory the measures you already have — and where the gaps are."},{nl:"Leg maatregelen vast en koppel ze aan de risicobeoordeling, met organisatorische, technische én menselijke barrières.",en:"Document measures and link them to the risk assessment, with organisational, technical and human barriers."},{nl:"Integreer preventie met ERM/BCM en toets maatregelen periodiek op werking.",en:"Integrate prevention with ERM/BCM and test measures periodically for effectiveness."},{nl:"Maak preventie data-gedreven en verankerd in investeringskeuzes.",en:"Make prevention data-driven and embedded in investment choices."}]},
  {id:"B3",code:"B3",name:{nl:"Scenariodenken & scenario-analyse",en:"Scenario thinking & analysis"},iso:"§5.3.2, §5.3.3, §9.3",note:{nl:"Let op: volgens ISO 22361 §5.3.4.2 bevat het crisismanagementplan (CMP) zélf géén scenario's.",en:"Note: per ISO 22361 §5.3.4.2 the crisis management plan (CMP) itself contains no scenarios."},
   levels:[{nl:"Geen scenariodenken; geen zicht op mogelijke crisistypen of impactketens.",en:"No scenario thinking; no view of possible crisis types or impact chains."},{nl:"Enkele scenario's informeel benoemd; niet gestructureerd.",en:"A few scenarios named informally; not structured."},{nl:"Set realistische scenario's vastgelegd incl. impact en aannames; basis voor planvorming en oefeningen.",en:"Set of realistic scenarios documented incl. impact and assumptions; basis for planning and exercises."},{nl:"Scenario's periodiek geactualiseerd; worst-case en cascade-effecten meegenomen.",en:"Scenarios periodically updated; worst-case and cascade effects included."},{nl:"Dynamische scenariobibliotheek; continue actualisatie; verankerd in strategische besluitvorming.",en:"Dynamic scenario library; continuous updating; embedded in strategic decision-making."}],
   firstSteps:[{nl:"Benoem 3 realistische crisisscenario's voor je organisatie en schrijf per scenario de impact op.",en:"Name 3 realistic crisis scenarios for your organisation and write down the impact of each."},{nl:"Leg een onderbouwde set scenario's vast op basis van je risicobeoordeling en gebruik ze voor oefeningen.",en:"Document a substantiated set of scenarios based on your risk assessment and use them for exercises."},{nl:"Actualiseer scenario's periodiek inclusief worst-case en keteneffecten.",en:"Update scenarios periodically including worst-case and chain effects."},{nl:"Bouw een dynamische scenariobibliotheek die op signalen meebeweegt.",en:"Build a dynamic scenario library that responds to signals."}]},
  {id:"B4",code:"B4",name:{nl:"Crisismanagementplan (CMP) & informatiemanagement",en:"Crisis management plan (CMP) & information management"},iso:"§5.3.4.2, §5.3.4.3, §5.3.4.4",
   levels:[{nl:"Geen CMP. Geen informatiemanagementproces.",en:"No CMP. No information management process."},{nl:"Basisplan aanwezig maar niet getest; contactlijsten deels beschikbaar.",en:"Basic plan present but untested; contact lists partly available."},{nl:"Volledig CMP met activeringsprotocol, escalatieprocedures, templates; versiebeheer aanwezig.",en:"Full CMP with activation protocol, escalation procedures, templates; version control in place."},{nl:"CMP regelmatig getest en bijgewerkt; situational awareness actief; offline toegang gewaarborgd.",en:"CMP regularly tested and updated; situational awareness active; offline access ensured."},{nl:"Dynamisch CMP met real-time updates; geavanceerde tools; volledig getest en gevalideerd.",en:"Dynamic CMP with real-time updates; advanced tools; fully tested and validated."}],
   firstSteps:[{nl:"Stel een basis-CMP op met activeringsprotocol en een actuele contactlijst — al is het kort.",en:"Draft a basic CMP with an activation protocol and an up-to-date contact list — even if short."},{nl:"Breid uit naar een volledig CMP met escalatieprocedures, templates en versiebeheer.",en:"Expand to a full CMP with escalation procedures, templates and version control."},{nl:"Test het CMP regelmatig, houd het bij en borg offline toegang.",en:"Test the CMP regularly, keep it current and ensure offline access."},{nl:"Maak het CMP dynamisch met real-time updates en gevalideerde tooling.",en:"Make the CMP dynamic with real-time updates and validated tooling."}]},
  {id:"B5",code:"B5",name:{nl:"Distributie & actualiteit van plannen & contactgegevens",en:"Distribution & currency of plans & contact data"},iso:"§5.3.4.2, §5.3.4.5, §5.3.4.6",
   levels:[{nl:"Plannen en contactlijsten niet actief verspreid; versies onbekend.",en:"Plans and contact lists not actively distributed; versions unknown."},{nl:"Verspreiding op aanvraag; actualiteit niet geborgd; contactgegevens verouderen.",en:"Distribution on request; currency not ensured; contact data goes stale."},{nl:"Vastgelegd distributieproces: juiste versie bij juiste rol; contactgegevens periodiek gevalideerd.",en:"Documented distribution process: right version to right role; contact data periodically validated."},{nl:"Actieve push van actuele plannen en contactdata; offline/redundante toegang geborgd.",en:"Active push of current plans and contact data; offline/redundant access ensured."},{nl:"Geautomatiseerde, real-time distributie en synchronisatie; ontvangstbevestiging gemonitord.",en:"Automated, real-time distribution and synchronisation; receipt confirmation monitored."}],
   firstSteps:[{nl:"Zorg dat iedere crisisrol nú de juiste, actuele versie van plan en contactlijst heeft.",en:"Ensure every crisis role now has the correct, current version of the plan and contact list."},{nl:"Leg een distributieproces vast met versiebeheer en periodieke validatie van contactdata.",en:"Document a distribution process with version control and periodic validation of contact data."},{nl:"Push actuele plannen actief naar alle rollen en borg offline/redundante toegang.",en:"Actively push current plans to all roles and ensure offline/redundant access."},{nl:"Automatiseer distributie en synchronisatie met ontvangstbevestiging.",en:"Automate distribution and synchronisation with receipt confirmation."}]},
  {id:"B6",code:"B6",name:{nl:"Documentatie & registratie",en:"Documentation & logging"},iso:"§5.3.4.2",
   levels:[{nl:"Geen documentbeheersysteem; losse, onbeheerde documenten.",en:"No document management system; loose, unmanaged documents."},{nl:"Documenten beschikbaar; basis titel/versienummering aanwezig.",en:"Documents available; basic title/version numbering present."},{nl:"Formeel documentbeheersysteem; versiebeheer; beslissingslogboek en situatierapporten.",en:"Formal document management system; version control; decision log and situation reports."},{nl:"Documentbeheersysteem bepaalt onderhoud en review; audit trail; offline toegang.",en:"Document system drives maintenance and review; audit trail; offline access."},{nl:"Speciale crisismanagementsoftware; real-time logging; metriekgestuurd beheer.",en:"Dedicated crisis management software; real-time logging; metrics-driven management."}],
   firstSteps:[{nl:"Verzamel alle crisisdocumenten op één vindbare plek met een duidelijke versienummering.",en:"Collect all crisis documents in one findable place with clear version numbering."},{nl:"Richt een formeel documentbeheersysteem in met beslissingslogboek en situatierapporten.",en:"Set up a formal document management system with a decision log and situation reports."},{nl:"Voeg een audit trail toe en borg onderhoud, review en offline toegang.",en:"Add an audit trail and ensure maintenance, review and offline access."},{nl:"Stap over op crisismanagementsoftware met real-time logging.",en:"Move to crisis management software with real-time logging."}]},
 ]},
 {id:"C",title:{nl:"Crisisrespons & Leiderschap",en:"Crisis Response & Leadership"},domains:[
  {id:"C1",code:"C1",name:{nl:"Crisisrespons & besluitvorming",en:"Crisis response & decision-making"},iso:"§5.3.5, Clause 6, Clause 7",
   levels:[{nl:"Geen gestructureerde respons; besluitvorming ad hoc.",en:"No structured response; ad hoc decision-making."},{nl:"Basisactiveringsprotocol; CMT komt samen maar zonder structuur.",en:"Basic activation protocol; crisis team meets but without structure."},{nl:"Gestructureerde CMT-respons; besluitvormingsproces gedocumenteerd; logboek bijgehouden.",en:"Structured team response; decision process documented; log maintained."},{nl:"CMT effectief onder druk; evidence-based besluitvorming; volledige audittrail; herstelfase vroeg gepland.",en:"Team effective under pressure; evidence-based decisions; full audit trail; recovery planned early."},{nl:"Adaptieve crisisrespons buiten standaardkaders; continue verbetering; schaalbare responscapaciteit.",en:"Adaptive response beyond standard frameworks; continuous improvement; scalable capacity."}],
   firstSteps:[{nl:"Spreek een vast vergaderritme en een simpel besluitvormingsstramien (situatie → opties → besluit) af.",en:"Agree a fixed meeting cadence and a simple decision pattern (situation → options → decision)."},{nl:"Documenteer het besluitvormingsproces en houd consequent een logboek bij.",en:"Document the decision process and keep a consistent log."},{nl:"Train het CMT op besluiten onder druk en plan de herstelfase vroeg in.",en:"Train the team on decisions under pressure and plan the recovery phase early."},{nl:"Ontwikkel adaptieve, schaalbare responscapaciteit.",en:"Develop adaptive, scalable response capacity."}]},
  {id:"C2",code:"C2",name:{nl:"Herstel (Recovery)",en:"Recovery"},iso:"§5.3.6",
   levels:[{nl:"Geen herstelplanning; focus alleen op directe respons.",en:"No recovery planning; focus only on immediate response."},{nl:"Basisoverwegingen voor herstel; geen formele strategie.",en:"Basic recovery considerations; no formal strategy."},{nl:"Formele herstelstrategie; overdrachtsprotocol respons → herstel; herstelteam benoemd.",en:"Formal recovery strategy; handover protocol response → recovery; recovery team appointed."},{nl:"Herstelplanning start vroeg tijdens respons; resources toegewezen; stakeholder-communicatie geborgd.",en:"Recovery planning starts early during response; resources allocated; stakeholder communication ensured."},{nl:"Strategische kansen benut tijdens herstel; volledig lessons-learned proces.",en:"Strategic opportunities used during recovery; full lessons-learned process."}],
   firstSteps:[{nl:"Benoem wie verantwoordelijk is voor herstel zodra de acute fase voorbij is.",en:"Name who is responsible for recovery once the acute phase is over."},{nl:"Leg een herstelstrategie en overdrachtsprotocol vast en benoem een herstelteam.",en:"Document a recovery strategy and handover protocol and appoint a recovery team."},{nl:"Laat herstelplanning al tijdens de respons starten met geborgde stakeholdercommunicatie.",en:"Start recovery planning during the response with ensured stakeholder communication."},{nl:"Benut herstel strategisch en borg een volledig lessons-learned proces.",en:"Use recovery strategically and embed a full lessons-learned process."}]},
 ]},
 {id:"D",title:{nl:"Crisiscommunicatie",en:"Crisis Communication"},domains:[
  {id:"D1",code:"D1",name:{nl:"Stakeholdermapping & contactdata",en:"Stakeholder mapping & contact data"},iso:"§8.3, §8.4",
   levels:[{nl:"Geen stakeholderlijst of contactmatrix.",en:"No stakeholder list or contact matrix."},{nl:"Basislijst; contactdata deels aanwezig; niet gestructureerd.",en:"Basic list; contact data partly present; unstructured."},{nl:"Stakeholdermatrix: prioriteit + kanaal + tracking; scenario-specifieke vragen.",en:"Stakeholder matrix: priority + channel + tracking; scenario-specific questions."},{nl:"Ketenafgestemd; dynamische stakeholderanalyse; scenario-vragen per type crisis.",en:"Chain-aligned; dynamic stakeholder analysis; scenario questions per crisis type."},{nl:"Real-time stakeholderanalyse op basis van impact/sentiment; continu bijgehouden.",en:"Real-time stakeholder analysis based on impact/sentiment; continuously maintained."}],
   firstSteps:[{nl:"Maak een lijst van je belangrijkste stakeholders met wie, hoe en wanneer je hen bereikt.",en:"Make a list of your key stakeholders with who, how and when you reach them."},{nl:"Bouw een stakeholdermatrix met prioriteit, kanaal en tracking.",en:"Build a stakeholder matrix with priority, channel and tracking."},{nl:"Maak de analyse dynamisch en stem af met ketenpartners per crisistype.",en:"Make the analysis dynamic and align with chain partners per crisis type."},{nl:"Voeg real-time stakeholderanalyse op impact en sentiment toe.",en:"Add real-time stakeholder analysis on impact and sentiment."}]},
  {id:"D2",code:"D2",name:{nl:"Boodschappen & Q&A",en:"Messaging & Q&A"},iso:"§8.5, §8.6, §8.7",
   levels:[{nl:"Inconsistente boodschappen; veel ad hoc.",en:"Inconsistent messaging; much ad hoc."},{nl:"Basisstatement en/of beperkte Q&A aanwezig.",en:"Basic statement and/or limited Q&A present."},{nl:"Standaard boodschappen + Q&A per doelgroep en toon; woordvoerder aangewezen.",en:"Standard messages + Q&A per audience and tone; spokesperson appointed."},{nl:"Pre-approved thema's en berichten per crisistype; desinformatie snel gecorrigeerd.",en:"Pre-approved themes and messages per crisis type; disinformation corrected quickly."},{nl:"Dynamische berichtenbibliotheek per crisistype; evidence-based optimalisatie.",en:"Dynamic message library per crisis type; evidence-based optimisation."}],
   firstSteps:[{nl:"Schrijf één holding statement dat je bij elke crisis als eerste reactie kunt gebruiken.",en:"Write one holding statement you can use as a first response to any crisis."},{nl:"Maak standaard boodschappen en Q&A per doelgroep en wijs een woordvoerder aan.",en:"Create standard messages and Q&A per audience and appoint a spokesperson."},{nl:"Werk met pre-approved berichten per crisistype en corrigeer desinformatie snel.",en:"Use pre-approved messages per crisis type and correct disinformation quickly."},{nl:"Bouw een dynamische berichtenbibliotheek met evidence-based optimalisatie.",en:"Build a dynamic message library with evidence-based optimisation."}]},
  {id:"D3",code:"D3",name:{nl:"Kanalen & bereik",en:"Channels & reach"},iso:"§8.6, §8.9",
   levels:[{nl:"Kanalen ad hoc; geen back-up.",en:"Channels ad hoc; no back-up."},{nl:"Beperkte kanalen beschikbaar; niet getest.",en:"Limited channels available; untested."},{nl:"Primair/secundair kanaal per doelgroep vastgelegd; kanaallijst gedocumenteerd.",en:"Primary/secondary channel per audience defined; channel list documented."},{nl:"Redundante en geteste kanalen; multi-taal overwogen; social media gemonitord.",en:"Redundant and tested channels; multi-language considered; social media monitored."},{nl:"Meerkanaals beheersing; continu bereikanalyses; real-time social media management.",en:"Multi-channel mastery; continuous reach analysis; real-time social media management."}],
   firstSteps:[{nl:"Leg per doelgroep vast via welk kanaal je hen primair bereikt en wat de back-up is.",en:"Define per audience the primary channel to reach them and the back-up."},{nl:"Documenteer een complete kanaallijst met primair én secundair kanaal.",en:"Document a complete channel list with primary and secondary channel."},{nl:"Test de kanalen, maak ze redundant en monitor social media.",en:"Test the channels, make them redundant and monitor social media."},{nl:"Stuur op meerkanaals beheersing met continue bereikanalyses.",en:"Aim for multi-channel mastery with continuous reach analysis."}]},
  {id:"D4",code:"D4",name:{nl:"Monitoring & feedback",en:"Monitoring & feedback"},iso:"§8.9, §5.3.4.4",
   levels:[{nl:"Geen monitoring.",en:"No monitoring."},{nl:"Handmatige check; geen triage.",en:"Manual check; no triage."},{nl:"Monitoringproces + tool aanwezig; triage gedefinieerd.",en:"Monitoring process + tool present; triage defined."},{nl:"Doorlopend; early warning; effectmeting van communicatie.",en:"Continuous; early warning; measuring communication impact."},{nl:"24/7 capability + dashboards; voorspellende signalen; sentimentanalyse.",en:"24/7 capability + dashboards; predictive signals; sentiment analysis."}],
   firstSteps:[{nl:"Wijs iemand aan die bij een crisis de media en social media in de gaten houdt.",en:"Appoint someone to watch media and social media during a crisis."},{nl:"Richt een monitoringproces met tool en triage in.",en:"Set up a monitoring process with tool and triage."},{nl:"Maak monitoring doorlopend met early warning en effectmeting.",en:"Make monitoring continuous with early warning and impact measurement."},{nl:"Bouw een 24/7-capability met dashboards en sentimentanalyse.",en:"Build a 24/7 capability with dashboards and sentiment analysis."}]},
 ]},
 {id:"E",title:{nl:"Leren & Continue Verbetering",en:"Learning & Continuous Improvement"},domains:[
  {id:"E1",code:"E1",name:{nl:"Training & oefeningen",en:"Training & exercises"},iso:"§9.2, §9.3",
   levels:[{nl:"Geen trainingen en/of oefeningen.",en:"No training and/or exercises."},{nl:"Incidenteel / onregelmatig; geen oefendoelen.",en:"Occasional / irregular; no exercise objectives."},{nl:"Regelmatige oefeningen; oefendoelen en evaluatie; competentieprofielen aanwezig.",en:"Regular exercises; objectives and evaluation; competency profiles present."},{nl:"Uitdagende oefeningen incl. externe partijen; verbeterplan opgevolgd; PDCA toegepast.",en:"Challenging exercises incl. external parties; improvement plan followed; PDCA applied."},{nl:"Doorlopend + cross-organisatie; lessons learned verankerd; continue competentieontwikkeling.",en:"Continuous + cross-organisation; lessons learned embedded; continuous competency development."}],
   firstSteps:[{nl:"Plan dit jaar één eenvoudige tabletop-oefening met het CMT in.",en:"Schedule one simple tabletop exercise with the crisis team this year."},{nl:"Maak oefenen regelmatig, met oefendoelen, evaluatie en competentieprofielen.",en:"Make exercising regular, with objectives, evaluation and competency profiles."},{nl:"Voeg externe partijen toe en volg verbeterpunten op via PDCA.",en:"Add external parties and follow up improvements via PDCA."},{nl:"Maak oefenen doorlopend en cross-organisatie met verankerde lessons learned.",en:"Make exercising continuous and cross-organisation with embedded lessons learned."}]},
  {id:"E2",code:"E2",name:{nl:"Juridisch & ethisch (incl. privacy / AVG)",en:"Legal & ethical (incl. privacy / GDPR)"},iso:"§4.5.7, §5.3.5",
   levels:[{nl:"Geen juridische toets; privacyrisico's niet in kaart; ethische kaders ontbreken.",en:"No legal review; privacy risks not mapped; ethical frameworks absent."},{nl:"Ad hoc juridisch advies; onduidelijke kaders; AVG/GDPR basis aanwezig.",en:"Ad hoc legal advice; unclear frameworks; GDPR basics present."},{nl:"Juridische kaders vastgelegd in CMP; AVG/GDPR richtlijnen gedocumenteerd; adviseur beschikbaar.",en:"Legal frameworks documented in CMP; GDPR guidelines documented; adviser available."},{nl:"Ethische toets geïntegreerd in besluitvorming; juridische review van communicatie geborgd.",en:"Ethical review integrated into decision-making; legal review of communication ensured."},{nl:"Proactief ethisch leiderschap; juridische kaders real-time beschikbaar; doorlopende compliance.",en:"Proactive ethical leadership; legal frameworks available real-time; ongoing compliance."}],
   firstSteps:[{nl:"Spreek af wie je juridisch en op AVG-vlak belt zodra er een crisis speelt.",en:"Agree who to call for legal and GDPR matters once a crisis occurs."},{nl:"Leg juridische en AVG-kaders vast in het CMP en zorg dat een adviseur beschikbaar is.",en:"Document legal and GDPR frameworks in the CMP and ensure an adviser is available."},{nl:"Integreer een ethische toets in de besluitvorming met juridische review van communicatie.",en:"Integrate an ethical review into decision-making with legal review of communication."},{nl:"Maak juridische kaders real-time beschikbaar met doorlopende compliancemonitoring.",en:"Make legal frameworks available real-time with ongoing compliance monitoring."}]},
  {id:"E3",code:"E3",name:{nl:"Integratie met BCMS (ISO 22301)",en:"Integration with BCMS (ISO 22301)"},iso:"§5.1",
   levels:[{nl:"Staat los van BCMS; overdracht onduidelijk.",en:"Separate from BCMS; handover unclear."},{nl:"Enkele koppelingen; beperkte afstemming.",en:"Some links; limited alignment."},{nl:"Gedeelde activering; consistente escalatielijn; gezamenlijk beleid.",en:"Shared activation; consistent escalation line; joint policy."},{nl:"PDCA-geborgd; gezamenlijke scenario's; gedeelde crisisplannen.",en:"PDCA-embedded; joint scenarios; shared crisis plans."},{nl:"Organisatiebreed geïntegreerd; data-delen; continue verbetering samen.",en:"Organisation-wide integration; data sharing; joint continuous improvement."}],
   firstSteps:[{nl:"Breng in kaart hoe crisismanagement en business continuity nu naar elkaar overdragen.",en:"Map how crisis management and business continuity currently hand over to each other."},{nl:"Richt gedeelde activering en een consistente escalatielijn in met gezamenlijk beleid.",en:"Set up shared activation and a consistent escalation line with joint policy."},{nl:"Borg de koppeling via PDCA met gezamenlijke scenario's en plannen.",en:"Embed the link via PDCA with joint scenarios and plans."},{nl:"Integreer beide organisatiebreed met data-delen en gezamenlijke verbetering.",en:"Integrate both organisation-wide with data sharing and joint improvement."}]},
 ]},
];

/* ============ MODEL 22301 — BCM (tweetalig) ============ */
const SECTIONS_22301 = [
 {id:"O",title:{nl:"Organisatie",en:"Organisation"},domains:[
  {id:"O1",code:"ORG",name:{nl:"Organisatie",en:"Organisation"},iso:{nl:"Hoe is de BCM-inspanning georganiseerd?",en:"How is the BCM effort organised?"},
   levels:[{nl:"Activiteiten zijn reactief.",en:"Activities are reactive."},{nl:"Projectmatige aanpak.",en:"Project-based approach."},{nl:"Alle benodigde onderdelen van de organisatie zijn betrokken met benoemde deelnemers per afdeling.",en:"All necessary parts of the organisation are involved with named participants per department."},{nl:"Een formele afdeling en toewijzing van vereist budget is gestandaardiseerd.",en:"A formal department and allocation of required budget is standardised."},{nl:"Geïntegreerd in BC-inspanningen rond bijv. Risico, Leveringsketen, Informatie-/Cyberbeveiliging, Crisis- en Incidentmanagement (incl. ISO 22318).",en:"Integrated into BC efforts around e.g. Risk, Supply Chain, Information/Cyber Security, Crisis and Incident Management (incl. ISO 22318)."}],
   firstSteps:[{nl:"Til BCM van reactief naar een projectmatige aanpak: benoem een trekker en een eenvoudig plan van aanpak.",en:"Move BCM from reactive to a project-based approach: appoint a lead and a simple plan."},{nl:"Betrek alle relevante organisatieonderdelen en wijs per afdeling een deelnemer aan.",en:"Involve all relevant parts of the organisation and assign a participant per department."},{nl:"Richt een formele afdeling/verantwoordelijkheid in en standaardiseer een vast BCM-budget.",en:"Establish a formal department/responsibility and standardise a fixed BCM budget."},{nl:"Integreer BCM met risicomanagement, supply chain, informatiebeveiliging en crisismanagement; overweeg ISO 22318.",en:"Integrate BCM with risk management, supply chain, information security and crisis management; consider ISO 22318."}]},
 ]},
 {id:"C",title:{nl:"Beheersing",en:"Governance"},domains:[
  {id:"C1",code:"BEH",name:{nl:"Beheersing",en:"Governance"},iso:{nl:"Hoe wordt de BCM-inspanning beheerd (bemensing)?",en:"How is the BCM effort governed (staffing)?"},
   levels:[{nl:"Additionele activiteit van bestaand personeel; geen specifiek BCM-personeel.",en:"Additional activity of existing staff; no dedicated BCM staff."},{nl:"Projectleider, geen formele ondersteuning van senior management.",en:"Project leader, no formal senior management support."},{nl:"Beheerd door een benoemde Business Continuity Manager.",en:"Managed by an appointed Business Continuity Manager."},{nl:"Actieve ondersteuning en betrokkenheid van het topmanagement.",en:"Active support and involvement of top management."},{nl:"Formele organisatiestructuur aanwezig (top-down).",en:"Formal organisational structure in place (top-down)."}],
   firstSteps:[{nl:"Maak BCM een formele (project)taak in plaats van iets dat er los bij hangt; benoem een projectleider.",en:"Make BCM a formal (project) task rather than a loose add-on; appoint a project leader."},{nl:"Stel een Business Continuity Manager aan met een duidelijk mandaat.",en:"Appoint a Business Continuity Manager with a clear mandate."},{nl:"Zorg voor actieve betrokkenheid en sponsoring vanuit het topmanagement.",en:"Ensure active involvement and sponsorship from top management."},{nl:"Leg een formele, top-down BCM-organisatiestructuur vast.",en:"Establish a formal, top-down BCM organisational structure."}]},
 ]},
 {id:"U",title:{nl:"Uitvoering",en:"Execution"},domains:[
  {id:"U1",code:"UIT",name:{nl:"Uitvoering",en:"Execution"},iso:{nl:"In welke vorm wordt de BCM-inspanning uitgevoerd?",en:"In what form is the BCM effort executed?"},
   levels:[{nl:"Niet gestructureerd en ad hoc.",en:"Unstructured and ad hoc."},{nl:"Jaarlijks terugkerende beoordeling van geïmplementeerde elementen.",en:"Annually recurring review of implemented elements."},{nl:"Aandacht voor benodigde competenties, oefenen & testen en interne audit.",en:"Attention to required competencies, exercising & testing and internal audit."},{nl:"Verantwoordelijkheid voor een gedegen aanpak van alle onderdelen van de PDCA-cyclus conform ISO 22301.",en:"Responsibility for a sound approach to all parts of the PDCA cycle per ISO 22301."},{nl:"BCMS gericht op continue verbetering en onderdeel van de organisatiecultuur/DNA.",en:"BCMS focused on continuous improvement and part of the organisational culture/DNA."}],
   firstSteps:[{nl:"Plan een vaste, jaarlijkse beoordeling van de bestaande BCM-elementen in.",en:"Schedule a fixed, annual review of the existing BCM elements."},{nl:"Voeg competentieontwikkeling, oefenen & testen en interne audit toe.",en:"Add competency development, exercising & testing and internal audit."},{nl:"Borg de volledige PDCA-cyclus (Plan-Do-Check-Act) conform ISO 22301.",en:"Embed the full PDCA cycle (Plan-Do-Check-Act) per ISO 22301."},{nl:"Maak continue verbetering onderdeel van het DNA, zoals bij Kwaliteit en Veiligheid.",en:"Make continuous improvement part of the DNA, as with Quality and Safety."}]},
 ]},
 {id:"I",title:{nl:"Inhoud",en:"Content"},domains:[
  {id:"I1",code:"INH",name:{nl:"Inhoud",en:"Content"},iso:{nl:"Wat is de inhoud van de BCM-inspanning?",en:"What is the content of the BCM effort?"},
   levels:[{nl:"Basis-BCP voor specifieke functies, niet conform standaarden of richtlijnen.",en:"Basic BCP for specific functions, not compliant with standards or guidelines."},{nl:"BIA, RB en BCP, ondersteund door standaarden of richtlijnen.",en:"BIA, RA and BCP, supported by standards or guidelines."},{nl:"BCM-toepassingsgebied, doelstellingen en beleid aanwezig; plus BIA, RB en BCP ondersteund door standaarden.",en:"BCM scope, objectives and policy present; plus BIA, RA and BCP supported by standards."},{nl:"BCMS aanwezig met een erkende standaardaanpak (bijv. ISO 22301 of BCI GPG).",en:"BCMS present using a recognised standard approach (e.g. ISO 22301 or BCI GPG)."},{nl:"Volledig conform ISO 22301 of gecertificeerd.",en:"Fully compliant with ISO 22301 or certified."}],
   firstSteps:[{nl:"Bouw vanuit het losse BCP een onderbouwde basis op met een BIA en risicobeoordeling.",en:"From the standalone BCP, build a substantiated basis with a BIA and risk assessment."},{nl:"Voeg toepassingsgebied (scope), doelstellingen en BCM-beleid toe.",en:"Add scope, objectives and BCM policy."},{nl:"Richt een volwaardig BCMS in volgens een erkende standaard (ISO 22301 of BCI GPG).",en:"Set up a full BCMS following a recognised standard (ISO 22301 or BCI GPG)."},{nl:"Werk toe naar volledige naleving of externe certificering van ISO 22301.",en:"Work towards full compliance or external certification of ISO 22301."}]},
 ]},
 {id:"D",title:{nl:"Documentatie",en:"Documentation"},domains:[
  {id:"D1",code:"DOC",name:{nl:"Documentatie",en:"Documentation"},iso:{nl:"Hoe wordt de documentatie-inspanning uitgevoerd?",en:"How is the documentation effort carried out?"},
   levels:[{nl:"Geen documentbeheersysteem.",en:"No document management system."},{nl:"Documenten beschikbaar voor wie toegang nodig heeft; titel, code en versienummer aanwezig.",en:"Documents available to those who need access; title, code and version number present."},{nl:"Formeel documentbeheersysteem in gebruik.",en:"Formal document management system in use."},{nl:"Documentbeheersysteem stuurt onderhoud en beoordeling en garandeert beschikbaarheid (on- én offline), met SLA-proces.",en:"Document system drives maintenance and review and guarantees availability (on- and offline), with an SLA process."},{nl:"Speciale BCM-software (idealiter SaaS) die on- en offline beschikbaar is.",en:"Dedicated BCM software (ideally SaaS) available on- and offline."}],
   firstSteps:[{nl:"Geef alle BCM-documenten een titel, code en versienummer en maak ze toegankelijk voor betrokkenen.",en:"Give all BCM documents a title, code and version number and make them accessible to those involved."},{nl:"Neem een formeel documentbeheersysteem in gebruik.",en:"Adopt a formal document management system."},{nl:"Borg onderhoud, review en beschikbaarheid (on- én offline) met een SLA-proces.",en:"Ensure maintenance, review and availability (on- and offline) with an SLA process."},{nl:"Stap over op speciale BCM-software (bij voorkeur SaaS) voor toegang tijdens een IT-storing.",en:"Move to dedicated BCM software (preferably SaaS) for access during an IT outage."}]},
 ]},
];

/* ============ SECTOR / GROOTTE ============ */
const SECTORS=[
 {id:"government",nl:"Overheid & publieke sector",en:"Government & public sector"},
 {id:"financial",nl:"Financi\u00eble dienstverlening",en:"Financial services"},
 {id:"healthcare",nl:"Zorg",en:"Healthcare"},
 {id:"energy",nl:"Energie & utilities",en:"Energy & utilities"},
 {id:"industry",nl:"Industrie & productie",en:"Industry & manufacturing"},
 {id:"it",nl:"IT & telecom",en:"IT & telecom"},
 {id:"transport",nl:"Transport & logistiek",en:"Transport & logistics"},
 {id:"education",nl:"Onderwijs & onderzoek",en:"Education & research"},
 {id:"retail",nl:"Retail & consumentendiensten",en:"Retail & consumer services"},
 {id:"other",nl:"Anders",en:"Other"}];
const ORG_SIZES=[
 {id:"1-50",nl:"1 \u2013 50 medewerkers",en:"1 \u2013 50 employees"},
 {id:"51-250",nl:"51 \u2013 250 medewerkers",en:"51 \u2013 250 employees"},
 {id:"251-1000",nl:"251 \u2013 1.000 medewerkers",en:"251 \u2013 1,000 employees"},
 {id:"1001-5000",nl:"1.001 \u2013 5.000 medewerkers",en:"1,001 \u2013 5,000 employees"},
 {id:"5000+",nl:"5.000+ medewerkers",en:"5,000+ employees"}];

/* ============ ANONIEME BENCHMARK-OPSLAG ============ */
function submitAnonymous(a,modelId,lang){
  try{
    const rated=ALL_DOMAINS.filter(d=>a.scores[d.id]!=null);
    const avg=rated.length?rated.reduce((s,d)=>s+a.scores[d.id],0)/rated.length:null;
    const payload={aid:a.id,model:modelId,date:a.date,ambition:a.ambition??TARGET,sector:a.sector||"",orgSize:a.orgSize||"",lang:lang||"en",ratedCount:rated.length,avg:avg!=null?Math.round(avg*100)/100:null,scores:a.scores};
    fetch("api/save.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload),keepalive:true}).catch(()=>{});
  }catch(e){}
}

/* ============ MODELLEN-REGISTER ============ */
const MODELS = {
  "22361":{id:"22361",code:"ISO 22361",name:{nl:"Crisismanagement",en:"Crisis Management"},tagline:{nl:"Volwassenheid van crisismanagement in 19 domeinen.",en:"Maturity of crisis management across 19 domains."},sections:SECTIONS_22361,
    appTitle:{nl:"ISO 22361 — Crisismanagement Volwassenheidsmatrix",en:"ISO 22361 — Crisis Management Maturity Matrix"},
    appSubtitle:{nl:"Self-assessment van de volwassenheid van uw crisismanagement (niveau 1–5)",en:"Self-assessment of your crisis management maturity (level 1–5)"},
    bullets:{nl:["19 domeinen in 5 aandachtsgebieden","Framework, anticipatie, respons, communicatie, leren","Conform ISO 22361:2022"],en:["19 domains across 5 focus areas","Framework, anticipation, response, communication, learning","Per ISO 22361:2022"]}},
  "22301":{id:"22301",code:"ISO 22301",name:{nl:"Business Continuity (BCM)",en:"Business Continuity (BCM)"},tagline:{nl:"Volwassenheid van business continuity management in 5 aandachtsgebieden.",en:"Maturity of business continuity management across 5 focus areas."},sections:SECTIONS_22301,
    appTitle:{nl:"ISO 22301 — BCM Volwassenheidsmatrix",en:"ISO 22301 — BCM Maturity Matrix"},
    appSubtitle:{nl:"Self-assessment van de volwassenheid van uw business continuity management (niveau 1–5)",en:"Self-assessment of your business continuity management maturity (level 1–5)"},
    bullets:{nl:["5 aandachtsgebieden (BCM Maturity Model)","Organisatie, Beheersing, Uitvoering, Inhoud, Documentatie","Conform ISO 22301"],en:["5 focus areas (BCM Maturity Model)","Organisation, Governance, Execution, Content, Documentation","Per ISO 22301"]}},
};
function modelDomainCount(modelId){ return MODELS[modelId].sections.flatMap(s=>s.domains).length; }

/* actieve model-helpers */
let SECTIONS_ACTIVE = SECTIONS_22361;
let ALL_DOMAINS = SECTIONS_22361.flatMap(s=>s.domains.map(d=>({...d,section:s.id,sectionTitle:s.title})));
let DOMAIN_BY_ID = Object.fromEntries(ALL_DOMAINS.map(d=>[d.id,d]));
function setActiveModel(modelId){
  SECTIONS_ACTIVE = MODELS[modelId].sections;
  ALL_DOMAINS = SECTIONS_ACTIVE.flatMap(s=>s.domains.map(d=>({...d,section:s.id,sectionTitle:s.title})));
  DOMAIN_BY_ID = Object.fromEntries(ALL_DOMAINS.map(d=>[d.id,d]));
}

/* ============ LOCALSTORAGE per model ============ */
const LS_KEY_BASE="iso_maturity_assessments_v2";
const LS_LANG="iso_maturity_lang";
const lsKey=(m)=>LS_KEY_BASE+"_"+m;
function loadAll(m){ try{ const r=localStorage.getItem(lsKey(m)); return r?JSON.parse(r):[]; }catch(e){ return []; } }
function saveAll(m,list){ try{ localStorage.setItem(lsKey(m),JSON.stringify(list)); return true; }catch(e){ return false; } }

function computeStats(scores){
  const rated=ALL_DOMAINS.filter(d=>scores[d.id]!=null);
  const total=rated.reduce((s,d)=>s+scores[d.id],0);
  const avg=rated.length?total/rated.length:null;
  const sectionAvgs={};
  for(const sec of SECTIONS_ACTIVE){ const r=sec.domains.filter(d=>scores[d.id]!=null); sectionAvgs[sec.id]=r.length?r.reduce((s,d)=>s+scores[d.id],0)/r.length:null; }
  return {ratedCount:rated.length,total,avg,sectionAvgs};
}
const emptyScores=()=>Object.fromEntries(ALL_DOMAINS.map(d=>[d.id,null]));
function newAssessment(m){ return {id:"a_"+Date.now().toString(36)+"_"+Math.random().toString(36).slice(2,7),model:m,orgName:"",sector:"",orgSize:"",assessmentName:"",date:new Date().toISOString().slice(0,10),ambition:TARGET,scores:emptyScores(),planDone:{},savedAt:null}; }

/* ============ ICONEN ============ */
const Ic=(p)=>React.createElement("svg",{width:p.s||16,height:p.s||16,viewBox:"0 0 24 24",fill:"none",stroke:p.c||"currentColor",strokeWidth:p.w||2,strokeLinecap:"round",strokeLinejoin:"round",className:p.cn,style:p.style},p.children);
const Icons={
  shield:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"}),React.createElement("path",{key:2,d:"M12 8v4"}),React.createElement("path",{key:3,d:"M12 16h.01"})]}),
  save:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"}),React.createElement("polyline",{key:2,points:"17 21 17 13 7 13 7 21"}),React.createElement("polyline",{key:3,points:"7 3 7 8 15 8"})]}),
  check:(p)=>Ic({...p,children:[React.createElement("polyline",{key:1,points:"20 6 9 17 4 12"})]}),
  plus:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M12 5v14M5 12h14"})]}),
  printer:(p)=>Ic({...p,children:[React.createElement("polyline",{key:1,points:"6 9 6 2 18 2 18 9"}),React.createElement("path",{key:2,d:"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"}),React.createElement("rect",{key:3,x:6,y:14,width:12,height:8})]}),
  building:(p)=>Ic({...p,children:[React.createElement("rect",{key:1,x:4,y:2,width:16,height:20,rx:2}),React.createElement("path",{key:2,d:"M9 22v-4h6v4M9 6h.01M15 6h.01M9 10h.01M15 10h.01M9 14h.01M15 14h.01"})]}),
  cal:(p)=>Ic({...p,children:[React.createElement("rect",{key:1,x:3,y:4,width:18,height:18,rx:2}),React.createElement("path",{key:2,d:"M16 2v4M8 2v4M3 10h18"})]}),
  file:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"}),React.createElement("polyline",{key:2,points:"14 2 14 8 20 8"})]}),
  gauge:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M12 14l4-4M3.34 19a10 10 0 1 1 17.32 0"})]}),
  target:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:12,cy:12,r:10}),React.createElement("circle",{key:2,cx:12,cy:12,r:6}),React.createElement("circle",{key:3,cx:12,cy:12,r:2})]}),
  book:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M4 19.5A2.5 2.5 0 0 1 6.5 17H20"}),React.createElement("path",{key:2,d:"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"})]}),
  info:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:12,cy:12,r:10}),React.createElement("path",{key:2,d:"M12 16v-4M12 8h.01"})]}),
  arrowR:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M5 12h14M12 5l7 7-7 7"})]}),
  arrowL:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M19 12H5M12 19l-7-7 7-7"})]}),
  chevR:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M9 18l6-6-6-6"})]}),
  folder:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"})]}),
  compare:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:18,cy:18,r:3}),React.createElement("circle",{key:2,cx:6,cy:6,r:3}),React.createElement("path",{key:3,d:"M6 21V9a9 9 0 0 0 9 9"})]}),
  trash:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"})]}),
  pencil:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M17 3a2.83 2.83 0 0 1 4 4L7.5 20.5 2 22l1.5-5.5z"})]}),
  x:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M18 6 6 18M6 6l12 12"})]}),
  circle:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:12,cy:12,r:10})]}),
  checkCircle:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M22 11.08V12a10 10 0 1 1-5.93-9.14"}),React.createElement("polyline",{key:2,points:"22 4 12 14.01 9 11.01"})]}),
  alert:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"}),React.createElement("path",{key:2,d:"M12 9v4M12 17h.01"})]}),
  up:(p)=>Ic({...p,children:[React.createElement("polyline",{key:1,points:"23 6 13.5 15.5 8.5 10.5 1 18"}),React.createElement("polyline",{key:2,points:"17 6 23 6 23 12"})]}),
  down:(p)=>Ic({...p,children:[React.createElement("polyline",{key:1,points:"23 18 13.5 8.5 8.5 13.5 1 6"}),React.createElement("polyline",{key:2,points:"17 18 23 18 23 12"})]}),
  minus:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M5 12h14"})]}),
  route:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:6,cy:19,r:3}),React.createElement("path",{key:2,d:"M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15"}),React.createElement("circle",{key:3,cx:18,cy:5,r:3})]}),
  rocket:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"}),React.createElement("path",{key:2,d:"M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"})]}),
  award:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:12,cy:8,r:6}),React.createElement("path",{key:2,d:"M15.477 12.89 17 22l-5-3-5 3 1.523-9.11"})]}),
  flag:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"}),React.createElement("line",{key:2,x1:4,y1:22,x2:4,y2:15})]}),
  clock:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:12,cy:12,r:10}),React.createElement("polyline",{key:2,points:"12 6 12 12 16 14"})]}),
  clipboard:(p)=>Ic({...p,children:[React.createElement("path",{key:1,d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}),React.createElement("rect",{key:2,x:8,y:2,width:8,height:4,rx:1})]}),
  globe:(p)=>Ic({...p,children:[React.createElement("circle",{key:1,cx:12,cy:12,r:10}),React.createElement("path",{key:2,d:"M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"})]}),
  lock:(p)=>Ic({...p,children:[React.createElement("rect",{key:1,x:3,y:11,width:18,height:11,rx:2}),React.createElement("path",{key:2,d:"M7 11V7a5 5 0 0 1 10 0v4"})]}),
};

/* ============ TAALSCHUIF ============ */
function LangToggle({lang,setLang}){
  return <div className="inline-flex items-center rounded-full p-0.5" style={{background:"#ffffff22"}}>
    {["nl","en"].map(lg=>(
      <button key={lg} onClick={()=>setLang(lg)} className="px-2.5 py-1 rounded-full text-xs font-bold transition-colors" style={{background:lang===lg?"#fff":"transparent",color:lang===lg?NAVY:"#cfe0f0"}}>{lg.toUpperCase()}</button>
    ))}
  </div>;
}

/* ============ KLEINE COMPONENTEN ============ */
function LevelChip({level,small,lang}){
  if(level==null) return <span className={"inline-flex items-center rounded-full bg-slate-100 text-slate-500 font-medium "+(small?"px-2 py-0.5 text-xs":"px-2.5 py-1 text-sm")}>—</span>;
  const m=LEVEL_META[level];
  return <span className={"inline-flex items-center rounded-full font-semibold "+m.chip+" "+(small?"px-2 py-0.5 text-xs":"px-2.5 py-1 text-sm")}>{level} · {L(LEVEL_LABELS[level],lang)}</span>;
}
function ProgressBar({value,total,lang}){
  const t=total||ALL_DOMAINS.length;const pct=Math.round((value/t)*100);
  return <div className="w-full"><div className="flex items-center justify-between mb-1"><span className="text-sm font-medium text-slate-600">{value} {L(T.ofWord,lang)} {t} {L(T.domainsFilled,lang)}</span><span className="text-sm font-semibold text-slate-700">{pct}%</span></div><div className="h-2 w-full rounded-full bg-slate-200 overflow-hidden"><div className="h-full rounded-full transition-all" style={{width:pct+"%",background:NAVY}}/></div></div>;
}
function RadarChartSVG({scores,ambition}){
  const size=460,cx=size/2,cy=size/2,maxR=size/2-64,n=ALL_DOMAINS.length;
  const angleFor=i=>(Math.PI*2*i)/n-Math.PI/2;
  const point=(i,v)=>{const r=(v/5)*maxR,a=angleFor(i);return [cx+r*Math.cos(a),cy+r*Math.sin(a)];};
  const ringPath=v=>ALL_DOMAINS.map((_,i)=>{const[x,y]=point(i,v);return (i===0?"M":"L")+x.toFixed(1)+","+y.toFixed(1);}).join(" ")+" Z";
  const scorePoly=ALL_DOMAINS.map((d,i)=>{const[x,y]=point(i,scores[d.id]??0);return x.toFixed(1)+","+y.toFixed(1);}).join(" ");
  const ambPoly=ALL_DOMAINS.map((_,i)=>{const[x,y]=point(i,ambition);return x.toFixed(1)+","+y.toFixed(1);}).join(" ");
  return <div className="w-full flex justify-center overflow-x-auto"><svg viewBox={"0 0 "+size+" "+size} width={size} height={size} className="max-w-full" style={{maxWidth:480}}>
    {[1,2,3,4,5].map(l=><path key={l} d={ringPath(l)} fill="none" stroke="#e2e8f0" strokeWidth={1}/>)}
    {ALL_DOMAINS.map((d,i)=>{const[ax,ay]=point(i,5);const r=maxR+22,a=angleFor(i),lx=cx+r*Math.cos(a),ly=cy+r*Math.sin(a);const anchor=Math.abs(lx-cx)<8?"middle":lx>cx?"start":"end";return <g key={d.id}><line x1={cx} y1={cy} x2={ax} y2={ay} stroke="#e2e8f0" strokeWidth={1}/><text x={lx} y={ly} fontSize={11} fontWeight={600} fill="#475569" textAnchor={anchor} dominantBaseline="middle">{d.code}</text></g>;})}
    <polygon points={ambPoly} fill="none" stroke={NAVY} strokeWidth={2} strokeDasharray="5 4"/>
    <polygon points={scorePoly} fill={BRAND_ACCENT} fillOpacity={0.22} stroke={BRAND_ACCENT} strokeWidth={2.5}/>
    {ALL_DOMAINS.map((d,i)=>{if(scores[d.id]==null)return null;const[x,y]=point(i,scores[d.id]);return <circle key={d.id} cx={x} cy={y} r={3.5} fill={BRAND_ACCENT} stroke="#fff" strokeWidth={1.5}/>;})}
  </svg></div>;
}
function DomainBars({scores,ambition,lang}){
  return <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-x-5 gap-y-2">{ALL_DOMAINS.map(d=>{const v=scores[d.id];const pct=v!=null?(v/5)*100:0;const amb=ambition?(ambition/5)*100:null;return <div key={d.id}><div className="flex items-center justify-between text-xs mb-0.5"><span className="text-slate-600 truncate pr-2"><span className="text-slate-400">{d.code}</span> {L(d.name,lang)}</span><span className="font-semibold text-slate-700 shrink-0">{v??"—"}</span></div><div className="relative h-2.5 w-full rounded-full bg-slate-100 overflow-hidden">{amb!=null&&<div className="absolute top-0 bottom-0 w-0.5 bg-slate-400 z-10" style={{left:amb+"%"}}/>}<div className="h-full rounded-full transition-all" style={{width:pct+"%",background:v?LEVEL_META[v].hex:"transparent"}}/></div></div>;})}</div>;
}

function GuideBlock({model,lang}){
  const steps=[T.step1,T.step2,T.step3,T.step4];
  const isBcm=model.id==="22301";
  return <div className="rounded-xl border border-slate-200 p-5 h-full" style={{background:"linear-gradient(to bottom,#f4f8fb,#fff)"}}>
    <h3 className="text-base font-semibold flex items-center gap-2 mb-2" style={{color:NAVY}}><Icons.book c={BRAND_ACCENT} s={20}/>{L(T.guide,lang)}</h3>
    <p className="text-sm text-slate-600 leading-relaxed">{L(T.guideIntro1,lang)} <strong>{L(isBcm?T.guideIntro2bcm:T.guideIntro2cm,lang)}</strong> {L(T.guideIntro3,lang)} <strong>{model.code}</strong>. {L(T.guideIntro4,lang)} {modelDomainCount(model.id)} {modelDomainCount(model.id)===1?L(T.guideDomainOne,lang):L(T.guideDomainMany,lang)} {L(T.guideScale,lang)}</p>
    <div className="mt-4 space-y-2.5">{steps.map((s,i)=><div key={i} className="flex gap-2.5"><span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-bold text-white" style={{background:NAVY}}>{i+1}</span><p className="text-sm text-slate-600 leading-snug">{L(s,lang)}</p></div>)}</div>
    <div className="mt-4 rounded-lg p-3 text-xs text-slate-600 flex gap-2" style={{background:BRAND_ACCENT+"12"}}><Icons.info c={BRAND_ACCENT} s={16}/><span>{L(T.guideTarget1,lang)} {model.code} {L(T.guideTarget2,lang)} <strong style={{color:NAVY}}>{L(T.guideTarget3,lang)}</strong>{L(T.guideTarget4,lang)}</span></div>
  </div>;
}

function AmbitionPicker({value,onChange,lang}){
  return <div>
    <span className="text-sm font-medium text-slate-600 flex items-center gap-1.5 mb-1"><Icons.gauge s={16}/>{L(T.ambition,lang)}</span>
    <div className="flex gap-1.5">{[1,2,3,4,5].map(lvl=>{const m=LEVEL_META[lvl];const sel=value===lvl;return <button key={lvl} onClick={()=>onChange(lvl)} className={"flex-1 rounded-lg border-2 py-2 text-center transition-all "+(sel?"shadow-sm":"opacity-70 hover:opacity-100")} style={{borderColor:sel?m.hex:"#e2e8f0",background:sel?m.hex+"1a":"#fff"}}><span className="block h-5 w-5 mx-auto rounded-full text-xs font-bold text-white flex items-center justify-center" style={{background:m.hex}}>{lvl}</span><span className="block text-[10px] font-medium mt-0.5 text-slate-600 leading-tight">{L(LEVEL_LABELS[lvl],lang)}</span></button>;})}</div>
    {(()=>{const e=MATURITY_EXPLAINER[value];const a=AMBITION_LEVELS[value];const m=LEVEL_META[value];return <div className="mt-2 rounded-lg border p-3" style={{borderColor:m.hex+"55",background:m.hex+"0d"}}>
      <div className="flex flex-wrap items-baseline gap-x-2"><p className="text-sm font-bold" style={{color:m.hex}}>{lang==="en"?"Level":"Niveau"} {value} · {L(LEVEL_LABELS[value],lang)}</p><span className="text-sm font-medium text-slate-500">— {L(e.headline,lang)}</span>{value===4&&<span className="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold" style={{background:BRAND_ACCENT+"1a",color:ACCENT_DK}}><Icons.target s={12}/>ISO {L(T.isoStreef,lang)}</span>}</div>
      <p className="text-sm text-slate-600 mt-1 leading-relaxed">{L(e.desc,lang)}</p>
      <div className="mt-2 flex flex-wrap gap-1.5">{L(e.signals,lang).map((s,i)=><span key={i} className="inline-flex items-center gap-1 rounded-full bg-white/70 px-2.5 py-1 text-xs text-slate-600 border" style={{borderColor:m.hex+"33"}}><span className="h-1.5 w-1.5 rounded-full" style={{background:m.hex}}/>{s}</span>)}</div>
      <div className="mt-3 rounded-lg overflow-hidden border-2" style={{borderColor:m.hex}}><div className="flex items-center gap-2 px-3 py-2" style={{background:m.hex}}><Icons.building c="#fff" s={16}/><p className="text-sm font-bold text-white uppercase tracking-wide">{L(T.passendVoor,lang)}</p></div><div className="p-3" style={{background:m.hex+"14"}}><p className="text-sm text-slate-700 leading-snug font-medium">{L(a.desc,lang)}</p><p className="text-xs text-slate-500 mt-2 italic"><span className="font-semibold not-italic" style={{color:m.hex}}>{L(T.sectors,lang)}</span>{L(a.examples,lang)}</p></div></div>
    </div>;})()}
  </div>;
}

function TextMatrixView({current,onSelect,lang}){
  const stats=computeStats(current.scores);
  const colTemplate="180px repeat(5, minmax(160px, 1fr))";
  return <div className="space-y-3">
    <div className="flex flex-wrap items-center gap-2 rounded-lg border border-slate-200 bg-white px-4 py-2.5 text-xs text-slate-500"><Icons.info c="#94a3b8" s={16}/>{L(T.matrixHint,lang)}</div>
    <div className="overflow-x-auto rounded-xl border border-slate-200 bg-white p-2 sm:p-3"><div className="grid gap-1.5" style={{gridTemplateColumns:colTemplate,minWidth:980}}>
      <div className="sticky left-0 z-20 bg-white flex items-end px-2 pb-1 text-xs font-semibold uppercase tracking-wide text-slate-400" style={{boxShadow:"3px 0 6px -4px rgba(0,0,0,0.15)"}}>{L(T.domain,lang)}</div>
      {[1,2,3,4,5].map(lvl=><div key={lvl} className="rounded-lg px-2 py-2 text-center text-white" style={{background:LEVEL_META[lvl].hex}}><div className="text-sm font-bold leading-none">{lvl}</div><div className="text-xs font-medium mt-0.5">{L(LEVEL_LABELS[lvl],lang)}</div></div>)}
      {SECTIONS_ACTIVE.map(sec=>{const a=stats.sectionAvgs[sec.id];const v=maturityVerdict(a,lang);return <Fragment key={sec.id}>
        <div className="rounded-lg px-3 py-1.5 mt-1" style={{background:NAVY,gridColumn:"1 / -1"}}><span className="sticky left-3 inline-flex items-center gap-2 text-sm font-semibold text-white"><span className="flex h-5 w-5 items-center justify-center rounded bg-white text-xs font-bold" style={{color:NAVY}}>{sec.id}</span>{L(sec.title,lang)}{v&&<span className={"rounded-full "+v.chip+" px-2 py-0.5 text-xs font-semibold"}>{(Math.round(a*10)/10).toFixed(1)} · {v.label}</span>}</span></div>
        {sec.domains.map(d=>{const value=current.scores[d.id];return <Fragment key={d.id}>
          <div className="sticky left-0 z-10 bg-white rounded-lg px-2 py-2 flex flex-col justify-center" style={{boxShadow:"3px 0 6px -4px rgba(0,0,0,0.15)"}}><span className="text-xs font-semibold text-slate-700 leading-snug"><span className="text-slate-400">{d.code}.</span> {L(d.name,lang)}</span><span className="text-xs text-slate-400 mt-0.5">{L(d.iso,lang)}</span><div className="mt-1.5"><LevelChip level={value} small lang={lang}/></div></div>
          {[1,2,3,4,5].map(lvl=>{const sel=value===lvl;const m=LEVEL_META[lvl];return <button key={lvl} onClick={()=>onSelect(d.id,sel?null:lvl)} className={"relative text-left rounded-lg p-2.5 border transition-all "+m.bg+" "+(sel?m.border+" border-2 shadow-sm":"border-slate-200 hover:border-slate-400")}><div className="flex items-center gap-1.5 mb-1"><span className="h-5 w-5 rounded-full flex items-center justify-center text-xs font-bold text-white shrink-0" style={{background:m.hex}}>{lvl}</span><span className={"text-xs font-semibold "+m.text}>{L(LEVEL_LABELS[lvl],lang)}</span>{sel&&<Icons.check cn={"ml-auto "+m.text} s={16}/>}</div><p className={"text-xs leading-snug "+(sel?"text-slate-800 font-medium":"text-slate-600")}>{L(d.levels[lvl-1],lang)}</p></button>;})}
        </Fragment>;})}
      </Fragment>;})}
    </div></div>
  </div>;
}

function AssessmentView({current,setCurrent,step,setStep,onSave,saveState,model,lang}){
  const onSelect=useCallback((id,lvl)=>setCurrent(c=>({...c,scores:{...c.scores,[id]:lvl}})),[setCurrent]);
  const stats=computeStats(current.scores);const DN=ALL_DOMAINS.length;
  const SaveBtn=()=><button onClick={onSave} disabled={saveState==="saving"} className="inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors hover:opacity-90 shrink-0" style={{background:saveState==="done"?"#059669":NAVY}}>{saveState==="saving"?<Icons.clock s={16}/>:saveState==="done"?<Icons.check s={16}/>:<Icons.save s={16}/>}{saveState==="done"?L(T.saved,lang):L(T.save,lang)}</button>;
  if(step==="matrix"){
    return <div className="space-y-5">
      <div className="rounded-xl border border-slate-200 bg-white p-4 sm:p-5"><div className="flex flex-wrap items-center justify-between gap-3"><div className="flex items-center gap-3 min-w-0"><button onClick={()=>setStep("intro")} className="inline-flex items-center gap-1.5 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50 shrink-0"><Icons.arrowL s={16}/>{L(T.backToData,lang)}</button><div className="min-w-0"><h2 className="text-base font-bold truncate" style={{color:NAVY}}>{current.assessmentName||L(T.unnamed,lang)}</h2><p className="text-xs text-slate-500 truncate">{current.orgName||L(T.orgUnknown,lang)} · {current.date} · {L(T.ambitionShort,lang).toLowerCase()} {current.ambition??TARGET}</p></div></div><SaveBtn/></div><div className="mt-4"><ProgressBar value={stats.ratedCount} total={DN} lang={lang}/></div></div>
      <TextMatrixView current={current} onSelect={onSelect} lang={lang}/>
      <div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-200 bg-slate-50 p-4"><p className="text-sm text-slate-600">{L(T.doneFillSave,lang)}</p><SaveBtn/></div>
    </div>;
  }
  return <div className="space-y-6">
    <div className="grid lg:grid-cols-5 gap-4">
      <div className="lg:col-span-2"><GuideBlock model={model} lang={lang}/></div>
      <div className="lg:col-span-3 rounded-xl border border-slate-200 bg-white p-5">
        <h3 className="text-base font-semibold flex items-center gap-2 mb-3" style={{color:NAVY}}><Icons.building c={BRAND_ACCENT} s={20}/>{L(T.dataAmbition,lang)}</h3>
        <div className="grid sm:grid-cols-2 gap-4">
          <label className="block"><span className="text-sm font-medium text-slate-600 flex items-center gap-1.5 mb-1"><Icons.building s={16}/>{L(T.org,lang)}</span><input value={current.orgName} onChange={e=>setCurrent(c=>({...c,orgName:e.target.value}))} placeholder={L(T.orgPh,lang)} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-slate-500"/></label>
          <label className="block"><span className="text-sm font-medium text-slate-600 flex items-center gap-1.5 mb-1"><Icons.cal s={16}/>{L(T.date,lang)}</span><input type="date" value={current.date} onChange={e=>setCurrent(c=>({...c,date:e.target.value}))} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-slate-500"/></label>
          <label className="block"><span className="text-sm font-medium text-slate-600 flex items-center gap-1.5 mb-1"><Icons.target s={16}/>{L(T.sector,lang)}</span><select value={current.sector} onChange={e=>setCurrent(c=>({...c,sector:e.target.value}))} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-slate-500 bg-white"><option value="">{L(T.sectorPh,lang)}</option>{SECTORS.map(o=><option key={o.id} value={o.id}>{L(o,lang)}</option>)}</select></label>
          <label className="block"><span className="text-sm font-medium text-slate-600 flex items-center gap-1.5 mb-1"><Icons.building s={16}/>{L(T.orgSize,lang)}</span><select value={current.orgSize} onChange={e=>setCurrent(c=>({...c,orgSize:e.target.value}))} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-slate-500 bg-white"><option value="">{L(T.orgSizePh,lang)}</option>{ORG_SIZES.map(o=><option key={o.id} value={o.id}>{L(o,lang)}</option>)}</select></label>
          <label className="block sm:col-span-2"><span className="text-sm font-medium text-slate-600 flex items-center gap-1.5 mb-1"><Icons.file s={16}/>{L(T.assessName,lang)}</span><input value={current.assessmentName} onChange={e=>setCurrent(c=>({...c,assessmentName:e.target.value}))} placeholder={L(T.assessNamePh,lang)} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-slate-500"/></label>
          <div className="sm:col-span-2"><AmbitionPicker value={current.ambition??TARGET} onChange={lvl=>setCurrent(c=>({...c,ambition:lvl}))} lang={lang}/></div>
        </div>
        <p className="mt-3 flex items-start gap-2 rounded-lg bg-slate-50 border border-slate-200 p-3 text-xs text-slate-500"><Icons.shield c="#2563EB" s={16} cn="shrink-0 mt-0.5"/>{L(T.anonNote,lang)}</p>
        <div className="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-slate-100 pt-4"><div className="flex-1 min-w-[180px]"><ProgressBar value={stats.ratedCount} total={DN} lang={lang}/></div><button onClick={onSave} className="inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-90" style={{background:NAVY}}><Icons.save s={16}/>{L(T.saveShowMatrix,lang)}<Icons.arrowR s={16}/></button></div>
      </div>
    </div>
    <div className="rounded-xl border border-dashed border-slate-300 bg-white p-6 text-center"><Icons.chevR c="#cbd5e1" s={36} cn="mx-auto mb-2"/><p className="text-slate-500 text-sm">{L(T.introHint1,lang)} <strong>{L(T.saveShowMatrix,lang)}</strong>. {L(T.introHint2,lang)} <strong>{L(T.introHintMatrix,lang)}</strong> {L(T.introHint3,lang)} {DN} {DN===1?L(T.guideDomainOne,lang):L(T.guideDomainMany,lang)} {L(T.introHint4,lang)}</p>{(stats.ratedCount>0||current.savedAt)&&<button onClick={()=>setStep("matrix")} className="mt-3 inline-flex items-center gap-1.5 rounded-lg border px-4 py-2 text-sm font-medium hover:bg-slate-50" style={{borderColor:NAVY,color:NAVY}}>{L(T.directMatrix,lang)}<Icons.arrowR s={16}/></button>}</div>
  </div>;
}

function Report({current,lang}){
  const scores=current.scores,stats=computeStats(scores),ambition=current.ambition??TARGET;
  const rated=ALL_DOMAINS.filter(d=>scores[d.id]!=null);
  const weakest=[...rated].sort((a,b)=>scores[a.id]-scores[b.id]).slice(0,3);
  const strongest=[...rated].sort((a,b)=>scores[b.id]-scores[a.id]).slice(0,3);
  if(rated.length===0) return <div className="rounded-xl border border-slate-200 bg-white p-10 text-center"><Icons.clipboard c="#cbd5e1" s={40} cn="mx-auto mb-3"/><p className="text-slate-500">{L(T.reportEmpty,lang)} <strong>{L(T.tabAssess,lang)}</strong>.</p></div>;
  const v=maturityVerdict(stats.avg,lang);
  return <div className="space-y-6">
    <div className="rounded-xl border border-slate-200 bg-white p-5"><h2 className="text-xl font-bold text-slate-800">{current.assessmentName||L(T.reportTitle,lang)}</h2><p className="text-slate-500 text-sm mt-1">{current.orgName||L(T.orgUnknown,lang)} · {current.date}</p><div className="mt-2 inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-semibold" style={{background:LEVEL_META[ambition].hex+"1a",color:LEVEL_META[ambition].hex}}><Icons.gauge s={14}/>{L(T.ambitionLevel,lang)}: {ambition} · {L(LEVEL_LABELS[ambition],lang)}</div></div>
    <div className="rounded-xl border border-slate-200 bg-white p-5 break-inside-avoid"><h3 className="text-lg font-semibold text-slate-800 flex items-center gap-2 mb-4"><Icons.award c="#475569" s={20}/>{L(T.mgmtSummary,lang)}</h3><div className="grid sm:grid-cols-2 gap-4">
      <div className="rounded-lg border border-red-200 bg-red-50 p-4"><p className="text-sm font-semibold text-red-800 flex items-center gap-1.5 mb-2"><Icons.shield s={16}/>{L(T.priorities,lang)}</p><ul className="space-y-1.5">{weakest.map(d=><li key={d.id} className="text-sm text-red-900 flex items-center justify-between gap-2"><span><span className="text-red-400">{d.code}</span> {L(d.name,lang)}</span><LevelChip level={scores[d.id]} small lang={lang}/></li>)}</ul></div>
      <div className="rounded-lg border border-green-200 bg-green-50 p-4"><p className="text-sm font-semibold text-green-800 flex items-center gap-1.5 mb-2"><Icons.up s={16}/>{L(T.strengths,lang)}</p><ul className="space-y-1.5">{strongest.map(d=><li key={d.id} className="text-sm text-green-900 flex items-center justify-between gap-2"><span><span className="text-green-500">{d.code}</span> {L(d.name,lang)}</span><LevelChip level={scores[d.id]} small lang={lang}/></li>)}</ul></div>
    </div></div>
    <div className="rounded-xl border border-slate-200 bg-white p-5 break-inside-avoid">
      <h3 className="text-lg font-semibold text-slate-800 mb-1">{L(T.profile,lang)}</h3>
      <p className="text-xs text-slate-400 mb-4">{L(T.profileSub1,lang)} {ambition} {L(T.profileSub2,lang)}</p>
      <div className="grid lg:grid-cols-12 gap-5 items-center">
        <div className="lg:col-span-4 space-y-2.5">
          <div className="rounded-lg border p-3" style={{borderColor:v?v.hex+"55":"#e2e8f0",background:v?v.hex+"0d":"#f8fafc"}}><div className="flex items-baseline gap-1"><span className="text-3xl font-bold text-slate-800">{stats.avg!=null?(Math.round(stats.avg*10)/10).toFixed(1):"—"}</span><span className="text-slate-400 text-sm">/ 5</span></div><p className="text-xs text-slate-500">{L(T.avgScore,lang)}</p>{v&&<span className={"mt-1.5 inline-flex items-center gap-1 rounded-full "+v.chip+" px-2 py-0.5 text-xs font-semibold"}>{v.emoji} {v.label}</span>}</div>
          <div className="rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 flex items-baseline justify-between"><span className="text-xs text-slate-500">{L(T.totalScore,lang)}</span><span className="text-lg font-bold text-slate-800">{stats.total}</span></div>
          <div className="rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 flex items-baseline justify-between"><span className="text-xs text-slate-500">{L(T.ratedDomains,lang)}</span><span className="text-lg font-bold text-slate-800">{stats.ratedCount}<span className="text-slate-300 text-sm">/{ALL_DOMAINS.length}</span></span></div>
          <div className="rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 flex items-center justify-between"><span className="text-xs text-slate-500 flex items-center gap-1"><Icons.target s={14}/>{L(T.ambitionShort,lang)}</span><span className="text-lg font-bold" style={{color:LEVEL_META[ambition].hex}}>≥ {ambition}</span></div>
          <div className="pt-1 space-y-1.5"><span className="flex items-center gap-2 text-xs font-medium text-slate-600"><span className="inline-block h-3 w-5 rounded-sm shrink-0" style={{background:BRAND_ACCENT,opacity:0.5}}/>{L(T.measuredLevel,lang)}</span><span className="flex items-center gap-2 text-xs font-medium text-slate-600"><svg width="22" height="8" className="shrink-0"><line x1="0" y1="4" x2="22" y2="4" stroke={NAVY} strokeWidth="2.5" strokeDasharray="5 4"/></svg>{L(T.ambitionLevel,lang)} ({ambition})</span></div>
        </div>
        <div className="lg:col-span-8"><RadarChartSVG scores={scores} ambition={ambition}/></div>
      </div>
      <div className="mt-4 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-x-4 gap-y-1 border-t border-slate-100 pt-3">{ALL_DOMAINS.map(d=><p key={d.id} className="text-xs text-slate-500 truncate"><span className="font-semibold text-slate-600">{d.code}</span> · {L(d.name,lang)}</p>)}</div>
    </div>
    <div className="rounded-xl border border-slate-200 bg-white p-5 break-inside-avoid"><h3 className="text-lg font-semibold text-slate-800 mb-1">{L(T.perDomainScore,lang)}</h3><p className="text-xs text-slate-400 mb-4">{L(T.perDomainSub,lang)} ({ambition}).</p><DomainBars scores={scores} ambition={ambition} lang={lang}/></div>
    <div className="rounded-xl border border-slate-200 bg-white p-5 break-inside-avoid"><h3 className="text-lg font-semibold text-slate-800 mb-4">{L(T.avgPerSection,lang)}</h3><div className="grid sm:grid-cols-2 lg:grid-cols-5 gap-3">{SECTIONS_ACTIVE.map(sec=>{const a=stats.sectionAvgs[sec.id];const sv=maturityVerdict(a,lang);return <div key={sec.id} className={"rounded-lg border p-3 "+(sv?sv.border+" "+sv.bg:"border-slate-200 bg-slate-50")}><p className="text-xs font-semibold text-slate-500">{sec.id}</p><p className="text-sm text-slate-700 leading-tight mt-0.5 h-9 overflow-hidden">{L(sec.title,lang)}</p><p className="text-2xl font-bold text-slate-800 mt-1">{a!=null?(Math.round(a*10)/10).toFixed(1):"—"}</p>{sv&&<p className={"text-xs "+sv.text}>{sv.label}</p>}</div>;})}</div></div>
    <div><h3 className="text-lg font-semibold text-slate-800 mb-3 flex items-center gap-2"><Icons.file c="#475569" s={20}/>{L(T.resultPerDomain,lang)}</h3><div className="space-y-3">{ALL_DOMAINS.map(d=>{const lvl=scores[d.id];if(lvl==null)return null;const below=lvl<ambition;const m=LEVEL_META[lvl];const nextDesc=lvl<5?L(d.firstSteps?.[lvl-1]||d.levels[lvl],lang):null;return <div key={d.id} className={"rounded-xl border p-4 break-inside-avoid "+(below?"border-amber-300 bg-amber-50/60":"border-slate-200 bg-white")}><div className="flex flex-wrap items-start justify-between gap-2"><div className="flex-1 min-w-0"><h4 className="font-semibold text-slate-800"><span className="text-slate-400 mr-1.5">{d.code}.</span>{L(d.name,lang)}{below&&<span className="ml-2 inline-flex items-center gap-1 text-xs text-amber-700"><Icons.alert s={14}/>{L(T.belowAmbition,lang)}</span>}</h4><p className="text-xs text-slate-400 mt-0.5">{model22361(d)} {L(d.iso,lang)}</p></div><LevelChip level={lvl} lang={lang}/></div><div className={"mt-3 rounded-lg "+m.bg+" "+m.border+" border p-3"}><p className="text-xs font-semibold uppercase tracking-wide mb-1 text-slate-500">{L(T.chosenLevel,lang)}: {lvl} · {L(LEVEL_LABELS[lvl],lang)}</p><p className="text-sm text-slate-700">{L(d.levels[lvl-1],lang)}</p></div>{nextDesc?<div className="mt-3 rounded-lg border border-slate-200 bg-slate-50 p-3"><p className="text-xs font-semibold uppercase tracking-wide mb-1 text-slate-500 flex items-center gap-1.5"><Icons.target s={14}/>{L(T.improveAction,lang)} {lvl+1} ({L(LEVEL_LABELS[lvl+1],lang)})</p><p className="text-sm text-slate-700">{nextDesc}</p></div>:<div className="mt-3 rounded-lg border border-green-200 bg-green-50 p-3"><p className="text-sm text-green-800 flex items-center gap-1.5"><Icons.checkCircle s={16}/>{L(T.highestReached,lang)}</p></div>}</div>;})}</div></div>
  </div>;
}
function model22361(d){ return "ISO"; }

const ACC={red:{bar:"bg-red-500",text:"text-red-700",bg:"bg-red-50",border:"border-red-200",chip:"bg-red-100 text-red-800"},amber:{bar:"bg-amber-500",text:"text-amber-700",bg:"bg-amber-50",border:"border-amber-200",chip:"bg-amber-100 text-amber-800"},green:{bar:"bg-green-600",text:"text-green-700",bg:"bg-green-50",border:"border-green-200",chip:"bg-green-100 text-green-800"}};
function PlanCard({domain,level,ambition,done,onToggle,lang}){
  const cur=LEVEL_META[level],nxt=LEVEL_META[level+1];
  const firstAction=L(domain.firstSteps?.[level-1]||domain.levels[level],lang);
  const thenAction=level+1<ambition?L(domain.firstSteps?.[level]||domain.levels[level+1],lang):null;
  const stepsLeft=Math.max(0,ambition-level);
  return <div className={"rounded-lg border border-slate-200 bg-white p-3.5 "+(done?"opacity-60":"")}><div className="flex gap-3"><button onClick={onToggle} className="mt-0.5 shrink-0">{done?<Icons.checkCircle c="#16a34a" s={20}/>:<Icons.circle c="#cbd5e1" s={20}/>}</button><div className="flex-1 min-w-0"><div className="flex flex-wrap items-center gap-x-2 gap-y-1"><span className={"text-sm font-semibold text-slate-800 "+(done?"line-through":"")}><span className="text-slate-400">{domain.code}.</span> {L(domain.name,lang)}</span><span className="inline-flex items-center gap-1"><span className="h-5 w-5 rounded-full flex items-center justify-center text-xs font-bold text-white" style={{background:cur.hex}}>{level}</span><Icons.arrowR c="#94a3b8" s={14}/><span className="h-5 w-5 rounded-full flex items-center justify-center text-xs font-bold text-white" style={{background:nxt.hex}}>{level+1}</span></span></div>
  <div className="mt-2 rounded-md bg-emerald-50 border border-emerald-200 p-2.5"><p className="text-xs font-semibold text-emerald-800 flex items-center gap-1.5 mb-0.5"><Icons.rocket s={14}/>{L(T.startHereCard,lang)}</p><p className={"text-sm text-emerald-900 "+(done?"line-through":"")}>{firstAction}</p></div>
  {thenAction&&<div className="mt-1.5 rounded-md bg-slate-50 border border-slate-200 p-2.5"><p className="text-xs font-semibold text-slate-500 flex items-center gap-1.5 mb-0.5"><Icons.chevR s={14}/>{L(T.thenCard,lang)}</p><p className="text-sm text-slate-600">{thenAction}</p></div>}
  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-slate-400"><span>{L(domain.sectionTitle,lang)}</span>{stepsLeft>1&&<span className="text-amber-600 font-medium">{L(T.stepsLeft1,lang)} {stepsLeft} {L(T.stepsLeft2,lang)}</span>}</div>
  </div></div></div>;
}
function ActionHeatmap({scores,ambition,lang}){
  const [sel,setSel]=useState(null);
  // X = tijdlijn (0 nu, 1 binnenkort, 2 later) afgeleid van huidig niveau
  // Y = prioriteit (2 hoog, 1 midden, 0 laag) afgeleid van afstand tot ambitie
  const timeOf=(lvl)=> lvl<=2?0 : lvl===3?1 : 2;          // lager niveau = eerder
  const prioOf=(dist)=> dist>=3?2 : dist===2?1 : dist===1?1 : 0; // grotere afstand = hogere prioriteit
  const cells={}; const unrated=[]; const atAmbition=[];
  ALL_DOMAINS.forEach(d=>{
    const lvl=scores[d.id];
    if(lvl==null){ unrated.push(d); return; }
    const dist=Math.max(0,ambition-lvl);
    if(dist<=0){ atAmbition.push(d); return; }   // op/boven ambitie -> aparte rij
    const x=timeOf(lvl), y=prioOf(dist);
    const k=y+"-"+x;
    (cells[k]=cells[k]||[]).push(d);
  });
  // kleur op basis van prioriteit (rij)
  const prioColor=(y)=> y===2?{bg:"#fee2e2",border:"#fca5a5",chip:"#dc2626",label:L(T.prioHigh,lang)}
                      : y===1?{bg:"#ffedd5",border:"#fdba74",chip:"#ea580c",label:L(T.prioMed,lang)}
                      : {bg:"#fef9c3",border:"#fde047",chip:"#ca8a04",label:L(T.prioLow,lang)};
  const cols=[{x:0,t:T.timeNow,s:T.timeNowSub},{x:1,t:T.timeSoon,s:T.timeSoonSub},{x:2,t:T.timeLater,s:T.timeLaterSub}];
  const rows=[2,1,0];
  const selDomain=sel?DOMAIN_BY_ID[sel]:null;
  const selLvl=selDomain?scores[selDomain.id]:null;
  const selAction=selDomain&&selLvl!=null&&selLvl<5?L(selDomain.firstSteps?.[selLvl-1]||selDomain.levels[selLvl],lang):null;
  const shortAction=(d,lvl)=> (lvl!=null&&lvl<5)?L(d.firstSteps?.[lvl-1]||d.levels[lvl],lang):"";

  const Tag=({d})=>{
    const lvl=scores[d.id];const c=LEVEL_META[lvl];const isSel=sel===d.id;
    return <button onClick={()=>setSel(isSel?null:d.id)} className="w-full text-left rounded-md px-1.5 py-1 transition-all hover:shadow-sm" style={{background:"#ffffffdd",border:"1px solid #e2e8f0",outline:isSel?"2px solid "+NAVY:"none"}}>
      <div className="flex items-center gap-1">
        <span className="inline-flex h-4 min-w-[30px] px-1 items-center justify-center rounded text-[9px] font-bold text-white shrink-0" style={{background:c.hex}}>{d.code}</span>
        <span className="text-[10px] leading-tight text-slate-700" style={{display:"-webkit-box",WebkitLineClamp:2,WebkitBoxOrient:"vertical",overflow:"hidden"}}>{shortAction(d,lvl)||L(d.name,lang)}</span>
      </div>
    </button>;
  };

  return <div className="rounded-xl border border-slate-200 bg-white p-5 break-inside-avoid">
    <h3 className="text-lg font-semibold text-slate-800 flex items-center gap-2 mb-1"><Icons.target c="#475569" s={20}/>{L(T.heatmapTitle,lang)}</h3>
    <p className="text-xs text-slate-400 mb-4">{L(T.heatmapSub,lang)}</p>

    <div className="overflow-x-auto">
      <div className="flex" style={{minWidth:640}}>
        {/* Y-as label */}
        <div className="flex items-center justify-center pr-1" style={{width:26}}>
          <span className="text-xs font-semibold text-slate-500" style={{writingMode:"vertical-rl",transform:"rotate(180deg)",whiteSpace:"nowrap"}}>{L(T.axisPriority,lang)}</span>
        </div>
        <div className="flex-1">
          <div className="grid gap-1.5" style={{gridTemplateColumns:"70px repeat(3,1fr)"}}>
            {/* kop met tijdlijn */}
            <div></div>
            {cols.map(col=><div key={col.x} className="text-center pb-1"><span className="text-xs font-bold text-slate-700">{L(col.t,lang)}</span><span className="block text-[10px] text-slate-400">{L(col.s,lang)}</span></div>)}
            {/* rijen */}
            {rows.map(y=>{
              const c=prioColor(y);
              return <Fragment key={y}>
                <div className="flex items-center justify-center rounded-md text-xs font-bold text-white" style={{background:c.chip,minHeight:90}}>{c.label}</div>
                {cols.map(col=>{
                  const list=cells[y+"-"+col.x]||[];
                  return <div key={col.x} className="rounded-md p-1 space-y-1" style={{background:c.bg,border:"1px solid "+c.border,minHeight:90}}>
                    {list.map(d=><Tag key={d.id} d={d}/>)}
                  </div>;
                })}
              </Fragment>;
            })}
          </div>
          <p className="text-center text-xs font-semibold text-slate-500 mt-1.5">{L(T.axisTime,lang)}</p>
        </div>
      </div>
    </div>

    {/* op/boven ambitie */}
    {atAmbition.length>0&&<div className="mt-3 rounded-lg border border-green-200 bg-green-50 p-2.5">
      <p className="text-xs font-semibold text-green-700 mb-1.5 flex items-center gap-1.5"><Icons.checkCircle c="#16a34a" s={14}/>{L(T.heatAtAmbition,lang)} ({atAmbition.length})</p>
      <div className="flex flex-wrap gap-1.5">{atAmbition.map(d=><span key={d.id} className="inline-flex items-center gap-1 rounded-md bg-white border border-green-200 px-1.5 py-0.5 text-[10px] text-slate-600"><span className="inline-flex h-3.5 px-1 items-center rounded text-[9px] font-bold text-white" style={{background:LEVEL_META[scores[d.id]].hex}}>{d.code}</span> {L(d.name,lang)}</span>)}</div>
    </div>}

    {/* niet beoordeeld */}
    {unrated.length>0&&<div className="mt-3 rounded-lg border border-slate-200 bg-slate-50 p-2.5">
      <p className="text-xs font-semibold text-slate-500 mb-1.5">{L(T.heatNotRated,lang)} ({unrated.length})</p>
      <div className="flex flex-wrap gap-1.5">{unrated.map(d=><span key={d.id} className="inline-flex items-center gap-1 rounded-md bg-white border border-slate-200 px-1.5 py-0.5 text-[10px] text-slate-500"><span className="font-bold text-slate-400">{d.code}</span> {L(d.name,lang)}</span>)}</div>
    </div>}

    {/* geselecteerde actie */}
    {selDomain&&<div className="mt-3 rounded-lg border-2 p-3" style={{borderColor:(selLvl!=null?LEVEL_META[selLvl].hex:"#cbd5e1")}}>
      <p className="text-sm font-semibold text-slate-800"><span className="text-slate-400">{selDomain.code}.</span> {L(selDomain.name,lang)} {selLvl!=null&&<LevelChip level={selLvl} small lang={lang}/>}</p>
      {selAction?<p className="text-sm text-slate-700 mt-1.5"><span className="font-semibold text-emerald-700">{L(T.firstActionLabel,lang)} </span>{selAction}</p>
        :selLvl===5?<p className="text-sm text-green-700 mt-1.5 flex items-center gap-1.5"><Icons.checkCircle c="#16a34a" s={16}/>{L(T.highestReached,lang)}</p>
        :<p className="text-sm text-slate-500 mt-1.5">{L(T.heatNotRated,lang)}.</p>}
    </div>}
  </div>;
}

function ImprovementPlanView({current,setCurrent,saved,onLoad,lang}){
  const scores=current.scores,ambition=current.ambition??TARGET,planDone=current.planDone||{};
  const toggleDone=key=>setCurrent(c=>({...c,planDone:{...(c.planDone||{}),[key]:!(c.planDone||{})[key]}}));
  const rated=ALL_DOMAINS.filter(d=>scores[d.id]!=null);
  const unrated=ALL_DOMAINS.filter(d=>scores[d.id]==null);
  const below=ALL_DOMAINS.filter(d=>scores[d.id]!=null&&scores[d.id]<ambition);
  const totalSteps=below.reduce((s,d)=>s+(ambition-scores[d.id]),0);
  const doneCount=below.filter(d=>planDone[d.id+":"+(scores[d.id]+1)]).length;
  const phase1=below.filter(d=>scores[d.id]<=2).sort((a,b)=>scores[a.id]-scores[b.id]);
  const phase2=below.filter(d=>scores[d.id]===3&&ambition>3).sort((a,b)=>scores[a.id]-scores[b.id]);
  const phase3=below.filter(d=>scores[d.id]>=ambition-1&&scores[d.id]>=3&&!phase2.includes(d)&&!phase1.includes(d));
  const PHASES=[{id:"p1",title:T.phase1Title,horizon:T.horizon1,desc:T.phase1Desc,icon:Icons.shield,accent:"red",domains:phase1},{id:"p2",title:T.phase2Title,horizon:T.horizon2,desc:T.phase2Desc,icon:Icons.target,accent:"amber",domains:phase2},{id:"p3",title:T.phase3Title,horizon:T.horizon3,desc:T.phase3Desc,icon:Icons.rocket,accent:"green",domains:phase3}];
  if(rated.length===0) return <div className="rounded-xl border border-slate-200 bg-white p-10 text-center"><Icons.route c="#cbd5e1" s={40} cn="mx-auto mb-3"/><p className="text-slate-500">{L(T.planEmpty,lang)}</p></div>;
  const Tile=({value,label,accent,children})=><div className={"rounded-xl border p-4 "+(accent?accent.border+" "+accent.bg:"border-slate-200 bg-white")}><span className="text-3xl font-bold text-slate-800">{value}</span><p className="text-sm text-slate-500 mt-1">{label}</p>{children}</div>;
  return <div className="space-y-6">
    <div className="rounded-xl border border-slate-200 bg-white p-5"><div className="flex flex-wrap items-start justify-between gap-3"><div><h2 className="text-xl font-bold text-slate-800 flex items-center gap-2"><Icons.route c="#475569" s={20}/>{L(T.planTitle,lang)}</h2><p className="text-sm text-slate-500 mt-1">{L(T.planBasedOn,lang)} <span className="font-medium text-slate-700">{current.assessmentName||L(T.currentAssessment,lang)}</span> · {current.orgName||L(T.orgUnknown,lang)} · {L(T.ambitionLevelWord,lang)} <span className="font-semibold" style={{color:LEVEL_META[ambition].hex}}>{ambition} ({L(LEVEL_LABELS[ambition],lang)})</span>.</p></div><div className="flex items-center gap-2 no-print">{saved.length>0&&<select value={current.id} onChange={e=>{const o=saved.find(s=>s.id===e.target.value);if(o)onLoad(o);}} className="rounded-lg border border-slate-300 px-2 py-2 text-sm outline-none"><option value={current.id}>{L(T.currentAssessment,lang)}</option>{saved.filter(s=>s.id!==current.id).map(s=><option key={s.id} value={s.id}>{s.assessmentName||s.date}</option>)}</select>}</div></div></div>
    <ActionHeatmap scores={scores} ambition={ambition} lang={lang}/>
    <div className="grid grid-cols-2 lg:grid-cols-4 gap-3"><Tile value={below.length} label={L(T.domainsBelowAmbition,lang)} accent={below.length?ACC.red:ACC.green}/><Tile value={totalSteps} label={L(T.stepsToLevel,lang)+" "+ambition} accent={ACC.amber}/><Tile value={doneCount+"/"+below.length} label={L(T.prioActionsDone,lang)}><div className="mt-2 h-1.5 w-full rounded-full bg-slate-200 overflow-hidden"><div className="h-full rounded-full bg-green-500" style={{width:(below.length?(doneCount/below.length)*100:0)+"%"}}/></div></Tile><Tile value={unrated.length} label={L(T.toRate,lang)}/></div>
    {below.length>0&&<div className="rounded-xl border-2 border-emerald-300 bg-emerald-50 p-5 break-inside-avoid"><h3 className="text-base font-semibold text-emerald-900 flex items-center gap-2 mb-1"><Icons.rocket s={20}/>{L(T.beginHere,lang)}</h3><p className="text-xs text-emerald-700 mb-3">{L(T.beginHereSub,lang)}</p><ol className="space-y-2">{[...below].sort((a,b)=>scores[a.id]-scores[b.id]).slice(0,3).map((d,i)=><li key={d.id} className="flex gap-3 rounded-lg bg-white border border-emerald-200 p-3"><span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-emerald-600 text-xs font-bold text-white">{i+1}</span><div className="min-w-0"><p className="text-sm font-semibold text-slate-800"><span className="text-slate-400">{d.code}.</span> {L(d.name,lang)} <LevelChip level={scores[d.id]} small lang={lang}/></p><p className="text-sm text-slate-600 mt-0.5">{L(d.firstSteps?.[scores[d.id]-1]||d.levels[scores[d.id]],lang)}</p></div></li>)}</ol></div>}
    {below.length===0&&<div className="rounded-xl border border-green-200 bg-green-50 p-5 flex items-start gap-3"><Icons.checkCircle c="#16a34a" s={24}/><div><p className="font-semibold text-green-800">{L(T.allAtAmbition1,lang)} ({ambition}).</p><p className="text-sm text-green-700 mt-1">{L(T.allAtAmbition2,lang)}</p></div></div>}
    {PHASES.map(phase=>{if(!phase.domains||phase.domains.length===0)return null;const ac=ACC[phase.accent];const PIcon=phase.icon;const phaseDone=phase.domains.filter(d=>planDone[d.id+":"+(scores[d.id]+1)]).length;return <section key={phase.id} className="break-inside-avoid"><div className={"rounded-t-xl border "+ac.border+" "+ac.bg+" px-4 py-3"}><div className="flex flex-wrap items-center gap-2"><span className={"flex h-8 w-8 items-center justify-center rounded-lg "+ac.bar+" text-white"}><PIcon c="#fff" s={16}/></span><div className="flex-1 min-w-0"><h3 className={"font-semibold "+ac.text}>{L(phase.title,lang)}</h3><p className="text-xs text-slate-500">{L(phase.desc,lang)}</p></div><span className={"inline-flex items-center gap-1 rounded-full "+ac.chip+" px-2.5 py-1 text-xs font-semibold"}><Icons.clock s={14}/>{L(phase.horizon,lang)}</span><span className="text-xs text-slate-500 font-medium">{phaseDone}/{phase.domains.length} {L(T.doneWord,lang)}</span></div></div><div className="rounded-b-xl border border-t-0 border-slate-200 bg-slate-50 p-3 grid gap-2">{phase.domains.map(d=>{const lvl=scores[d.id];const key=d.id+":"+(lvl+1);return <PlanCard key={d.id} domain={d} level={lvl} ambition={ambition} done={!!planDone[key]} onToggle={()=>toggleDone(key)} lang={lang}/>;})}</div></section>;})}
    {unrated.length>0&&<section><div className="rounded-t-xl border border-slate-200 bg-slate-100 px-4 py-3 flex flex-wrap items-center gap-2"><span className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-500 text-white"><Icons.flag c="#fff" s={16}/></span><div className="flex-1 min-w-0"><h3 className="font-semibold text-slate-700">{L(T.rateFirst,lang)}</h3><p className="text-xs text-slate-500">{L(T.rateFirstSub,lang)}</p></div><span className="rounded-full bg-slate-200 px-2.5 py-1 text-xs font-semibold text-slate-600">{unrated.length}</span></div><div className="rounded-b-xl border border-t-0 border-slate-200 bg-white p-3 grid sm:grid-cols-2 gap-2">{unrated.map(d=><div key={d.id} className="text-sm text-slate-600 rounded-lg border border-slate-200 px-3 py-2"><span className="text-slate-400">{d.code}.</span> {L(d.name,lang)}</div>)}</div></section>}
  </div>;
}

function SavedView({saved,onOpen,onRename,onDelete,selected,toggleSelect,onCompare,lang}){
  const [editId,setEditId]=useState(null),[editVal,setEditVal]=useState(""),[confirmId,setConfirmId]=useState(null);
  if(saved.length===0) return <div className="rounded-xl border border-dashed border-slate-300 bg-white p-10 text-center"><Icons.folder c="#cbd5e1" s={40} cn="mx-auto mb-3"/><p className="text-slate-500">{L(T.savedEmpty,lang)}</p></div>;
  return <div className="space-y-4">
    <div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-200 bg-slate-50 p-4"><p className="text-sm text-slate-600">{L(T.selectToCompare,lang)}</p><button onClick={onCompare} disabled={selected.length<2} className="inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white disabled:opacity-40 disabled:cursor-not-allowed" style={{background:NAVY}}><Icons.compare c="#fff" s={16}/>{L(T.compareSelected,lang)} ({selected.length})</button></div>
    <div className="grid gap-3 sm:grid-cols-2">{saved.map(a=>{const st=computeStats(a.scores);const v=maturityVerdict(st.avg,lang);const isSel=selected.includes(a.id);const amb=a.ambition??TARGET;return <div key={a.id} className={"rounded-xl border bg-white p-4 "+(isSel?"border-slate-700 ring-1 ring-slate-700":"border-slate-200")}>
      <div className="flex items-start gap-3"><button onClick={()=>toggleSelect(a.id)} className="mt-0.5 shrink-0">{isSel?<Icons.checkCircle c={NAVY} s={20}/>:<Icons.circle c="#cbd5e1" s={20}/>}</button><div className="flex-1 min-w-0">{editId===a.id?<div className="flex items-center gap-2"><input autoFocus value={editVal} onChange={e=>setEditVal(e.target.value)} onKeyDown={e=>{if(e.key==="Enter"){onRename(a.id,editVal);setEditId(null);}}} className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-sm outline-none"/><button onClick={()=>{onRename(a.id,editVal);setEditId(null);}} className="rounded-md p-1.5 text-white" style={{background:NAVY}}><Icons.check s={16}/></button><button onClick={()=>setEditId(null)} className="rounded-md bg-slate-100 p-1.5 text-slate-600"><Icons.x s={16}/></button></div>:<h4 className="font-semibold text-slate-800 truncate">{a.assessmentName||L(T.unnamed,lang)}</h4>}<p className="text-xs text-slate-500 mt-0.5 truncate">{a.orgName||"—"} · {a.date}</p><div className="flex flex-wrap items-center gap-2 mt-2">{v?<span className={"inline-flex items-center gap-1 rounded-full "+v.chip+" px-2 py-0.5 text-xs font-semibold"}>{v.emoji} {(Math.round(st.avg*10)/10).toFixed(1)} · {v.label}</span>:<span className="text-xs text-slate-400">{L(T.noScores,lang)}</span>}<span className="text-xs text-slate-400">{st.ratedCount}/{ALL_DOMAINS.length}</span><span className="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium" style={{background:LEVEL_META[amb].hex+"1a",color:LEVEL_META[amb].hex}}><Icons.gauge s={12}/>{L(T.ambitionShort,lang).toLowerCase()} {amb}</span></div></div></div>
      <div className="mt-3 flex flex-wrap gap-2 border-t border-slate-100 pt-3"><button onClick={()=>onOpen(a)} className="inline-flex items-center gap-1.5 rounded-lg bg-slate-100 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200"><Icons.folder s={14}/>{L(T.open,lang)}</button><button onClick={()=>{setEditId(a.id);setEditVal(a.assessmentName||"");}} className="inline-flex items-center gap-1.5 rounded-lg bg-slate-100 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200"><Icons.pencil s={14}/>{L(T.rename,lang)}</button>{confirmId===a.id?<span className="inline-flex items-center gap-1.5"><span className="text-xs text-red-600 font-medium">{L(T.removeQ,lang)}</span><button onClick={()=>{onDelete(a.id);setConfirmId(null);}} className="rounded-lg bg-red-600 px-2.5 py-1.5 text-xs font-medium text-white">{L(T.yes,lang)}</button><button onClick={()=>setConfirmId(null)} className="rounded-lg bg-slate-100 px-2.5 py-1.5 text-xs font-medium text-slate-600">{L(T.no,lang)}</button></span>:<button onClick={()=>setConfirmId(a.id)} className="inline-flex items-center gap-1.5 rounded-lg bg-red-50 px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-100"><Icons.trash s={14}/>{L(T.remove,lang)}</button>}</div>
    </div>;})}</div>
  </div>;
}

const PAL=["#2563EB","#16a34a","#db2777","#ea580c","#7c3aed","#0891b2"];
function CompareView({saved,selectedIds,lang}){
  const items=useMemo(()=>selectedIds.map(id=>saved.find(a=>a.id===id)).filter(Boolean).sort((a,b)=>(a.date||"").localeCompare(b.date||"")),[saved,selectedIds]);
  if(items.length<2) return <div className="rounded-xl border border-dashed border-slate-300 bg-white p-10 text-center"><Icons.compare c="#cbd5e1" s={40} cn="mx-auto mb-3"/><p className="text-slate-500">{L(T.compareEmpty,lang)} <strong>{L(T.tabSaved,lang)}</strong>.</p></div>;
  const baseline=items[0],latest=items[items.length-1];
  const avgFmt=st=>st.avg!=null?(Math.round(st.avg*10)/10).toFixed(1):"—";
  return <div className="space-y-6">
    <div className="rounded-xl border border-slate-200 bg-white p-5"><h3 className="text-lg font-semibold text-slate-800 mb-4">{L(T.avgOverTime,lang)}</h3><div className="grid gap-3" style={{gridTemplateColumns:"repeat("+Math.min(items.length,4)+",minmax(0,1fr))"}}>{items.map((it,idx)=>{const st=computeStats(it.scores);const v=maturityVerdict(st.avg,lang);const prev=idx>0?computeStats(items[idx-1].scores).avg:null;const diff=prev!=null&&st.avg!=null?st.avg-prev:null;return <div key={it.id} className="rounded-lg border border-slate-200 p-3"><div className="flex items-center gap-1.5 mb-1"><span className="h-3 w-3 rounded-full" style={{background:PAL[idx%PAL.length]}}/><p className="text-xs font-medium text-slate-500 truncate">{it.assessmentName||it.date}</p></div><p className="text-2xl font-bold text-slate-800">{avgFmt(st)}</p>{v&&<p className={"text-xs "+v.text}>{v.label}</p>}{diff!=null&&<p className={"text-xs mt-1 flex items-center gap-1 "+(diff>0?"text-green-600":diff<0?"text-red-600":"text-slate-400")}>{diff>0?<Icons.up s={12}/>:diff<0?<Icons.down s={12}/>:<Icons.minus s={12}/>}{diff>0?"+":""}{(Math.round(diff*10)/10).toFixed(1)} {L(T.vsPrev,lang)}</p>}</div>;})}</div></div>
    <div className="rounded-xl border border-slate-200 bg-white p-5 overflow-x-auto"><h3 className="text-lg font-semibold text-slate-800 mb-1">{L(T.scoresPerDomain,lang)}</h3><p className="text-xs text-slate-400 mb-4">{L(T.deltaExpl,lang)}</p><table className="w-full text-sm border-collapse"><thead><tr className="border-b border-slate-200"><th className="text-left py-2 pr-3 font-semibold text-slate-600">{L(T.domain,lang)}</th>{items.map((it,idx)=><th key={it.id} className="text-center px-2 py-2 font-semibold text-slate-600 whitespace-nowrap"><span className="inline-flex items-center gap-1"><span className="h-2.5 w-2.5 rounded-full" style={{background:PAL[idx%PAL.length]}}/>{it.assessmentName||it.date}</span></th>)}<th className="text-center px-2 py-2 font-semibold text-slate-600">Δ</th></tr></thead><tbody>{SECTIONS_ACTIVE.map(sec=><Fragment key={sec.id}><tr className="bg-slate-50"><td colSpan={items.length+2} className="py-1.5 px-2 text-xs font-semibold text-slate-500 uppercase tracking-wide">{sec.id} · {L(sec.title,lang)}</td></tr>{sec.domains.map(d=>{const first=baseline.scores[d.id],last=latest.scores[d.id];const delta=first!=null&&last!=null?last-first:null;return <tr key={d.id} className="border-b border-slate-100"><td className="py-2 pr-3 text-slate-700"><span className="text-slate-400">{d.code}</span> {L(d.name,lang)}</td>{items.map(it=>{const sc=it.scores[d.id];return <td key={it.id} className="text-center px-2 py-2">{sc!=null?<span className="inline-flex h-7 w-7 items-center justify-center rounded-full text-xs font-bold text-white" style={{background:LEVEL_META[sc].hex}}>{sc}</span>:<span className="text-slate-300">—</span>}</td>;})}<td className="text-center px-2 py-2">{delta==null?<span className="text-slate-300">—</span>:delta>0?<span className="inline-flex items-center gap-0.5 text-green-600 font-semibold"><Icons.up s={16}/>+{delta}</span>:delta<0?<span className="inline-flex items-center gap-0.5 text-red-600 font-semibold"><Icons.down s={16}/>{delta}</span>:<span className="inline-flex items-center gap-0.5 text-slate-400"><Icons.minus s={16}/>0</span>}</td></tr>;})}</Fragment>)}</tbody></table></div>
  </div>;
}

const ACCESS_PASSWORD="NEN2026";
const LS_ACCESS="iso_maturity_access";
const SESSION_MS=30*60*1000; // 30 minuten
function grantAccess(){ try{ localStorage.setItem(LS_ACCESS, String(Date.now()+SESSION_MS)); }catch(e){} }
function accessValid(){ return true; } /* password gate removed */
function clearAccess(){ try{ localStorage.removeItem(LS_ACCESS); }catch(e){} }
function LockScreen({onUnlock,lang,setLang}){
  const [val,setVal]=useState("");
  const [err,setErr]=useState(false);
  const submit=()=>{ if(val===ACCESS_PASSWORD){ grantAccess(); onUnlock(); } else { setErr(true); } };
  return <div className="min-h-screen flex flex-col" style={{background:NAVY}}>
    <div className="flex justify-end px-6 pt-5"><LangToggle lang={lang} setLang={setLang}/></div>
    <div className="flex-1 flex items-center justify-center px-4 py-8">
      <div className="w-full max-w-sm rounded-2xl bg-white p-7 shadow-xl">
        <div className="text-center mb-5">
          <span className="inline-flex h-14 w-14 items-center justify-center rounded-2xl mb-3" style={{background:BRAND_ACCENT}}><Icons.lock c="#fff" s={26}/></span>
          <h1 className="text-xl font-bold text-slate-800">{L(T.lockTitle,lang)}</h1>
          <p className="mt-1 text-sm text-slate-500">{L(T.lockSub,lang)}</p>
        </div>
        <input
          type="password" autoFocus value={val}
          onChange={e=>{ setVal(e.target.value); setErr(false); }}
          onKeyDown={e=>{ if(e.key==="Enter") submit(); }}
          placeholder={L(T.lockPlaceholder,lang)}
          className={"w-full rounded-lg border px-3 py-2.5 text-sm outline-none "+(err?"border-red-400 focus:border-red-500":"border-slate-300 focus:border-slate-500")}
        />
        {err&&<p className="mt-2 text-xs text-red-600 flex items-center gap-1.5"><Icons.alert c="#dc2626" s={14}/>{L(T.lockError,lang)}</p>}
        <button onClick={submit} className="mt-4 w-full inline-flex items-center justify-center gap-1.5 rounded-lg px-4 py-2.5 text-sm font-semibold text-white hover:opacity-90" style={{background:NAVY}}>
          <Icons.lock c="#fff" s={16}/>{L(T.lockButton,lang)}
        </button>
      </div>
    </div>
    <p className="text-center text-xs pb-6" style={{color:"#7a98b4"}}>{L(T.pickFoot,lang)}</p>
  </div>;
}

function ModelPicker({onPick,lang,setLang}){
  const cards=[{m:MODELS["22361"],accent:BRAND_ACCENT,icon:Icons.shield},{m:MODELS["22301"],accent:"#0d9488",icon:Icons.route}];
  return <div className="min-h-screen flex flex-col" style={{background:NAVY}}>
    <div className="flex justify-end px-6 pt-5"><LangToggle lang={lang} setLang={setLang}/></div>
    <div className="flex-1 flex items-center justify-center px-4 py-8">
      <div className="w-full max-w-4xl">
        <div className="text-center mb-8"><span className="inline-flex h-14 w-14 items-center justify-center rounded-2xl mb-4" style={{background:BRAND_ACCENT}}><Icons.gauge c="#fff" s={28}/></span><h1 className="text-2xl sm:text-3xl font-bold text-white">{L(T.pickTitle,lang)}</h1><p className="mt-2 text-sm" style={{color:"#aac8e4"}}>{L(T.pickSub,lang)}</p></div>
        <div className="grid sm:grid-cols-2 gap-5">{cards.map(({m,accent,icon:CI})=>{const cnt=loadAll(m.id).length;return <button key={m.id} onClick={()=>onPick(m.id)} className="group text-left rounded-2xl bg-white p-6 transition-all hover:-translate-y-1 hover:shadow-xl border-2 border-transparent">
          <div className="flex items-center gap-3 mb-3"><span className="flex h-12 w-12 items-center justify-center rounded-xl" style={{background:accent}}><CI c="#fff" s={24}/></span><div><p className="text-xs font-semibold uppercase tracking-wide" style={{color:accent}}>{m.code}</p><h2 className="text-lg font-bold text-slate-800 leading-tight">{L(m.name,lang)}</h2></div>{cnt>0&&<span className="ml-auto rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-500">{cnt} {L(T.savedCount,lang)}</span>}</div>
          <p className="text-sm text-slate-600 mb-3">{L(m.tagline,lang)}</p>
          <ul className="space-y-1.5 mb-4">{L(m.bullets,lang).map((b,i)=><li key={i} className="flex items-start gap-2 text-xs text-slate-500"><span className="mt-1 h-1.5 w-1.5 rounded-full shrink-0" style={{background:accent}}/>{b}</li>)}</ul>
          <span className="inline-flex items-center gap-1.5 text-sm font-semibold" style={{color:accent}}>{L(T.pickStart,lang)}<Icons.arrowR c={accent} s={16}/></span>
        </button>;})}</div>
        <p className="text-center text-xs mt-8" style={{color:"#7a98b4"}}>{L(T.pickFoot,lang)}</p>
      </div>
    </div>
  </div>;
}

function PrintCover({current,model,lang,kind}){
  const stats=computeStats(current.scores);
  const ambition=current.ambition??TARGET;
  const v=maturityVerdict(stats.avg,lang);
  const titleTxt=kind==="plan"?L(T.planTitle,lang):L(T.reportTitle,lang);
  return <div className="print-only print-cover" style={{padding:"0"}}>
    <div style={{borderTop:"6px solid "+NAVY,paddingTop:"28px"}}>
      <div style={{display:"flex",alignItems:"center",gap:"12px",marginBottom:"40px"}}>
        <span style={{display:"inline-flex",height:"44px",width:"44px",alignItems:"center",justifyContent:"center",borderRadius:"10px",background:NAVY}}><Icons.shield c="#fff" s={22}/></span>
        <div><p style={{fontSize:"12px",fontWeight:700,letterSpacing:"0.08em",textTransform:"uppercase",color:BRAND_ACCENT,margin:0}}>{model.code}</p><p style={{fontSize:"13px",color:"#475569",margin:0}}>{L(model.name,lang)}</p></div>
      </div>
      <p style={{fontSize:"13px",fontWeight:600,letterSpacing:"0.05em",textTransform:"uppercase",color:"#94a3b8",margin:"0 0 6px"}}>{titleTxt}</p>
      <h1 style={{fontSize:"34px",fontWeight:800,color:"#0f172a",lineHeight:1.15,margin:"0 0 8px"}}>{current.orgName||L(T.orgUnknown,lang)}</h1>
      <p style={{fontSize:"15px",color:"#64748b",margin:"0 0 32px"}}>{current.assessmentName||titleTxt} · {current.date}</p>
      {v&&<div style={{display:"inline-flex",alignItems:"center",gap:"16px",border:"1px solid "+v.hex+"55",background:v.hex+"0d",borderRadius:"14px",padding:"18px 22px"}}>
        <div><div style={{fontSize:"40px",fontWeight:800,color:"#0f172a",lineHeight:1}}>{stats.avg!=null?(Math.round(stats.avg*10)/10).toFixed(1):"—"}<span style={{fontSize:"18px",color:"#94a3b8"}}> / 5</span></div><p style={{fontSize:"12px",color:"#64748b",margin:"4px 0 0"}}>{L(T.avgScore,lang)}</p></div>
        <div style={{borderLeft:"1px solid #e2e8f0",paddingLeft:"16px"}}><p style={{fontSize:"15px",fontWeight:700,color:v.hex,margin:0}}>{v.emoji} {v.label}</p><p style={{fontSize:"12px",color:"#64748b",margin:"4px 0 0"}}>{L(T.ambitionLevel,lang)}: {ambition} · {L(LEVEL_LABELS[ambition],lang)}</p></div>
      </div>}
      <p style={{fontSize:"11px",color:"#94a3b8",marginTop:"40px"}}>{L(T.footer,lang)} {model.code}{L(T.footerEnd,lang)}</p>
    </div>
  </div>;
}

function App(){
  const [lang,setLangState]=useState(()=>{ try{ return localStorage.getItem(LS_LANG)||"en"; }catch(e){ return "nl"; } });
  const setLang=useCallback(l=>{ setLangState(l); try{localStorage.setItem(LS_LANG,l);}catch(e){} try{document.documentElement.setAttribute("lang",l);}catch(e){} },[]);
  const [unlocked,setUnlocked]=useState(()=>accessValid());
  const [modelId,setModelId]=useState(null);
  const [view,setView]=useState("assess");
  const [current,setCurrent]=useState(null);
  const [saved,setSaved]=useState([]);
  const [selectedIds,setSelectedIds]=useState([]);
  const [saveState,setSaveState]=useState("idle");
  const [assessStep,setAssessStep]=useState("intro");

  const pickModel=useCallback((id)=>{ setActiveModel(id); setModelId(id); setCurrent(newAssessment(id)); setSaved(loadAll(id)); setSelectedIds([]); setView("assess"); setAssessStep("intro"); },[]);
  useEffect(()=>{ pickModel(window.RMM_MODEL||"22361"); },[pickModel]);
  useEffect(()=>{ window.__rmmSetLang=setLang; return ()=>{ if(window.__rmmSetLang===setLang) delete window.__rmmSetLang; }; },[setLang]);

  // Sessietimeout: bewaak of de 30 minuten verlopen zijn; verleng bij activiteit
  useEffect(()=>{
    if(!unlocked) return;
    const check=()=>{ if(!accessValid()){ clearAccess(); setUnlocked(false); setModelId(null); setCurrent(null); } };
    const renew=()=>{ if(accessValid()) grantAccess(); }; // schuif de 30 min op bij activiteit
    const iv=setInterval(check,15000);
    const onFocus=()=>check();
    window.addEventListener("focus",onFocus);
    window.addEventListener("click",renew);
    window.addEventListener("keydown",renew);
    return ()=>{ clearInterval(iv); window.removeEventListener("focus",onFocus); window.removeEventListener("click",renew); window.removeEventListener("keydown",renew); };
  },[unlocked]);
  const backToPicker=useCallback(()=>{ setModelId(null); setCurrent(null); },[]);
  const persist=useCallback((list)=>{ saveAll(modelId,list); setSaved(list); },[modelId]);
  const handleSave=useCallback(()=>{ setSaveState("saving"); const toSave={...current,model:modelId,savedAt:new Date().toISOString()}; const list=loadAll(modelId);const idx=list.findIndex(a=>a.id===toSave.id); if(idx>=0)list[idx]=toSave; else list.unshift(toSave); persist(list); setCurrent(toSave); submitAnonymous(toSave,modelId,lang); const fromMatrix=assessStep==="matrix"; setAssessStep("matrix"); setSaveState("done"); setTimeout(()=>setSaveState("idle"),1800); if(fromMatrix) setView("report"); },[current,modelId,persist,assessStep,lang]);
  const handleOpen=useCallback(a=>{ setCurrent(JSON.parse(JSON.stringify(a))); setAssessStep("matrix"); setView("assess"); },[]);
  const handleLoadInPlace=useCallback(a=>setCurrent(JSON.parse(JSON.stringify(a))),[]);
  const handleRename=useCallback((id,name)=>{ const list=loadAll(modelId);const i=list.findIndex(a=>a.id===id);if(i>=0){list[i]={...list[i],assessmentName:name};persist(list);} setCurrent(c=>c&&c.id===id?{...c,assessmentName:name}:c); },[modelId,persist]);
  const handleDelete=useCallback(id=>{ persist(loadAll(modelId).filter(a=>a.id!==id)); setSelectedIds(s=>s.filter(x=>x!==id)); },[modelId,persist]);
  const toggleSelect=useCallback(id=>setSelectedIds(s=>s.includes(id)?s.filter(x=>x!==id):[...s,id]),[]);
  const handleNew=useCallback(()=>{ setCurrent(newAssessment(modelId)); setAssessStep("intro"); setView("assess"); },[modelId]);
  const doPrint=useCallback((kind)=>{
    setView(kind==="plan"?"plan":"report");
    setTimeout(()=>{
      document.body.classList.add(kind==="plan"?"printing-plan":"printing-report");
      const cleanup=()=>{ document.body.classList.remove("printing-plan","printing-report"); window.removeEventListener("afterprint",cleanup); };
      window.addEventListener("afterprint",cleanup);
      window.print();
      setTimeout(cleanup,1000);
    },300);
  },[]);

  if(!modelId||!current) return null;
  const otherId=modelId==="22361"?"22301":"22361";
  const otherHref=otherId==="22301"?"assessment-bcm.html":"assessment-cm.html";
  const model=MODELS[modelId];
  const tabs=[{id:"assess",label:T.tabAssess,icon:Icons.clipboard},{id:"report",label:T.tabReport,icon:Icons.file},{id:"plan",label:T.tabPlan,icon:Icons.route},{id:"saved",label:T.tabSaved,icon:Icons.folder,badge:saved.length||null},{id:"compare",label:T.tabCompare,icon:Icons.compare}];
  return <div className="min-h-screen text-slate-900">
    <header className="no-print" style={{background:"#fff",borderBottom:"1px solid #DCE5F1",boxShadow:"0 6px 18px rgba(14,31,58,.04)"}}><div className="mx-auto max-w-6xl px-4 sm:px-6 py-4">
      <div className="flex flex-wrap items-center justify-between gap-3"><div className="flex items-center gap-3 min-w-0"><span className="flex h-10 w-10 items-center justify-center rounded-lg shrink-0" style={{background:BRAND_ACCENT}}><Icons.shield c="#fff" s={20}/></span><div className="min-w-0"><h1 className="text-lg font-bold leading-tight truncate" style={{color:NAVY,fontFamily:"'Manrope',sans-serif"}}>{L(model.appTitle,lang)}</h1><p className="text-xs truncate" style={{color:"#7C8AA5"}}>{L(model.appSubtitle,lang)}</p></div></div>
      <div className="flex items-center gap-2 flex-wrap"><a href={otherHref} className="inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium hover:bg-slate-50" style={{borderColor:"#DCE5F1",color:NAVY}}><Icons.arrowL c={NAVY} s={16}/>{L(MODELS[otherId].name,lang)}</a><button onClick={handleNew} className="inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium hover:bg-slate-50" style={{borderColor:"#DCE5F1",color:NAVY}}><Icons.plus c={NAVY} s={16}/>{L(T.newAssessment,lang)}</button><button onClick={handleSave} disabled={saveState==="saving"} className="inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-medium text-white hover:opacity-90" style={{background:saveState==="done"?"#059669":BRAND_ACCENT,boxShadow:"0 6px 14px rgba(37,99,235,.25)"}}>{saveState==="saving"?<Icons.clock c="#fff" s={16}/>:saveState==="done"?<Icons.check c="#fff" s={16}/>:<Icons.save c="#fff" s={16}/>}{saveState==="done"?L(T.saved,lang):L(T.save,lang)}</button><button onClick={()=>doPrint("report")} className="inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium hover:bg-slate-50" style={{borderColor:"#DCE5F1",color:NAVY}}><Icons.printer c={NAVY} s={16}/>{L(T.printReport,lang)}</button><button onClick={()=>doPrint("plan")} className="inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium hover:bg-slate-50" style={{borderColor:"#DCE5F1",color:NAVY}}><Icons.route c={NAVY} s={16}/>{L(T.printPlan,lang)}</button></div></div>
      <nav className="mt-4 flex flex-wrap gap-1 -mb-4" style={{borderBottom:"1px solid #E5EDF8"}}>{tabs.map(t=>{const active=view===t.id;const TI=t.icon;return <button key={t.id} onClick={()=>setView(t.id)} className="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold border-b-2" style={{borderColor:active?BRAND_ACCENT:"transparent",color:active?NAVY:"#7C8AA5"}}><TI c={active?BRAND_ACCENT:"#7C8AA5"} s={16}/>{L(t.label,lang)}{t.badge?<span className="rounded-full px-1.5 py-0.5 text-xs" style={{background:"#EAF1FB",color:"#1D4ED8"}}>{t.badge}</span>:null}</button>;})}</nav>
    </div></header>
    <main className="mx-auto max-w-6xl px-4 sm:px-6 py-6 print-container">
      {view==="assess"&&<AssessmentView current={current} setCurrent={setCurrent} step={assessStep} setStep={setAssessStep} onSave={handleSave} saveState={saveState} model={model} lang={lang}/>}
      {view==="report"&&<div className="print-block-report"><PrintCover current={current} model={model} lang={lang} kind="report"/><Report current={current} lang={lang}/></div>}
      {view==="plan"&&<div className="print-block-plan"><PrintCover current={current} model={model} lang={lang} kind="plan"/><ImprovementPlanView current={current} setCurrent={setCurrent} saved={saved} onLoad={handleLoadInPlace} lang={lang}/></div>}
      {view==="saved"&&<SavedView saved={saved} onOpen={handleOpen} onRename={handleRename} onDelete={handleDelete} selected={selectedIds} toggleSelect={toggleSelect} onCompare={()=>setView("compare")} lang={lang}/>}
      {view==="compare"&&<CompareView saved={saved} selectedIds={selectedIds} lang={lang}/>}
    </main>
    <footer className="mx-auto max-w-6xl px-4 sm:px-6 py-6 text-center text-xs text-slate-400 no-print">{L(T.footer,lang)} {model.code}{L(T.footerEnd,lang)}</footer>
  </div>;
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);