#!/usr/bin/env python3
"""Generate standalone HTML eLearning + SCORM 1.2 package for Kwik Awareness Training."""

import os
import json
import zipfile
import shutil

BASE_DIR = "/root/projects/jg/2026-pbm-KWIK"
CONTENT_FILE = os.path.join(BASE_DIR, "working/hseq-content-analysis_v1.0.md")
LOGO_B64_FILE = "/root/projects/jg/assets/branding/logo-white-base64.txt"
LOGO_PNG = "/root/projects/jg/assets/branding/jvg-consultancy-logo.png"
HTML_OUT = os.path.join(BASE_DIR, "deliverables/html/kwik-elearning_v1.0.html")
SCORM_DIR = os.path.join(BASE_DIR, "deliverables/scorm")
SCORM_ZIP = os.path.join(SCORM_DIR, "kwik-scorm_v1.0.zip")
SCORM_WDIR = os.path.join(SCORM_DIR, "_build")

# Read logo base64
with open(LOGO_B64_FILE, "r", encoding="utf-8", errors="replace") as f:
    LOGO_B64 = f.read().strip()
# Ensure valid base64 prefix
if not LOGO_B64.startswith("data:image"):
    LOGO_B64 = "data:image/png;base64," + LOGO_B64

MODULES = [
    {
        "id": 1, "title": "Kwik — Eigenschappen en Vormen",
        "duration": "3 min",
        "content": """
<h3>Elementair kwik (Hg\u2070)</h3>
<ul>
<li>Enige metaal dat bij standaardomstandigheden <strong>vloeibaar</strong> is (smeltpunt \u221238,83\u00b0C, kookpunt 356,73\u00b0C)</li>
<li>Zilverwit, metallische glans — dichtheid <strong>13,534 g/cm\u00b3</strong></li>
<li>Dampdruk bij 20\u00b0C: <strong>0,00185 mmHg</strong> (0,246 Pa) — reeds aanzienlijk bij kamertemperatuur</li>
<li>Dampdrukcurve: 0,012 mmHg (40\u00b0C) \u2192 0,27 mmHg (100\u00b0C) \u2192 12 mmHg (250\u00b0C)</li>
<li>Relatieve dampdichtheid: <strong>~6,9</strong> (ten opzichte van lucht) — accumuleert in lage ruimtes</li>
</ul>
<h3>Anorganische kwikverbindingen</h3>
<table class="data-table">
<tr><th>Vorm</th><th>Formule</th><th>Kenmerken</th></tr>
<tr><td>Kwik(I)-chloride (calomel)</td><td>Hg\u2082Cl\u2082</td><td>Wit poeder, weinig oplosbaar</td></tr>
<tr><td>Kwik(II)-chloride (sublimaat)</td><td>HgCl\u2082</td><td>Hoog oplosbaar, zeer toxisch</td></tr>
<tr><td>Kwik(II)-oxide</td><td>HgO</td><td>Rood/geel poeder, batterijen</td></tr>
<tr><td>Kwiksulfide</td><td>HgS (cinnaber)</td><td>Rood, zeer laag oplosbaar — stabiele eindvorm</td></tr>
</table>
<h3>Organische kwikverbindingen</h3>
<ul>
<li><strong>Methylkwik</strong> (CH\u2083Hg\u207a): Lipofiel, passeert BBB, bioaccumuleert, halfwaardetijd ~70 dagen</li>
<li><strong>Dimethylkwik</strong>: Zeer vluchtig, extreem toxisch, breekvlamig</li>
<li><strong>Ethylkwik</strong>: Historisch fungicide (verboden EU sinds 1991)</li>
</ul>
<div class="alert alert-danger">
<strong>\u26a0 Kwikdamp</strong>: Kleurloos én reukloos — geen sensorische waarschuwing mogelijk. Accumuleert in lage punten.
</div>
"""
    },
    {
        "id": 2, "title": "Kwik in de Industrie — Bronnen en Toepassingen",
        "duration": "4 min",
        "content": """
<h3>Typische kwikconcentraties per industrie</h3>
<table class="data-table">
<tr><th>Industrie</th><th>Bron</th><th>Concentratie</th></tr>
<tr><td>Olie & gas — Aardgas</td><td>Geassocieerd met reservoir</td><td>0,1 – 5.000 \u00b5g/Nm\u00b3</td></tr>
<tr><td>Olie & gas — Condensaat</td><td>Opgelost in vloeistoffase</td><td>1 – 200 \u00b5g/kg</td></tr>
<tr><td>Petrochemie — PVC</td><td>HgCl\u2082 katalysator</td><td>Varieert mg/kg</td></tr>
<tr><td>Chloor-alkali (historisch)</td><td>Kwik-cel proces</td><td>1 – 50 g Hg/ton Cl\u2082</td></tr>
<tr><td>Fluorescentielampen</td><td>Amalgam</td><td>3 – 5 mg/lamp</td></tr>
</table>
<h3>Olie & Gas — Kwikspecifiek</h3>
<ul>
<li>Kwik komt van nature voor in geologische formaties; concentraties sterk variabel per veld</li>
<li><strong>Corrosierisico</strong>: Kwik vormt met aluminium een amalgam \u2192 intergranulaire corrosie \u2192 catastrofaal falen van cryogene heat exchangers</li>
<li>Seizoenspatronen: Hogere concentraties in warme periodes</li>
</ul>
<h3>Uitdovende toepassingen</h3>
<ul>
<li>Chloor-alkali: membranelektrolyse vervangt kwik-cel proces sinds ~2010</li>
<li>Meetinstrumenten: Digitale sensoren vervangen kwikthermometers</li>
<li>Amalgam in tandheelkunde: Uitfaserend per EU-verordening</li>
</ul>
"""
    },
    {
        "id": 3, "title": "Blootstellingsroutes en Toxicokinetiek",
        "duration": "4 min",
        "content": """
<h3>Absorptiepercentages per route</h3>
<table class="data-table">
<tr><th>Route</th><th>Elementair Hg\u2070</th><th>Anorganisch Hg\u00b2\u207a</th><th>Methylkwik CH\u2083Hg\u207a</th></tr>
<tr><td><strong>Inhalatie</strong></td><td>~80%</td><td>~10-30%</td><td>~60-80%</td></tr>
<tr><td>Dermal</td><td>&lt;0,01%</td><td>~2-5%</td><td>~5-10%</td></tr>
<tr><td>Oraal (GI)</td><td>&lt;0,01%</td><td>~7-15%</td><td>&gt;95%</td></tr>
</table>
<h3>Distributie in het lichaam</h3>
<ul>
<li><strong>Hg\u2070</strong>: Longen \u2192 bloedbaan \u2192 BBB \u2192 hersenen, nieren, lever. Oxideert tot Hg\u00b2\u207a.</li>
<li><strong>Hg\u00b2\u207a</strong>: Bindt aan glutathion, metallothioneine \u2192 accumuleert in <strong>nieren</strong></li>
<li><strong>CH\u2083Hg\u207a</strong>: BBB \u2192 hersenen \u2192 <strong>placenta-passage</strong> \u2192 fetus (10-50\u00d7 hogere concentratie dan moederbloed)</li>
</ul>
<h3>Biologische halfwaardetijden</h3>
<table class="data-table">
<tr><th>Vorm</th><th>Orgaan</th><th>Halfwaardetijd</th></tr>
<tr><td>Hg\u2070</td><td>Heel lichaam</td><td>~58 dagen</td></tr>
<tr><td>Hg\u00b2\u207a</td><td>Nieren</td><td>~30-60 dagen</td></tr>
<tr><td>CH\u2083Hg\u207a</td><td>Hersenen</td><td><strong>~230 dagen</strong></td></tr>
</table>
"""
    },
    {
        "id": 4, "title": "Gezondheidseffecten — Acuut en Chronisch",
        "duration": "5 min",
        "content": """
<h3>Acute blootstelling</h3>
<table class="data-table">
<tr><th>Symptoom</th><th>Vorm</th><th>Niveau</th></tr>
<tr><td>Chemische pneumonitis</td><td>Hg\u2070 damp</td><td>&gt;1-5 mg/m\u00b3</td></tr>
<tr><td>Stomatitis, kwiklijn</td><td>Alle vormen</td><td>Variabel</td></tr>
<tr><td>Tremor, ataxie</td><td>Hg\u2070 damp</td><td>&gt;0,5 mg/m\u00b3</td></tr>
<tr><td>Shock, nierfalen</td><td>HgCl\u2082 (oraal)</td><td>&gt;1 g (potentieel fataal)</td></tr>
</table>
<h3>Chronisch — Elementair kwik</h3>
<ul>
<li><strong>Tremor</strong> ("hatter's shakes"): &gt;50 \u00b5g/m\u00b3 langdurig</li>
<li><strong>Erethisme</strong>: prikkelbaarheid, geheugenverlies, depressie</li>
<li><strong>Nefrotoxiciteit</strong>: tubulaire schade, proteinurie</li>
<li><strong>Neurocognitieve achteruitgang</strong>: concentratieverlies, reactietijd</li>
</ul>
<h3>Chronisch — Methylkwik (Minamata disease)</h3>
<ul>
<li>Cerebellaire ataxie, gezichtsveldbeperking, gehoorverlies</li>
<li><strong>Fetaal</strong>: Ernstige cerebrale schade — microcefalie, verstandelijke beperking</li>
</ul>
<div class="alert alert-info">
<strong>Historie</strong>: "Mad as a Hatter" — 18e eeuw hoedenmakers gebruikten Hg(NO\u2083)\u2082. Term uit Lewis Carroll, Alice in Wonderland (1865).
</div>
"""
    },
    {
        "id": 5, "title": "Wettelijk Kader — OEL's en Classificatie",
        "duration": "5 min",
        "content": """
<h3>Nederlandse Arbowaarden</h3>
<table class="data-table">
<tr><th>Parameter</th><th>Waarde</th></tr>
<tr><td><strong>MAC-waarde (TWA-8h)</strong></td><td>0,025 mg/m\u00b3</td></tr>
<tr><td><strong>STEL (15 min)</strong></td><td>0,075 mg/m\u00b3 (3\u00d7 MAC)</td></tr>
<tr><td>Biologische grenswaarde — Urine</td><td>&lt;20 \u00b5g/g creatinine</td></tr>
<tr><td>Biologische grenswaarde — Bloed</td><td>&lt;15 \u00b5g/L totaal Hg</td></tr>
</table>
<h3>EU- en internationale classificatie</h3>
<ul>
<li><strong>EU CLP/REACH</strong>: SVHC (Substance of Very High Concern)</li>
<li><strong>EU KMR</strong>: Repr. 1B (H360Df) — schadelijk voor ongeboren kind</li>
<li><strong>EU KMR</strong>: STOT RE 1 (H372) — specifieke doelorgaan toxiciteit</li>
<li><strong>IARC</strong>: Groep 3 — niet classificeerbaar</li>
<li><strong>EU CLP</strong>: Aquatic Acute 1, Aquatic Chronic 1</li>
</ul>
<h3>Belangrijke verordeningen</h3>
<ul>
<li><strong>EU Verordening 2017/852</strong>: Verbod op productie, import, export van kwik</li>
<li><strong>Minamata Convention (UNEP 2013)</strong>: Internationaal verdrag, 140+ landen</li>
<li><strong>REACH</strong>: SVHC, autorisatieplicht</li>
</ul>
<div class="alert alert-warning">
<strong>NCvB</strong>: Kwikintoxicatie (GEO 1801), Kwiknefrose (GEO 1802) — melding verplicht via bedrijfsarts.
</div>
"""
    },
    {
        "id": 6, "title": "Meten en Monitoring",
        "duration": "4 min",
        "content": """
<h3>Luchtmetingen</h3>
<table class="data-table">
<tr><th>Methode</th><th>Instrument</th><th>Detectielimiet</th></tr>
<tr><td>Directe aflezing</td><td>Jerome 411-X (gold film)</td><td>0,003 mg/m\u00b3</td></tr>
<tr><td>Directe aflezing</td><td>Lumex RA-915+ (Zeeman-AAS)</td><td>0,001 mg/m\u00b3</td></tr>
<tr><td>Bemonstering</td><td>Hopcalite tube + lab</td><td>0,0005 mg/m\u00b3</td></tr>
<tr><td>Persoonlijk</td><td>Pump + tube (NIOSH 6009)</td><td>0,001 mg/m\u00b3</td></tr>
</table>
<h3>Biologische monitoring</h3>
<table class="data-table">
<tr><th>Parameter</th><th>Matrix</th><th>Grenswaarde</th><th>Frequentie</th></tr>
<tr><td>Totaal kwik</td><td>Urine</td><td>&lt;20 \u00b5g/g creat.</td><td>Halfjaarlijks</td></tr>
<tr><td>Totaal kwik</td><td>Bloed</td><td>&lt;15 \u00b5g/L</td><td>Bij incidenten</td></tr>
<tr><td>Methylkwik</td><td>Bloed</td><td>&lt;15 \u00b5g/L</td><td>Indicatiegericht</td></tr>
</table>
<h3>Meetstrategie conform NEN-EN 689</h3>
<ol>
<li>Inventarisatie: bepaal blootstellingspatronen</li>
<li>Minimaal 3 representatieve metingen per werkplek</li>
<li>Indeling: Groep 1 (&lt;10% MAC), Groep 2 (10-50%), Groep 3 (&gt;50%)</li>
<li>Documentatie met kalibratiecertificaat</li>
</ol>
"""
    },
    {
        "id": 7, "title": "Preventieve Maatregelen — STOP-strategie",
        "duration": "5 min",
        "content": """
<h3>S — Bronmaatregelen</h3>
<ul>
<li>Kwikvrije alternatieven (digitale sensoren, LED-verlichting) — \u2605\u2605\u2605\u2605\u2605</li>
<li>Kwikremoval units (MRU): actieve kool/chemisorptie — \u2605\u2605\u2605\u2605</li>
<li>Amalgamvrije tandheelkunde, membraanelektrolyse — \u2605\u2605\u2605\u2605\u2605</li>
</ul>
<h3>T — Technische maatregelen</h3>
<ul>
<li>Lokale afzuiging: minimaal 0,5 m/s vangsnelheid</li>
<li>Ventilatie: minimaal 6 ACH in kwikwerkruimtes</li>
<li>Gesloten systemen, gastight verbindingen</li>
<li>Continue kwikdampmonitoring met alarm (&gt;50% MAC)</li>
</ul>
<h3>O — Organisatorische maatregelen</h3>
<ul>
<li>Werkvergunning verplicht</li>
<li>Blootstellingsregister per medewerker</li>
<li>Jobrotatie, PAGO met biologische monitoring</li>
<li><strong>Zwangere vrouwen &lt;18 jaar</strong>: geen werk met kwik (Repr. 1B)</li>
</ul>
<h3>P — Persoonlijke beschermingsmiddelen</h3>
<ul>
<li><strong>Adembescherming</strong>: Halfgezichtsmasker P3 + gasfilter type Hg (EN 371)</li>
<li>Handschoenen: Nitril, minimaal 0,5 mm</li>
<li>Wegwerp overal (type 5/6), veiligheidsbril</li>
<li><strong>NIET</strong> eten, drinken of roken op werkplek</li>
</ul>
"""
    },
    {
        "id": 8, "title": "Kwik in Olie & Gas — Specifieke Risico's",
        "duration": "4 min",
        "content": """
<h3>Aluminiumcorrosie — Catastrofaal falen</h3>
<div class="alert alert-danger">
<strong>Mechanisme</strong>: Hg\u2070 \u2192 Contact Al \u2192 Hg-Al amalgam \u2192 Intergranulaire corrosie \u2192 Brittle fracture \u2192 Explosie/lekkage risico
</div>
<ul>
<li>Zelfs lage concentraties (&lt;10 \u00b5g/Nm\u00b3) kunnen op termijn leiden tot corrosie</li>
<li>Vochtigheid versterkt corrosieproces</li>
<li>Aluminiumlegeringen (met koper) extra gevoelig</li>
</ul>
<h3>Kwikremoval Units (MRU)</h3>
<table class="data-table">
<tr><th>Type MRU</th><th>Principe</th><td>Efficiëntie</td></tr>
<tr><td>Actieve kool (geïmpregneerd)</td><td>Adsorptie + chemisorptie</td><td>&gt;99%</td></tr>
<tr><td>Sulfur-geïmpregneerde kool</td><td>HgS binding</td><td>&gt;99,5%</td></tr>
<tr><td>Metaloxide sorbent</td><td>ZnO/CuO chemisorptie</td><td>&gt;99%</td></tr>
</table>
<h3>Case Study: Skikda LNG (19 jan 2004, Algerije)</h3>
<ul>
<li>Explosie in LNG trein — 27 doden, 74 gewonden</li>
<li>Kwik-gerelateerde aluminiumcorrosie als bijdragende factor</li>
<li>Les: Materiaalselectie (geen Al bij Hg) en monitoring essentieel</li>
</ul>
"""
    },
    {
        "id": 9, "title": "Noodprocedure bij Kwikspill",
        "duration": "4 min",
        "content": """
<h3>Kleine spill (&lt;25 ml)</h3>
<ol>
<li>Directe zone evacueren (3 m)</li>
<li>Ventilatie maximaliseren</li>
<li>SBD / leidinggevende informeren</li>
<li>Spill-kit halen, PBM dragen</li>
<li><strong>Zwavelpoeder</strong> royaal aanbrengen</li>
<li>15 minuten laten reageren (HgS-vorming)</li>
<li>Opruimen met amalgamerende spons</li>
<li>Oppervlak decontamineren</li>
<li><strong>Nameten</strong> — &lt;0,025 mg/m\u00b3</li>
<li>Zone vrijgeven na goedkeuring</li>
</ol>
<h3>Grote spill (&gt;25 ml)</h3>
<ol>
<li><strong>Algemeen alarm / ontruiming</strong></li>
<li>Ventilatie afsluiten (recirculatie UIT)</li>
<li>Zone afzetten — minimum 10 m</li>
<li>Professionele saneringsfirma inschakelen</li>
<li>Continue monitoring</li>
</ol>
<h3>Persoonlijke besmetting</h3>
<table class="data-table">
<tr><th>Situatie</th><th>Actie</th></tr>
<tr><td>Kwik op huid</td><td>Wassen met water en zeep; geen oplosmiddelen</td></tr>
<tr><td>Kwik op kleding</td><td>Uittrekken; als gevaarlijk afval behandelen</td></tr>
<tr><td>Kwik in oog</td><td>Minimaal 15 min spoelen; medische controle</td></tr>
<tr><td>Inademing</td><td>Direct frisse lucht; medische controle</td></tr>
</table>
<div class="alert alert-warning">
<strong>Brand met kwik</strong>: Blussen met droog poeder of CO\u2082. <strong>NIET</strong> blussen met water!
</div>
"""
    },
    {
        "id": 10, "title": "Praktijk en Casuïstiek",
        "duration": "3 min",
        "content": """
<h3>Casus 1: Gebroken thermometer in kelder</h3>
<div class="case-study">
<p><strong>Situatie</strong>: Medewerker vindt gebroken kwikthermometer in kelderopslag. Zilveren druppels op vloer. Beperkte ventilatie.</p>
<p><strong>Correct</strong>: Niet zelf opruimen \u2192 kelder afsluiten \u2192 ventilatie maximaliseren \u2192 getrainde medewerker met spill-kit + PBM \u2192 zwavelpoeder \u2192 15 min wachten \u2192 opruimen \u2192 meting &lt;0,025 mg/m\u00b3.</p>
<p><strong>Leerpunt</strong>: Kwikdamp is zwaarder dan lucht en accumuleert in kelders.</p>
</div>
<h3>Casus 2: Kwikverdacht materiaal bij pijpleiding inspectie</h3>
<div class="case-study">
<p><strong>Situatie</strong>: Donkere afzetting in filter/separator. Geen MRU upstream.</p>
<p><strong>Correct</strong>: Niet aanraken \u2192 gastight monstername \u2192 analyse (CV-AAS/ICP-MS) \u2192 bij bevestiging: risicobeoordeling Al-corrosie \u2192 MRU installeren.</p>
<p><strong>Leerpunt</strong>: Afwezigheid MRU \u2260 afwezigheid kwik.</p>
</div>
<h3>Casus 3: Fluorescentielampen vervangen</h3>
<div class="case-study">
<p><strong>Correct</strong>: Ruimte evacueren (15 min) \u2192 papier/karton opruimen \u2192 <strong>GEEN stofzuiger!</strong> \u2192 afvoer 20 01 23*.</p>
<p><strong>Leerpunt</strong>: Elke lamp bevat 3-5 mg kwik. Cumulatie bij grootschalig vervangen is een risico.</p>
</div>
<h3>Key Messages</h3>
<ol>
<li>Kwikdamp is onzichtbaar én reukloos</li>
<li>MAC-waarde: 0,025 mg/m\u00b3 — bij &gt;50% maatregelen verplicht</li>
<li>STOP-strategie: Bron \u2192 Technisch \u2192 Organisatorisch \u2192 Persoonlijk</li>
<li>MRU en materiaalselectie zijn noodzaak, geen luxe</li>
<li><strong>NOOIT</strong> standaard stofzuiger bij kwik</li>
</ol>
"""
    },
]

QUIZ = [
    {
        "q": "Wat is de dampdruk van elementair kwik bij 20\u00b0C?",
        "options": ["0,00012 mmHg", "0,00185 mmHg", "0,0185 mmHg", "0,185 mmHg"],
        "correct": 1,
        "explanation": "Bij 20\u00b0C bedraagt de dampdruk 0,00185 mmHg (0,246 Pa). Kwik produceert reeds bij kamertemperatuur aanzienlijke damp."
    },
    {
        "q": "Wat is de Nederlandse MAC-waarde (TWA-8h) voor kwikdamp?",
        "options": ["0,010 mg/m\u00b3", "0,025 mg/m\u00b3", "0,050 mg/m\u00b3", "0,100 mg/m\u00b3"],
        "correct": 1,
        "explanation": "Conform Arbobesluit (bijlage XI-A) bedraagt de MAC-waarde 0,025 mg/m\u00b3 als TWA-8h."
    },
    {
        "q": "Welke blootstellingsroute heeft de hoogste absorptie voor elementair kwik?",
        "options": ["Orale opname (~50%)", "Dermal contact (~30%)", "Inhalatie (~80%)", "Oogcontact (~10%)"],
        "correct": 2,
        "explanation": "Hg\u2070 damp wordt via longen ~80% geabsorbeerd, passeert BBB en oxideert tot Hg\u00b2\u207a."
    },
    {
        "q": "Wat is de IARC-classificatie voor kwik?",
        "options": ["Groep 1", "Groep 2A", "Groep 2B", "Groep 3"],
        "correct": 3,
        "explanation": "IARC classificeert elementair/anorganisch kwik als Groep 3 (niet classificeerbaar)."
    },
    {
        "q": "Wat is de biologische halfwaardetijd van methylkwik in de hersenen?",
        "options": ["~30 dagen", "~70 dagen", "~150 dagen", "~230 dagen"],
        "correct": 3,
        "explanation": "~230 dagen hersenen — verklaart ernstige en langdurige neurologische effecten (Minamata)."
    },
    {
        "q": "Een medewerker breekt een fluorescentielamp in een slecht geventileerde kelder. Wat is de eerste actie?",
        "options": ["Direct opruimen met bezem", "De kelder evacueren en ventileren", "Standaard stofzuiger gebruiken", "Fragmenten in de prullenbak"],
        "correct": 1,
        "explanation": "Eerst evacueren en ventileren. Kwikdamp is zwaarder dan lucht en accumuleert in lage ruimtes."
    },
    {
        "q": "Bij een kwikmeting wordt 0,018 mg/m\u00b3 gemeten (TWA-8h). Wat is correct?",
        "options": ["Geen actie nodig", "Actiewaarde overschreden — strategie verplicht", "Direct ontruimen", "Alleen PBM dragen"],
        "correct": 1,
        "explanation": "Actiewaarde = 50% MAC = 0,0125 mg/m\u00b3. Bij 0,018 is actiewaarde overschreden."
    },
    {
        "q": "Welk PBM-combinatie is correct voor kwikdamp?",
        "options": ["Stofmasker FFP2 + katoenen handschoenen", "P3 + gasfilter Hg + nitril handschoenen", "P3-filter + latex handschoenen", "Mondkapje + veiligheidsbril"],
        "correct": 1,
        "explanation": "Halfgezichtsmasker P3 + gasfilter Hg (EN 371) + nitril handschoenen verplicht."
    },
    {
        "q": "Bij een kleine kwikspill is de laatste stap vóór vrijgave:",
        "options": ["Afvoerbon invullen", "Kwikdampmeting — &lt;0,025 mg/m\u00b3", "Vloer dweilen met water", "Incident rapporteren aan de pers"],
        "correct": 1,
        "explanation": "Verificatiemeting verplicht. Pas bij &lt;0,025 mg/m\u00b3 mag zone vrijgegeven worden."
    },
    {
        "q": "Aluminium heat exchangers + kwik = welk risico?",
        "options": ["Geen risico", "Amalgam \u2192 corrosie \u2192 catastrofaal falen", "Oppervlakteroest", "Verhoogde thermische geleidbaarheid"],
        "correct": 1,
        "explanation": "Hg-Al amalgam \u2192 intergranulaire corrosie \u2192 brittle fracture. Case: Skikda LNG 2004."
    },
    {
        "q": "Biologische monitoring toont 35 \u00b5g/g creatinine. BGW is &lt;20. Wat is correct?",
        "options": ["Normaal, geen actie", "BGW overschreden — werkplek stilleggen, medische evaluatie", "Acceptabel, symptomen toeval", "Bloometing vereist"],
        "correct": 1,
        "explanation": "BGW &lt;20 \u00b5g/g. Bij 35 is overschreden. Symptomen (tremor, erethisme) consistent met chronische blootstelling."
    },
    {
        "q": "MRU efficiëntie 99,5%, piekbelasting 3.000 \u00b5g/Nm\u00b3. Resterende concentratie?",
        "options": ["0,15 \u00b5g/Nm\u00b3", "15 \u00b5g/Nm\u00b3", "150 \u00b5g/Nm\u00b3", "1.500 \u00b5g/Nm\u00b3"],
        "correct": 1,
        "explanation": "3.000 \u00d7 0,5% = 15 \u00b5g/Nm\u00b3. Significant — extra monitoring aanbevolen."
    },
    {
        "q": "Zwangere medewerkster, Repr. 1B (H360Df). Wat is correct?",
        "options": ["Geen beperkingen", "PBM voldoende", "Herplaatsing verplicht", "Schriftelijke toestemming bedrijfsarts"],
        "correct": 2,
        "explanation": "Repr. 1B: verboden voor zwangere vrouwen. Herplaatsing naar kwikvrije werkplek verplicht."
    },
    {
        "q": "Zwavelpoeder bij kwikspill: wat is het reactieproduct?",
        "options": ["HgSO\u2084", "HgS (kinzinnabar)", "Hg\u2082S", "HgO"],
        "correct": 1,
        "explanation": "HgS is zeer laag oplosbaar, stabiel, niet-vluchtig — veilig voor afvoer."
    },
    {
        "q": "Hoeveel procent van de MAC-waarde is de actiewaarde?",
        "options": ["25%", "50%", "75%", "100%"],
        "correct": 1,
        "explanation": "Actiewaarde = 50% MAC = 0,0125 mg/m\u00b3. Arbobesluit verplicht arbeidshygiënische strategie."
    },
]

CHECKLIST_ITEMS = [
    "Zone geëvacueerd en afgezet",
    "Ventilatie geregeld (maximaliseren of afsluiten)",
    "SBD / leidinggevende geïnformeerd",
    "PBM gedragen door hulpverleners",
    "Spill-kit gebruikt (zwavelpoeder + amalgamerende spons)",
    "15 minuten reactietijd gerespecteerd",
    "Kwik opgevangen in afsluitbare container",
    "Oppervlak gedesinfecteerd",
    "Kwikdampconcentratie gemeten — <0,025 mg/m³ bevestigd",
    "Alles gedocumenteerd (rapport, afvoerbon, metingsrapport)",
]


def generate_html():
    """Generate the standalone HTML eLearning file."""
    # Build modules HTML
    modules_html_parts = []
    for m in MODULES:
        modules_html_parts.append(
            '<div class="module" id="module-' + str(m["id"]) + '" style="display:none;">'
            + '<div class="module-header"><span class="module-num">' + str(m["id"]) + '</span> '
            + '<span class="module-title">' + m["title"] + '</span>'
            + '<span class="module-duration">' + m["duration"] + '</span></div>'
            + '<div class="module-content">' + m["content"] + '</div>'
            + '<div class="nav-buttons">'
            + ('<button class="btn btn-secondary" onclick="prevModule()">\u2190 Vorige</button>' if m["id"] > 1 else '')
            + ('<button class="btn btn-primary" onclick="nextModule()">Volgende \u2192</button>' if m["id"] < 10 else '<button class="btn btn-primary" onclick="showQuiz()">Naar Quiz \u2192</button>')
            + '</div></div>'
        )

    # Build quiz HTML
    quiz_parts = []
    for i, q in enumerate(QUIZ):
        opts = ""
        for j, opt in enumerate(q["options"]):
            opts += '<label class="quiz-option"><input type="radio" name="q' + str(i) + '" value="' + str(j) + '"> ' + opt + '</label>\n'
        quiz_parts.append(
            '<div class="quiz-question" id="qq' + str(i) + '" style="display:none;">'
            + '<p class="quiz-num">Vraag ' + str(i+1) + '/' + str(len(QUIZ)) + '</p>'
            + '<p class="quiz-text">' + q["q"] + '</p>'
            + '<div class="quiz-options">' + opts + '</div>'
            + '<div class="quiz-feedback" id="qf' + str(i) + '"></div>'
            + '<button class="btn btn-secondary" onclick="checkAnswer(' + str(i) + ')">Controleer</button> '
            + '<button class="btn btn-primary" onclick="nextQuestion(' + str(i) + ')">Volgende \u2192</button>'
            + '</div>'
        )

    # Build checklist HTML
    checklist_parts = []
    for i, item in enumerate(CHECKLIST_ITEMS):
        checklist_parts.append(
            '<label class="checklist-item"><input type="checkbox" id="cl' + str(i) + '" onchange="updateChecklist()"> ' + item + '</label>'
        )

    # Quiz data as JSON for JS
    quiz_json = json.dumps(QUIZ)

    html = """<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Awareness Training Kwik — JvG Consultancy</title>
<style>
:root {
  --primary: #1a3a5c;
  --primary-light: #2a5a8c;
  --accent: #e8a838;
  --accent-dark: #c48820;
  --success: #28a745;
  --danger: #dc3545;
  --warning: #ffc107;
  --info: #17a2b8;
  --bg: #f5f7fa;
  --card-bg: #ffffff;
  --text: #2c3e50;
  --text-light: #6c757d;
  --border: #dee2e6;
  --radius: 8px;
  --shadow: 0 2px 12px rgba(0,0,0,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
}
.header {
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
  color: white;
  padding: 20px 30px;
  display: flex;
  align-items: center;
  gap: 20px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.header img { height: 50px; }
.header-text h1 { font-size: 1.4em; font-weight: 600; }
.header-text p { font-size: 0.85em; opacity: 0.85; }
.container { max-width: 900px; margin: 20px auto; padding: 0 20px; }
.progress-bar {
  background: var(--border);
  border-radius: 10px;
  height: 8px;
  margin-bottom: 20px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-dark));
  border-radius: 10px;
  transition: width 0.4s ease;
  width: 0%;
}
.card {
  background: var(--card-bg);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 30px;
  margin-bottom: 20px;
}
.welcome { text-align: center; }
.welcome h2 { color: var(--primary); margin-bottom: 15px; }
.welcome p { margin-bottom: 10px; }
.welcome .meta { color: var(--text-light); font-size: 0.9em; }
.module-header {
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  padding: 15px 20px;
  border-radius: var(--radius) var(--radius) 0 0;
  display: flex;
  align-items: center;
  gap: 12px;
}
.module-num {
  background: var(--accent);
  color: var(--primary);
  font-weight: 700;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1em;
}
.module-title { font-weight: 600; flex: 1; }
.module-duration {
  background: rgba(255,255,255,0.2);
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 0.85em;
}
.module-content { padding: 25px; }
.module-content h3 { color: var(--primary); margin: 20px 0 10px; font-size: 1.15em; }
.module-content h3:first-child { margin-top: 0; }
.module-content ul, .module-content ol { margin-left: 20px; margin-bottom: 15px; }
.module-content li { margin-bottom: 6px; }
.data-table {
  width: 100%;
  border-collapse: collapse;
  margin: 15px 0;
  font-size: 0.9em;
}
.data-table th, .data-table td {
  border: 1px solid var(--border);
  padding: 10px 12px;
  text-align: left;
}
.data-table th {
  background: var(--primary);
  color: white;
  font-weight: 600;
}
.data-table tr:nth-child(even) { background: #f8f9fa; }
.data-table tr:hover { background: #eef3f8; }
.alert {
  padding: 15px 20px;
  border-radius: var(--radius);
  margin: 15px 0;
  border-left: 4px solid;
}
.alert-danger { background: #f8d7da; border-color: var(--danger); color: #721c24; }
.alert-warning { background: #fff3cd; border-color: var(--warning); color: #856404; }
.alert-info { background: #d1ecf1; border-color: var(--info); color: #0c5460; }
.alert-success { background: #d4edda; border-color: var(--success); color: #155724; }
.case-study {
  background: #f0f4f8;
  border-left: 4px solid var(--primary-light);
  padding: 15px 20px;
  margin: 15px 0;
  border-radius: 0 var(--radius) var(--radius) 0;
}
.case-study p { margin-bottom: 8px; }
.nav-buttons { padding: 15px 25px; display: flex; gap: 10px; justify-content: space-between; }
.btn {
  padding: 10px 24px;
  border: none;
  border-radius: var(--radius);
  font-size: 0.95em;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 3px 8px rgba(0,0,0,0.15); }
.btn-primary { background: var(--accent); color: var(--primary); }
.btn-primary:hover { background: var(--accent-dark); }
.btn-secondary { background: var(--border); color: var(--text); }
.btn-secondary:hover { background: #c8ced6; }
.btn-success { background: var(--success); color: white; }

/* Quiz */
.quiz-question .quiz-num { color: var(--accent-dark); font-weight: 700; margin-bottom: 8px; }
.quiz-question .quiz-text { font-size: 1.05em; font-weight: 500; margin-bottom: 15px; }
.quiz-options { display: flex; flex-direction: column; gap: 8px; margin-bottom: 15px; }
.quiz-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: all 0.2s;
}
.quiz-option:hover { border-color: var(--primary-light); background: #f0f4f8; }
.quiz-option input { width: 18px; height: 18px; }
.quiz-feedback {
  padding: 12px 16px;
  border-radius: var(--radius);
  margin-bottom: 15px;
  display: none;
}
.quiz-feedback.correct { display: block; background: #d4edda; color: #155724; border-left: 4px solid var(--success); }
.quiz-feedback.incorrect { display: block; background: #f8d7da; color: #721c24; border-left: 4px solid var(--danger); }

/* Checklist */
.checklist-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 8px;
  cursor: pointer;
  transition: all 0.2s;
}
.checklist-item:hover { border-color: var(--primary-light); }
.checklist-item input { width: 20px; height: 20px; }
.checklist-item.checked { background: #d4edda; border-color: var(--success); text-decoration: line-through; }

/* Results */
.results-card { text-align: center; padding: 40px; }
.results-card .score { font-size: 3em; font-weight: 700; color: var(--primary); }
.results-card .score.pass { color: var(--success); }
.results-card .score.fail { color: var(--danger); }
.results-card .message { font-size: 1.2em; margin: 15px 0; }

/* TOC */
.toc { list-style: none; margin: 15px 0; }
.toc li {
  padding: 10px 15px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  transition: background 0.2s;
}
.toc li:hover { background: #eef3f8; }
.toc li .toc-num { color: var(--accent-dark); font-weight: 700; margin-right: 10px; }
.toc li .toc-dur { color: var(--text-light); font-size: 0.85em; }

.footer {
  text-align: center;
  padding: 20px;
  color: var(--text-light);
  font-size: 0.8em;
  border-top: 1px solid var(--border);
  margin-top: 30px;
}
@media (max-width: 600px) {
  .header { flex-direction: column; text-align: center; }
  .container { padding: 0 10px; }
  .card { padding: 20px; }
  .module-content { padding: 15px; }
  .nav-buttons { flex-direction: column; }
  .data-table { font-size: 0.8em; }
  .data-table th, .data-table td { padding: 6px 8px; }
}
</style>
</head>
<body>

<div class="header">
  <img src="__LOGO__" alt="JvG Consultancy Logo">
  <div class="header-text">
    <h1>Awareness Training Kwik (Hg)</h1>
    <p>Gevorderd niveau | Olie, Gas, Petrochemie, Offshore | ~45 min</p>
  </div>
</div>

<div class="container">
  <div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>

  <!-- Welcome -->
  <div class="card welcome" id="welcomeScreen">
    <h2>Awareness Training Kwik</h2>
    <p>Deze training behandelt de eigenschappen, risico's en veiligheidsmaatregelen rondom kwik (Hg) in industriële omgevingen.</p>
    <p class="meta">10 modules &bull; 15 quizvragen &bull; Noodprocedure checklist &bull; Mastery: 70%</p>
    <h3 style="margin-top:25px;">Inhoud</h3>
    <ul class="toc">
      __TOC_ITEMS__
    </ul>
    <button class="btn btn-primary" style="margin-top:20px;" onclick="startTraining()">Start Training \u2192</button>
  </div>

  <!-- Modules -->
  __MODULES_HTML__

  <!-- Quiz -->
  <div class="card" id="quizScreen" style="display:none;">
    <div class="module-header">
      <span class="module-num">Q</span>
      <span class="module-title">Kennisquiz — 15 Vragen</span>
      <span class="module-duration">Mastery 70%</span>
    </div>
    <div style="padding:25px;">
      <div id="quizProgress" style="margin-bottom:15px;color:var(--text-light);font-size:0.9em;"></div>
      __QUIZ_HTML__
      <div class="results-card" id="quizResults" style="display:none;">
        <div class="score" id="scoreDisplay"></div>
        <div class="message" id="scoreMessage"></div>
        <div style="margin-top:20px;">
          <button class="btn btn-primary" onclick="showChecklist()">Naar Checklist \u2192</button>
          <button class="btn btn-secondary" onclick="retryQuiz()">Opnieuw</button>
        </div>
      </div>
    </div>
  </div>

  <!-- Checklist -->
  <div class="card" id="checklistScreen" style="display:none;">
    <div class="module-header">
      <span class="module-num">\u2713</span>
      <span class="module-title">10-punten Noodplan Kwik</span>
    </div>
    <div style="padding:25px;">
      <p style="margin-bottom:15px;">Controleer of alle stappen zijn uitgevoerd bij een kwikspill:</p>
      __CHECKLIST_HTML__
      <div id="checklistProgress" style="margin-top:15px;font-weight:600;"></div>
      <div style="margin-top:20px;text-align:center;">
        <button class="btn btn-secondary" onclick="showScreen('quizScreen')">\u2190 Terug naar Quiz</button>
        <button class="btn btn-primary" onclick="showCertificate()">Certificaat \u2192</button>
      </div>
    </div>
  </div>

  <!-- Certificate -->
  <div class="card results-card" id="certScreen" style="display:none;">
    <div style="font-size:2em;margin-bottom:10px;">\ud83c\udfc6</div>
    <h2 style="color:var(--primary);">Training Voltooid</h2>
    <p>U heeft de Awareness Training Kwik succesvol afgerond.</p>
    <p style="color:var(--text-light);font-size:0.9em;" id="certDate"></p>
    <div style="margin-top:20px;">
      <button class="btn btn-primary" onclick="window.print()">Afdrukken</button>
      <button class="btn btn-secondary" onclick="startTraining()">Opnieuw</button>
    </div>
  </div>
</div>

<div class="footer">
  &copy; 2026 JvG Consultancy | Awareness Training Kwik v1.0 | HSEQ Trainingmateriaal
</div>

<script>
var quizData = __QUIZ_JSON__;
var currentModule = 0;
var currentQuestion = 0;
var score = 0;
var answered = 0;
var totalSteps = 12; // 10 modules + quiz + checklist

function updateProgress(step) {
  var pct = Math.round((step / totalSteps) * 100);
  document.getElementById("progressFill").style.width = pct + "%";
}

function showScreen(id) {
  var screens = ["welcomeScreen","quizScreen","checklistScreen","certScreen"];
  screens.forEach(function(s) {
    var el = document.getElementById(s);
    if (el) el.style.display = "none";
  });
  for (var i = 1; i <= 10; i++) {
    var m = document.getElementById("module-" + i);
    if (m) m.style.display = "none";
  }
  var target = document.getElementById(id);
  if (target) target.style.display = "block";
}

function startTraining() {
  currentModule = 1;
  showModule(1);
}

function showModule(n) {
  currentModule = n;
  for (var i = 1; i <= 10; i++) {
    document.getElementById("module-" + i).style.display = (i === n) ? "block" : "none";
  }
  showScreen("module-" + n);
  document.getElementById("module-" + n).style.display = "block";
  updateProgress(n);
  window.scrollTo(0, 0);
}

function nextModule() {
  if (currentModule < 10) showModule(currentModule + 1);
}

function prevModule() {
  if (currentModule > 1) showModule(currentModule - 1);
}

function showQuiz() {
  currentQuestion = 0;
  score = 0;
  answered = 0;
  for (var i = 0; i < quizData.length; i++) {
    var el = document.getElementById("qq" + i);
    if (el) el.style.display = "none";
    var fb = document.getElementById("qf" + i);
    if (fb) { fb.className = "quiz-feedback"; fb.style.display = "none"; fb.innerHTML = ""; }
    var radios = document.getElementsByName("q" + i);
    for (var j = 0; j < radios.length; j++) radios[j].checked = false;
  }
  document.getElementById("quizResults").style.display = "none";
  document.getElementById("qq0").style.display = "block";
  updateQuizProgress();
  showScreen("quizScreen");
  document.getElementById("quizScreen").style.display = "block";
  updateProgress(11);
  window.scrollTo(0, 0);
}

function updateQuizProgress() {
  document.getElementById("quizProgress").textContent = "Vraag " + (currentQuestion + 1) + " van " + quizData.length + " | Score: " + score + "/" + answered;
}

function checkAnswer(idx) {
  var radios = document.getElementsByName("q" + idx);
  var selected = -1;
  for (var i = 0; i < radios.length; i++) {
    if (radios[i].checked) selected = parseInt(radios[i].value);
  }
  if (selected === -1) return;

  var fb = document.getElementById("qf" + idx);
  var correct = quizData[idx].correct;
  if (selected === correct) {
    fb.className = "quiz-feedback correct";
    fb.innerHTML = "\u2713 Correct! " + quizData[idx].explanation;
    if (!document.getElementById("q" + idx + "_scored")) {
      score++;
      document.getElementById("q" + idx + "_scored") = true;
    }
  } else {
    fb.className = "quiz-feedback incorrect";
    fb.innerHTML = "\u2717 Incorrect. " + quizData[idx].explanation;
  }
  if (!document.getElementById("q" + idx + "_answered")) {
    answered++;
    document.getElementById("q" + idx + "_answered") = true;
  }
  updateQuizProgress();
}

function nextQuestion(idx) {
  checkAnswer(idx);
  if (idx < quizData.length - 1) {
    document.getElementById("qq" + idx).style.display = "none";
    currentQuestion = idx + 1;
    document.getElementById("qq" + currentQuestion).style.display = "block";
    updateQuizProgress();
    window.scrollTo(0, 0);
  } else {
    showResults();
  }
}

function showResults() {
  var pct = Math.round((score / quizData.length) * 100);
  var el = document.getElementById("scoreDisplay");
  el.textContent = score + "/" + quizData.length + " (" + pct + "%)";
  el.className = "score " + (pct >= 70 ? "pass" : "fail");
  document.getElementById("scoreMessage").textContent = pct >= 70
    ? "Gefeliciteerd! U heeft de training succesvol afgerond."
    : "U heeft het minimum niet gehaald (70%). Raadpleeg de modules en probeer opnieuw.";
  for (var i = 0; i < quizData.length; i++) {
    document.getElementById("qq" + i).style.display = "none";
  }
  document.getElementById("quizProgress").style.display = "none";
  document.getElementById("quizResults").style.display = "block";
  // SCORM
  if (typeof setScormScore === "function") setScormScore(score, quizData.length, pct >= 70);
}

function retryQuiz() {
  for (var i = 0; i < quizData.length; i++) {
    delete document.getElementById("q" + i + "_scored");
    delete document.getElementById("q" + i + "_answered");
  }
  document.getElementById("quizProgress").style.display = "block";
  showQuiz();
}

function showChecklist() {
  showScreen("checklistScreen");
  document.getElementById("checklistScreen").style.display = "block";
  updateProgress(12);
  window.scrollTo(0, 0);
}

function updateChecklist() {
  var total = __CHECKLIST_COUNT__;
  var checked = 0;
  for (var i = 0; i < total; i++) {
    var cb = document.getElementById("cl" + i);
    var label = cb.parentElement;
    if (cb.checked) {
      checked++;
      label.classList.add("checked");
    } else {
      label.classList.remove("checked");
    }
  }
  document.getElementById("checklistProgress").textContent = checked + "/" + total + " punten gecontroleerd";
  document.getElementById("checklistProgress").style.color = checked === total ? "var(--success)" : "var(--text)";
}

function showCertificate() {
  document.getElementById("certDate").textContent = "Datum: " + new Date().toLocaleDateString("nl-NL", {year:"numeric",month:"long",day:"numeric"});
  showScreen("certScreen");
  document.getElementById("certScreen").style.display = "block";
  if (typeof scormComplete === "function") scormComplete();
}
</script>
</body>
</html>"""

    # Build TOC items
    toc_items = ""
    for m in MODULES:
        toc_items += '<li onclick="startTraining();showModule(' + str(m["id"]) + ')"><span><span class="toc-num">' + str(m["id"]) + '.</span> ' + m["title"] + '</span><span class="toc-dur">' + m["duration"] + '</span></li>\n'

    # Format the HTML
    html = html.replace('__LOGO__', LOGO_B64).replace('__TOC_ITEMS__', toc_items).replace('__MODULES_HTML__', '\n'.join(modules_html_parts)).replace('__QUIZ_HTML__', '\n'.join(quiz_parts)).replace('__QUIZ_JSON__', quiz_json).replace('__CHECKLIST_COUNT__', str(len(CHECKLIST_ITEMS))).replace('__CHECKLIST_HTML__', '\n'.join(checklist_parts))

    with open(HTML_OUT, "w", encoding="utf-8", errors="surrogatepass") as f:
        f.write(html)
    print("HTML generated: " + HTML_OUT)
    print("Size: " + str(os.path.getsize(HTML_OUT)) + " bytes")


def generate_scorm():
    """Generate SCORM 1.2 package."""
    if os.path.exists(SCORM_WDIR):
        shutil.rmtree(SCORM_WDIR)
    os.makedirs(SCORM_WDIR, exist_ok=True)

    # Copy HTML and logo
    shutil.copy2(HTML_OUT, os.path.join(SCORM_WDIR, "kwik-elearning_v1.0.html"))
    shutil.copy2(LOGO_PNG, os.path.join(SCORM_WDIR, "jvg-consultancy-logo.png"))

    # imsmanifest.xml
    manifest = """<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="JVG_KWIK_V1" version="1.0"
  xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
  xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd
                       http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd
                       http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">
  <metadata>
    <schema>ADL SCORM</schema>
    <schemaversion>1.2</schemaversion>
  </metadata>
  <organizations default="JVG_ORG">
    <organization identifier="JVG_ORG">
      <title>Awareness Training Kwik</title>
      <item identifier="KWIK_ITEM" identifierref="KWIK_RES">
        <title>Awareness Training Kwik (Hg) — JvG Consultancy</title>
        <adlcp:masteryscore>70</adlcp:masteryscore>
      </item>
    </organization>
  </organizations>
  <resources>
    <resource identifier="KWIK_RES" type="webcontent" adlcp:scormtype="sco" href="kwik-elearning_v1.0.html">
      <file href="kwik-elearning_v1.0.html"/>
      <file href="jvg-consultancy-logo.png"/>
    </resource>
  </resources>
</manifest>"""

    with open(os.path.join(SCORM_WDIR, "imsmanifest.xml"), "w", encoding="utf-8") as f:
        f.write(manifest)

    # Create ZIP
    if os.path.exists(SCORM_ZIP):
        os.remove(SCORM_ZIP)
    with zipfile.ZipFile(SCORM_ZIP, "w", zipfile.ZIP_DEFLATED) as zf:
        for root, dirs, files in os.walk(SCORM_WDIR):
            for file in files:
                filepath = os.path.join(root, file)
                arcname = os.path.relpath(filepath, SCORM_WDIR)
                zf.write(filepath, arcname)

    size = os.path.getsize(SCORM_ZIP)
    print("SCORM package generated: " + SCORM_ZIP)
    print("Size: " + str(size) + " bytes")
    # Cleanup build dir
    shutil.rmtree(SCORM_WDIR)


if __name__ == "__main__":
    generate_html()
    generate_scorm()
    print("\nDone! Both deliverables generated successfully.")
