// White-label leerpad — engine
// Reads window.LEERPAD_CONFIG (defined by a content-*.jsx file loaded before this engine).
// Renders the entire learning path: welcome → tips → overview → modules → completion.

const { useState, useEffect, useMemo } = React;

if (!window.LEERPAD_CONFIG) {
  throw new Error("engine.jsx: window.LEERPAD_CONFIG is missing. Load a content-<client>.jsx file before engine.jsx.");
}
const C = window.LEERPAD_CONFIG;
const MODULES = C.modules;
const TIPS = C.tips.items;

// Set page title
if (C.pageTitle) document.title = C.pageTitle;

// Apply theme tokens to :root
(() => {
  const root = document.documentElement;
  const t = C.theme || {};
  if (t.dark) root.style.setProperty("--dark", t.dark);
  if (t.bright) root.style.setProperty("--bright", t.bright);
  if (t.bright2) root.style.setProperty("--bright-2", t.bright2);
  if (t.soft) root.style.setProperty("--soft", t.soft);
  if (t.cream) root.style.setProperty("--cream", t.cream);
  if (t.pager) root.style.setProperty("--pager", t.pager);
})();


/* ──────────── MASCOT + ICONS ──────────── */
const MASCOT_CELLS = [[3, 0], [2, 1], [3, 1], [4, 1], [0, 1], [7, 1], [1, 2], [2, 2], [4, 2], [5, 2], [0, 3], [1, 3], [6, 3], [7, 3], [0, 4], [1, 4], [6, 4], [7, 4], [1, 5], [2, 5], [4, 5], [5, 5], [2, 6], [3, 6], [4, 6], [0, 6], [7, 6], [3, 7]];
function Mascot({ size = 120 }) {
  const grid = 8,cell = size / grid;
  return (
    <svg className="mascot" width={size} height={size} viewBox={`0 0 ${size} ${size}`} aria-hidden="true">
      {MASCOT_CELLS.map(([c, r], i) =>
      <rect key={i} x={c * cell + 1} y={r * cell + 1} width={cell - 2} height={cell - 2} rx={1} />
      )}
      <rect x={cell * 3 + 1} y={cell * 3 + 1} width={cell * 2 - 2} height={cell * 2 - 2} rx={3} fill="white" />
    </svg>);

}

/* Pretencify brand mark — animated dot-network ("connecting the dots").
   Bold node-chain (ring + connectors + nodes) inside a twinkling halo of squares.
   currentColor drives the fill; animation lives in pretencify.css (reduced-motion aware). */
const PF_HALO = [
[41, 14], [57, 22], [64, 34], [65, 51], [57, 61], [41, 69],
[24, 61], [16, 51], [15, 33], [24, 22], [30, 30], [18, 42], [30, 55]];

function PretencifyMark({ size = 40 }) {
  return (
    <svg className="pf-mark" width={size} height={size} viewBox="0 0 80 80" aria-hidden="true" fill="currentColor">
      {/* halo of twinkling squares */}
      {PF_HALO.map(([x, y], i) =>
      <rect key={i} className="pf-dot" x={x - 2.6} y={y - 2.6} width="5.2" height="5.2" rx="1.3"
      style={{ animationDelay: `${i % PF_HALO.length * 0.16}s` }} />
      )}
      {/* connectors */}
      <g stroke="currentColor" strokeLinecap="round" fill="none">
        <line className="pf-link" x1="41" y1="37" x2="35" y2="27" strokeWidth="7" />
        <line className="pf-link" x1="35" y1="26" x2="46" y2="21" strokeWidth="5.5" />
        <line className="pf-link" x1="40" y1="49" x2="34" y2="57" strokeWidth="7" />
        <line className="pf-link" x1="34" y1="57" x2="32" y2="64" strokeWidth="5.5" />
      </g>
      {/* right stub */}
      <rect x="49" y="37" width="13" height="10" rx="5" />
      {/* square terminals */}
      <rect className="pf-dot" x="28.5" y="11" width="8" height="8" rx="1.6" style={{ animationDelay: "0.5s" }} />
      <rect className="pf-dot" x="27.5" y="61.5" width="9" height="9" rx="1.6" style={{ animationDelay: "0.9s" }} />
      {/* chain nodes */}
      <circle className="pf-node" cx="35" cy="26" r="6" style={{ animationDelay: "0.1s" }} />
      <circle className="pf-node" cx="47" cy="20" r="4.5" style={{ animationDelay: "0.25s" }} />
      <circle className="pf-node" cx="34" cy="57" r="5.5" style={{ animationDelay: "0.4s" }} />
      {/* hollow centre ring */}
      <circle className="pf-ring" cx="41" cy="42" r="8.5" fill="none" stroke="currentColor" strokeWidth="6" />
    </svg>);

}
const I = {
  flag: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 22V4h12l-2 4 2 4H6" /><line x1="4" y1="4" x2="4" y2="22" /></svg>,
  arrowUp: <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="19" x2="12" y2="5" /><polyline points="5 12 12 5 19 12" /></svg>,
  arrowLeft: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="20" y1="12" x2="4" y2="12" /><polyline points="10 6 4 12 10 18" /></svg>,
  arrowRight: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="12" x2="20" y2="12" /><polyline points="14 6 20 12 14 18" /></svg>,
  up: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 14 12 8 18 14" /></svg>,
  down: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 10 12 16 18 10" /></svg>,
  check: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12" /></svg>,
  x: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>,
  lock: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="4" y="11" width="16" height="9" rx="2" /><path d="M8 11V7a4 4 0 0 1 8 0v4" /></svg>,
  checkBig: <svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12" /></svg>,
  play: <svg width="36" height="36" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z" /></svg>,
  info: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 4h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4z" /><line x1="11" y1="9.5" x2="11" y2="9.5" /><line x1="11" y1="12" x2="11" y2="14.5" /></svg>,
  chev: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6" /></svg>,
  iMap: <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><polygon points="2 6 9 3 15 6 22 3 22 19 15 22 9 19 2 22 2 6" /><line x1="9" y1="3" x2="9" y2="19" /><line x1="15" y1="6" x2="15" y2="22" /><circle cx="9" cy="11" r="2" fill="currentColor" /></svg>,
  iRank: <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="6" height="12" /><rect x="3" y="14" width="6" height="7" /><rect x="15" y="6" width="6" height="15" /><circle cx="12" cy="4" r="2" /></svg>,
  iTip: <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M3 5h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4z" /><polygon points="11 8 12 11 15 11 12.5 13 13.5 16 11 14 8.5 16 9.5 13 7 11 10 11" /></svg>,
  trophy: <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9H4a2 2 0 0 1-2-2V5h4" /><path d="M18 9h2a2 2 0 0 0 2-2V5h-4" /><path d="M6 2v7a6 6 0 0 0 12 0V2" /><line x1="6" y1="2" x2="18" y2="2" /><path d="M9 22h6" /><path d="M12 17v5" /></svg>
};
const TIP_ICONS = { map: I.iMap, rank: I.iRank, tip: I.iTip };

/* ──────────── GELUID (Web Audio, geen bestanden) ────────────
   Eén feestelijke fanfare op het beloningsvenster.
   Respecteert de Tweak-toggle via SFX.setEnabled(). */
const SFX = (() => {
  let ctx = null;
  let enabled = true;
  const ac = () => {
    if (!ctx) {try {ctx = new (window.AudioContext || window.webkitAudioContext)();} catch (e) {return null;}}
    if (ctx && ctx.state === "suspended") ctx.resume();
    return ctx;
  };
  const tone = (freq, start, dur, type, gain) => {
    const c = ac();
    if (!c) return;
    const o = c.createOscillator();
    const g = c.createGain();
    o.type = type || "sine";
    o.frequency.value = freq;
    const t0 = c.currentTime + start;
    o.connect(g);g.connect(c.destination);
    g.gain.setValueAtTime(0.0001, t0);
    g.gain.exponentialRampToValueAtTime(gain || 0.16, t0 + 0.012);
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
    o.start(t0);o.stop(t0 + dur + 0.04);
  };
  return {
    setEnabled(v) {enabled = !!v;},
    isEnabled() {return enabled;},
    // feestelijke fanfare op het beloningsvenster; uitbundiger naarmate meer Sparks
    reward(n) {
      if (!enabled) return;
      const big = n >= 3;
      // vrolijk oplopend arpeggio (C-majeur, eindigt hoog)
      const melody = [523.25, 659.25, 783.99, 1046.5, 1318.5];
      const steps = Math.max(3, Math.min(n + 2, melody.length));
      for (let i = 0; i < steps; i++) {
        tone(melody[i], i * 0.11, 0.34, "triangle", 0.16);
        tone(melody[i] * 2, i * 0.11, 0.18, "sine", 0.05); // glans-octaaf
      }
      // afsluitend majeur-akkoord (do-mi-sol-do)
      const end = steps * 0.11 + 0.04;
      [523.25, 659.25, 783.99, 1046.5].forEach((f) => tone(f, end, 0.85, "triangle", 0.12));
      // sprankelend staartje van hoge belletjes
      const sparkleN = big ? 7 : 4;
      for (let i = 0; i < sparkleN; i++) {
        tone(1568 + Math.random() * 900, end + 0.12 + i * 0.07, 0.22, "sine", 0.07);
      }
    }
  };
})();


/* ───────────────────────────────────────────────────────────────────
   DYNAMIC DECORATION COMPONENTS
   ────────────────────────────────────────────────────────────────── */

// Background decoration layer — floating shapes that drift behind everything.
// Activates based on :root data-bg attribute (set via Tweaks).
// Neural-network mesh — the AI-thema backdrop: nodes wired by faint edges,
// gently pulsing ("signals" travelling the net). Doubles as Pretencify's
// "connecting the dots". Styling + animation live in styles.css / pretencify.css.
function NeuralMesh({ reward = false }) {
  const { nodes, edges } = useMemo(() => {
    const pts = [
    [8, 16], [19, 60], [14, 37], [33, 26], [29, 82], [46, 50], [53, 14],
    [60, 73], [67, 38], [78, 19], [83, 58], [90, 82], [73, 92], [41, 7], [89, 33]].
    map(([x, y]) => ({ x, y }));
    const edges = [];
    pts.forEach((p, i) => {
      pts.map((q, j) => ({ j, d: (q.x - p.x) ** 2 + (q.y - p.y) ** 2 })).
      filter((o) => o.j !== i).sort((a, b) => a.d - b.d).slice(0, 2).
      forEach((o) => {if (i < o.j) edges.push([i, o.j]);});
    });
    return { nodes: pts, edges };
  }, []);
  return (
    <svg className={"neural-mesh" + (reward ? " on-dark" : "")} viewBox="0 0 100 100"
    preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <g className="nm-edges">
        {edges.map(([a, b], i) =>
        <line key={i} x1={nodes[a].x} y1={nodes[a].y} x2={nodes[b].x} y2={nodes[b].y}
        style={{ animationDelay: `${i % 7 * 0.5}s` }} />
        )}
      </g>
      <g className="nm-nodes">
        {nodes.map((n, i) =>
        <circle key={i} cx={n.x} cy={n.y} r={i % 4 === 0 ? 1.15 : 0.72}
        style={{ animationDelay: `${i % 6 * 0.6}s` }} />
        )}
      </g>
    </svg>);

}

function BgDeco({ variant = "dots", reward = false }) {
  const shapes = useMemo(() => {
    if (variant === "none") return [];
    const kinds = ["ring", "dot"];
    return Array.from({ length: reward ? 7 : 5 }, (_, i) => ({
      kind: kinds[i % 2],
      size: 28 + i * 37 % 80,
      top: i * 73 % 90 + 4,
      left: i * 113 % 92 + 2,
      dur: 10 + i * 5 % 14,
      delay: -(i * 1.7 % 12)
    }));
  }, [variant, reward]);
  return (
    <div className="bg-deco" aria-hidden="true">
      {variant !== "none" && <NeuralMesh reward={reward} />}
      {shapes.map((s, i) =>
      <div key={i} className={"float-shape " + s.kind}
      style={{
        width: s.size, height: s.size,
        top: s.top + "%", left: s.left + "%",
        animationDuration: s.dur + "s",
        animationDelay: s.delay + "s"
      }} />
      )}
    </div>);

}

// Confetti burst — rendered on completion screen.
function Confetti({ count = 80 }) {
  const pieces = useMemo(() => {
    const colors = ["var(--bright)", "var(--reward)", "var(--soft)", "var(--dark)", "#FFFFFF"];
    return Array.from({ length: count }, (_, i) => ({
      left: Math.random() * 100,
      dur: 2.6 + Math.random() * 2,
      delay: Math.random() * 1.5,
      color: colors[i % colors.length],
      size: 6 + Math.random() * 8,
      rot: Math.random() * 360
    }));
  }, [count]);
  return (
    <div className="confetti-wrap" aria-hidden="true">
      {pieces.map((p, i) =>
      <span key={i} className="confetti-piece" style={{
        left: p.left + "%",
        background: p.color,
        width: p.size, height: p.size * 1.5,
        animationDuration: p.dur + "s",
        animationDelay: p.delay + "s",
        transform: `rotate(${p.rot}deg)`
      }} />
      )}
    </div>);

}

// Halo rings pulsing out from a central point (reward screen).
function RewardHalo({ enabled = true }) {
  if (!enabled) return null;
  return (
    <>
      <div className="reward-halo" style={{ left: "50%", top: "38%" }} />
      <div className="reward-halo delay-1" style={{ left: "50%", top: "38%" }} />
      <div className="reward-halo delay-2" style={{ left: "50%", top: "38%" }} />
    </>);

}


const DIFF_LABEL = { easy: "Eenvoudig", medium: "Gemiddeld", hard: "Uitdagend" };
const TYPE_LABEL = { quiz: "Meerkeuze", blank: "Aanvullen", dragdrop: "Sorteren", match: "Verbinden", order: "Volgorde", hotspot: "Foutdetectie", flashcard: "Flashcards", finderror: "Zoek de fout" };

/* ──────────── TOP BAR ──────────── */
function TopBar({ progress, score, flash, currentModuleTitle, onProfile }) {
  const [pop, setPop] = useState(false);
  const prevScore = React.useRef(score);
  useEffect(() => {
    if (score > prevScore.current) {
      setPop(true);
      const tm = setTimeout(() => setPop(false), 600);
      prevScore.current = score;
      return () => clearTimeout(tm);
    }
    prevScore.current = score;
  }, [score]);
  const brand = C.branding.brand;
  const logo = brand && brand.logo;
  return (
    <div className="top-bar">
      <div className="brand-cell">
        {logo ?
        <img className="brand-logo" src={logo} alt={brand.logoAlt || C.branding.appName} /> :
        <>
          <div className="brand-mark"><Mascot size={36} /></div>
          <div className="brand-app">{C.branding.appName}</div>
        </>}
        <span className="brand-divider" aria-hidden="true"></span>
        <div className="brand-sub">{currentModuleTitle || C.branding.moduleName}</div>
      </div>
      <div className="flag-bar" role="progressbar" aria-valuenow={progress}>
        <div className="fill" style={{ width: `calc(${progress}% - 6px)` }}></div>
        {flash && progress > 0 && <div className="pulse" style={{ left: `calc(${progress}% - 4px)`, top: "50%" }}></div>}
        <div className="flag-end">{I.flag}</div>
      </div>
      <div className="right-cell">
        <button className={"score-pill" + (pop ? " pop" : "")} onClick={onProfile} title="Bekijk je Sparks in je profiel">
          {(flash || pop) && <span className="up">{I.arrowUp}</span>}
          <span>{score}</span>
        </button>
        <button className="user-chip" onClick={onProfile} title="Naar je profiel">
          <span className="user-avatar">{C.branding.userInitials}</span>
          <span>{C.branding.userName}</span>
        </button>
      </div>
    </div>);

}

/* ──────────── EXERCISE HEADER (shared) ──────────── */
function ExHead({ exercise, exIdx, exTotal, timer }) {
  return (
    <div className="ex-head">
      <span className={"diff-badge " + exercise.difficulty}>● {DIFF_LABEL[exercise.difficulty]}</span>
      <span className="ex-type-label">{TYPE_LABEL[exercise.type]} · oefening {exIdx + 1} van {exTotal}</span>
      {timer != null && <TimerBadge seconds={timer.seconds} total={timer.total} expired={timer.expired} />}
    </div>);
}

/* TimerBadge: countdown pill with colour-shift as time runs out */
function TimerBadge({ seconds, total, expired }) {
  const pct = Math.max(0, Math.min(1, seconds / total));
  let cls = "timer-badge";
  if (expired) cls += " expired";else
  if (pct < 0.25) cls += " critical";else
  if (pct < 0.5) cls += " warn";
  const mm = Math.floor(seconds / 60);
  const ss = Math.max(0, seconds % 60).toString().padStart(2, "0");
  const R = 13, C = 2 * Math.PI * R;
  return (
    <span className={cls}>
      <span className="timer-ring">
        <svg viewBox="0 0 32 32" width="32" height="32">
          <circle className="tr-track" cx="16" cy="16" r={R} />
          <circle className="tr-arc" cx="16" cy="16" r={R}
          style={{ strokeDasharray: C, strokeDashoffset: C * (1 - pct) }} />
        </svg>
      </span>
      <span className="timer-num" key={expired ? "x" : seconds}>{expired ? "Tijd op" : `${mm}:${ss}`}</span>
    </span>);
}

/* useExerciseTimer: counts down when exercise.timeLimit is set, pauses when submitted */
function useExerciseTimer(exercise, submitted, onExpire) {
  const total = exercise.timeLimit;
  const [seconds, setSeconds] = useState(total || 0);
  const expiredRef = React.useRef(false);
  useEffect(() => {
    if (!total) return;
    if (submitted) return;
    if (seconds <= 0) {
      if (!expiredRef.current) {
        expiredRef.current = true;
        onExpire && onExpire();
      }
      return;
    }
    const t = setTimeout(() => setSeconds((s) => s - 1), 1000);
    return () => clearTimeout(t);
  }, [total, seconds, submitted]);
  if (!total) return null;
  return { seconds, total, expired: seconds <= 0 };
}
function ExFeedback({ isCorrect, explain }) {
  return (
    <div className={"ex-feedback " + (isCorrect ? "correct" : "wrong")}>
      <b>{isCorrect ? "Juist." : "Niet helemaal."}</b>
      {explain}
    </div>);

}

/* ──────────── EXERCISE: QUIZ ──────────── */
function ExQuiz({ exercise, exIdx, exTotal, onResult, onNext }) {
  const [picked, setPicked] = useState(null);
  const submitted = picked != null;
  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} />
          <div className="ex-q">{exercise.q}</div>
          <div className="quiz-options">
            {exercise.options.map((opt, i) => {
              let state = "";
              if (submitted) {
                if (i === exercise.correct) state = "is-correct";else
                if (i === picked) state = "is-wrong";else
                state = "is-dim";
              }
              return (
                <button key={i} className={"quiz-opt " + state} disabled={submitted}
                onClick={() => {setPicked(i);onResult(i === exercise.correct);}}>
                  <span className="opt-letter">{String.fromCharCode(65 + i)}</span>
                  <span>{opt}</span>
                </button>);

            })}
          </div>
          {submitted && <ExFeedback isCorrect={picked === exercise.correct} explain={exercise.explain} />}
        </div>
      </div>
      <div className="foot">
        <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
        <button className={"btn-pill" + (submitted ? "" : " disabled")} disabled={!submitted} onClick={onNext}>
          {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
        </button>
      </div>
    </>);

}

/* ──────────── EXERCISE: BLANK ──────────── */
function ExBlank({ exercise, exIdx, exTotal, onResult, onNext }) {
  const [val, setVal] = useState("");
  const [submitted, setSubmitted] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);
  const check = () => {
    const norm = val.trim().toLowerCase();
    const valid = [exercise.answer, ...(exercise.alts || [])].map((s) => s.toLowerCase());
    const ok = valid.includes(norm);
    setIsCorrect(ok);
    setSubmitted(true);
    onResult(ok);
  };
  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} />
          <div className="blank-sentence">
            <span>{exercise.before}</span>
            <input className={"blank-input" + (submitted ? isCorrect ? " is-correct" : " is-wrong" : "")}
            value={val} onChange={(e) => setVal(e.target.value)}
            onKeyDown={(e) => {if (e.key === "Enter" && !submitted && val.trim()) check();}}
            disabled={submitted} placeholder="…" aria-label="vul aan" />
            <span>{exercise.after}</span>
          </div>
          {!submitted &&
          <div className="ex-submit">
              <button className={"btn-pill" + (val.trim() ? "" : " disabled")} disabled={!val.trim()} onClick={check}>
                Controleren {I.arrowRight}
              </button>
            </div>
          }
          {submitted && <ExFeedback isCorrect={isCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {submitted &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── EXERCISE: DRAG-DROP (echt slepen + tik-fallback) ──────────── */
function ExDragDrop({ exercise, exIdx, exTotal, onResult, onNext }) {
  const [selected, setSelected] = useState(null); // tik-fallback: gekozen item
  const [dragId, setDragId] = useState(null); // item dat nu gesleept wordt
  const [overBin, setOverBin] = useState(null); // bak waarboven gehoverd wordt
  const [placement, setPlacement] = useState({}); // itemId -> binId
  const [submitted, setSubmitted] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);

  const binLabel = (binId) => {
    const b = exercise.bins.find((x) => x.id === binId);
    return b ? b.label : "";
  };

  const placeIn = (binId) => {
    const id = selected || dragId;
    if (!id || submitted) return;
    setPlacement((p) => ({ ...p, [id]: binId }));
    setSelected(null);
  };
  const unplace = (itemId) => {
    if (submitted) return;
    setPlacement((p) => {const n = { ...p };delete n[itemId];return n;});
    setSelected(null);
  };

  // native drag handlers
  const onDragStart = (e, itemId) => {
    if (submitted) {e.preventDefault();return;}
    setDragId(itemId);
    setSelected(null);
    e.dataTransfer.effectAllowed = "move";
    try {e.dataTransfer.setData("text/plain", itemId);} catch (err) {}
  };
  const onDragEnd = () => {setDragId(null);setOverBin(null);};
  const onBinDragOver = (e, binId) => {if (submitted) return;e.preventDefault();e.dataTransfer.dropEffect = "move";setOverBin(binId);};
  const onBinDrop = (e, binId) => {
    if (submitted) return;
    e.preventDefault();
    const id = dragId || e.dataTransfer.getData("text/plain");
    if (id) setPlacement((p) => ({ ...p, [id]: binId }));
    setDragId(null);setOverBin(null);setSelected(null);
  };

  const allPlaced = exercise.items.every((it) => placement[it.id]);
  const correctCount = exercise.items.filter((it) => placement[it.id] === exercise.answer[it.id]).length;
  const wrongCount = exercise.items.length - correctCount;
  const check = () => {
    if (submitted) return;
    const ok = exercise.items.every((it) => placement[it.id] === exercise.answer[it.id]);
    setIsCorrect(ok);
    setSubmitted(true);
    onResult(ok);
  };
  const timer = useExerciseTimer(exercise, submitted, () => check());
  const itemsInBin = (binId) => exercise.items.filter((it) => placement[it.id] === binId);
  const unplacedItems = exercise.items.filter((it) => !placement[it.id]);

  // gedeelde renderer voor een sleepbaar item (pool én bak)
  const renderItem = (it, inBin) => {
    let cls = "";
    let wrong = false;
    if (submitted && inBin) {
      wrong = exercise.answer[it.id] !== placement[it.id];
      cls = wrong ? " wrong-place" : " correct-place";
    }
    return (
      <div key={it.id}
      className={"dd-item" + (it.image ? " dd-item-img" : "") + (selected === it.id ? " selected" : "") + (dragId === it.id ? " dragging" : "") + cls}
      draggable={!submitted}
      onDragStart={(e) => onDragStart(e, it.id)}
      onDragEnd={onDragEnd}
      role="button" tabIndex={submitted ? -1 : 0}
      aria-pressed={selected === it.id}
      onClick={(e) => {
        if (submitted) return;
        e.stopPropagation();
        if (inBin) {unplace(it.id);return;}
        setSelected((s) => s === it.id ? null : it.id);
      }}>
        {it.image && <img src={it.image} alt={it.label || ""} className="dd-img" draggable="false" />}
        <span className="dd-item-label">{it.label}</span>
        {submitted && inBin &&
        <span className={"dd-mark " + (wrong ? "is-wrong" : "is-right")} aria-hidden="true">
          {wrong ? I.x : I.check}
        </span>}
        {submitted && wrong &&
        <span className="dd-correct-hint">Hoort bij: {binLabel(exercise.answer[it.id])}</span>}
      </div>);
  };

  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} timer={timer} />
          <div className="ex-q">{exercise.q}</div>
          <div className="dd-stage">
            <div className="dd-hint">{exercise.hint}</div>
            {!submitted &&
            <div className={"dd-items" + (dragId ? " is-source" : "")}
            onDragOver={(e) => {if (!submitted && dragId) {e.preventDefault();}}}
            onDrop={(e) => {
              if (submitted) return;
              e.preventDefault();
              const id = dragId || e.dataTransfer.getData("text/plain");
              if (id) unplace(id);
              setDragId(null);setOverBin(null);
            }}>
              {unplacedItems.length === 0 ?
              <div className="dd-empty">Alle foto's zijn gesorteerd. Klik op "Controleren".</div> :
              unplacedItems.map((it) => renderItem(it, false))}
            </div>}
            <div className="dd-bins">
              {exercise.bins.map((bin) => {
                const items = itemsInBin(bin.id);
                return (
                  <div key={bin.id}
                  className={"dd-bin" + ((selected || dragId) && !submitted ? " target-ready" : "") + (overBin === bin.id ? " drag-over" : "")}
                  onClick={() => placeIn(bin.id)}
                  onDragOver={(e) => onBinDragOver(e, bin.id)}
                  onDragLeave={() => setOverBin((b) => b === bin.id ? null : b)}
                  onDrop={(e) => onBinDrop(e, bin.id)}>
                    <div className="dd-bin-label">
                      {bin.label}
                      <span className="dd-bin-count">{items.length}</span>
                    </div>
                    <div className="dd-bin-items">
                      {items.length === 0 && !submitted &&
                      <span className="dd-bin-empty">Sleep hierheen</span>}
                      {items.map((it) => renderItem(it, true))}
                    </div>
                  </div>);
              })}
            </div>
          </div>
          {!submitted &&
          <div className="ex-submit">
              <button className={"btn-pill" + (allPlaced ? "" : " disabled")} disabled={!allPlaced} onClick={check}>
                Controleren {I.arrowRight}
              </button>
            </div>
          }
          {submitted &&
          <div className={"dd-score " + (isCorrect ? "all-right" : "has-wrong")}>
            <span className="dd-score-badge">{isCorrect ? I.check : I.x}</span>
            <span className="dd-score-text">{isCorrect ?
              `Alles juist. ${correctCount} van ${exercise.items.length} foto's correct gesorteerd.` :
              `${correctCount} van ${exercise.items.length} juist, ${wrongCount} verkeerd (in het rood). Kijk waar ze thuishoren.`}</span>
          </div>}
          {submitted && <ExFeedback isCorrect={isCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {submitted &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── EXERCISE: MATCH PAIRS ──────────── */
function ExMatch({ exercise, exIdx, exTotal, onResult, onNext }) {
  // shuffle right column deterministically
  const rightOrder = useMemo(() => {
    const arr = exercise.pairs.map((p, i) => ({ ...p, origIdx: i }));
    // Fisher-Yates with seed for stability
    for (let i = arr.length - 1; i > 0; i--) {
      const j = (i * 7 + 3) % (i + 1); // deterministic
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  }, [exercise]);

  const [selectedLeft, setSelectedLeft] = useState(null);
  const [pairs, setPairs] = useState({}); // leftIdx -> rightIdx (origIdx)
  const [submitted, setSubmitted] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);

  const dotColors = ["#C745CC", "#1F7A4C", "#C9A227", "#4A5AE0", "#B3322B", "#0EA5B7"];

  const pickLeft = (i) => {
    if (submitted) return;
    setSelectedLeft((s) => s === i ? null : i);
  };
  const pickRight = (origIdx) => {
    if (submitted || selectedLeft == null) return;
    // remove any existing pairing involving this right
    setPairs((p) => {
      const n = { ...p };
      Object.keys(n).forEach((k) => {if (n[k] === origIdx) delete n[k];});
      n[selectedLeft] = origIdx;
      return n;
    });
    setSelectedLeft(null);
  };

  const allPaired = Object.keys(pairs).length === exercise.pairs.length;
  const check = () => {
    const ok = exercise.pairs.every((_, i) => pairs[i] === i);
    setIsCorrect(ok);
    setSubmitted(true);
    onResult(ok);
  };

  const pairedRights = new Set(Object.values(pairs));

  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} />
          <div className="ex-q">{exercise.q}</div>
          <div className="dd-hint">{exercise.hint}</div>
          <div className="match-stage">
            <div className="match-col">
              {exercise.pairs.map((p, i) => {
                const dotIdx = pairs[i];
                let stateCls = "";
                if (submitted) stateCls = pairs[i] === i ? " correct" : " wrong";else
                if (selectedLeft === i) stateCls = " selected";else
                if (dotIdx != null) stateCls = " paired";
                return (
                  <button key={i} className={"match-row" + stateCls} onClick={() => pickLeft(i)} disabled={submitted}>
                    {dotIdx != null && <span className="pair-dot" style={{ background: dotColors[i % dotColors.length] }}></span>}
                    <span>{p.left}</span>
                  </button>);

              })}
            </div>
            <div className="match-col">
              {rightOrder.map((r) => {
                const leftIdx = Object.keys(pairs).find((k) => pairs[k] === r.origIdx);
                let stateCls = "";
                if (submitted) {
                  if (leftIdx != null) stateCls = Number(leftIdx) === r.origIdx ? " correct" : " wrong";
                } else if (pairedRights.has(r.origIdx)) stateCls = " paired";
                return (
                  <button key={r.origIdx} className={"match-row" + stateCls}
                  onClick={() => pickRight(r.origIdx)}
                  disabled={submitted || selectedLeft == null}>
                    {leftIdx != null && <span className="pair-dot" style={{ background: dotColors[Number(leftIdx) % dotColors.length] }}></span>}
                    <span>{r.right}</span>
                  </button>);

              })}
            </div>
          </div>
          {!submitted &&
          <div className="ex-submit">
              <button className={"btn-pill" + (allPaired ? "" : " disabled")} disabled={!allPaired} onClick={check}>
                Controleren {I.arrowRight}
              </button>
            </div>
          }
          {submitted && <ExFeedback isCorrect={isCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {submitted &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── EXERCISE: FLASHCARD (raad → draai om) ──────────── */
function ExFlashcard({ exercise, exIdx, exTotal, onResult, onNext }) {
  const cards = exercise.cards || [];
  const options = useMemo(() => {
    const arr = cards.map((c) => c.back);
    for (let i = arr.length - 1; i > 0; i--) {
      const j = (i * 7 + 3) % (i + 1);
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  }, [exercise]);
  const [idx, setIdx] = useState(0);
  const [picks, setPicks] = useState({});
  const [done, setDone] = useState(false);
  const cur = cards[idx];
  const picked = picks[idx];
  const answered = picked != null;
  const correct = answered && picked === cur.back;
  const allCorrect = cards.every((c, i) => picks[i] === c.back);
  const pick = (term) => {
    if (answered) return;
    setPicks((p) => ({ ...p, [idx]: term }));
  };
  const next = () => {
    if (idx < cards.length - 1) {setIdx(idx + 1);return;}
    setDone(true);
    onResult(cards.every((c, i) => picks[i] === c.back));
  };
  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} />
          <div className="ex-q">{exercise.q}</div>
          <div className="dd-hint">{exercise.hint}</div>
          {!done &&
          <>
            <div className="flash-progress">Kaart {idx + 1} / {cards.length}</div>
            <div className={"flash-card" + (answered ? " is-flipped " + (correct ? "is-right" : "is-wrong") : "")}>
              <div className="flash-inner">
                <div className="flash-face flash-front">
                  <span className="flash-front-label">Welke AI-misstap is dit?</span>
                  <span className="flash-situatie">{cur.front}</span>
                </div>
                <div className="flash-face flash-back">
                  <span className="flash-verdict">{correct ? "Juist" : "Niet juist"}</span>
                  <span className="flash-term">{cur.back}</span>
                  {!correct && <span className="flash-yourpick">Jouw keuze: {picked}</span>}
                  {cur.note && <span className="flash-note">{cur.note}</span>}
                </div>
              </div>
            </div>
            {!answered ?
            <div className="flash-options">
                {options.map((opt, oi) =>
              <button key={oi} className="flash-opt" onClick={() => pick(opt)}>{opt}</button>
              )}
              </div> :

            <div className="ex-submit">
                <button className="btn-pill" onClick={next}>
                  {idx < cards.length - 1 ? "Volgende kaart" : "Bekijk resultaat"} {I.arrowRight}
                </button>
              </div>
            }
          </>
          }
          {done && <ExFeedback isCorrect={allCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {done &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── EXERCISE: ZOEK DE FOUT (klik de foute zinnen) ──────────── */
function ExFindError({ exercise, exIdx, exTotal, onResult, onNext }) {
  const sentences = exercise.sentences || [];
  const [selected, setSelected] = useState(() => new Set());
  const [submitted, setSubmitted] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);
  const toggle = (i) => {
    if (submitted) return;
    setSelected((prev) => {
      const n = new Set(prev);
      if (n.has(i)) n.delete(i);else n.add(i);
      return n;
    });
  };
  const check = () => {
    const errors = sentences.map((s, i) => s.error ? i : -1).filter((i) => i >= 0);
    const ok = errors.length === selected.size && errors.every((i) => selected.has(i));
    setIsCorrect(ok);
    setSubmitted(true);
    onResult(ok);
  };
  const timer = useExerciseTimer(exercise, submitted, () => check());
  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} timer={timer} />
          <div className="ex-q">{exercise.q}</div>
          <div className="dd-hint">{exercise.hint}</div>
          {exercise.prompt &&
          <div className="fe-prompt"><span className="fe-prompt-label">Prompt</span>{exercise.prompt}</div>}
          <div className="fe-answer">
            {exercise.answerLabel && <div className="fe-answer-label">{exercise.answerLabel}</div>}
            <div className="fe-sentences">
              {sentences.map((s, i) => {
                const sel = selected.has(i);
                let cls = "fe-sentence";
                if (submitted) {
                  if (s.error) cls += " is-error";else
                  if (sel) cls += " is-falsepos";
                } else if (sel) cls += " selected";
                return (
                  <button key={i} className={cls} onClick={() => toggle(i)} disabled={submitted}>
                    <span className="fe-text">{s.text}</span>
                    {submitted && s.error && <span className="fe-tag">{s.type}</span>}
                  </button>);

              })}
            </div>
          </div>
          {!submitted &&
          <div className="ex-submit">
              <button className={"btn-pill" + (selected.size ? "" : " disabled")} disabled={!selected.size} onClick={check}>
                Controleren {I.arrowRight}
              </button>
            </div>
          }
          {submitted && <ExFeedback isCorrect={isCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {submitted &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── EXERCISE: ORDER ──────────── */
function ExOrder({ exercise, exIdx, exTotal, onResult, onNext }) {
  // start order = reversed of answer (or shuffled deterministically)
  const initial = useMemo(() => {
    const arr = [...exercise.items];
    // simple deterministic reorder: reverse
    return arr.reverse();
  }, [exercise]);

  const [list, setList] = useState(initial);
  const [submitted, setSubmitted] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);

  const move = (idx, delta) => {
    if (submitted) return;
    const target = idx + delta;
    if (target < 0 || target >= list.length) return;
    const next = [...list];
    [next[idx], next[target]] = [next[target], next[idx]];
    setList(next);
  };

  const check = () => {
    const ok = list.every((it, i) => it.id === exercise.answer[i]);
    setIsCorrect(ok);
    setSubmitted(true);
    onResult(ok);
  };

  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} />
          <div className="ex-q">{exercise.q}</div>
          <div className="dd-hint">{exercise.hint}</div>
          <div className="order-list">
            {list.map((it, i) => {
              let cls = "";
              if (submitted) cls = it.id === exercise.answer[i] ? " correct" : " wrong";
              return (
                <div key={it.id} className={"order-row" + cls}>
                  <div className="order-num">{i + 1}</div>
                  <div>{it.label}</div>
                  <div className="order-arrows">
                    <button className="order-arrow" onClick={() => move(i, -1)} disabled={submitted || i === 0} aria-label="omhoog">{I.up}</button>
                    <button className="order-arrow" onClick={() => move(i, +1)} disabled={submitted || i === list.length - 1} aria-label="omlaag">{I.down}</button>
                  </div>
                </div>);

            })}
          </div>
          {!submitted &&
          <div className="ex-submit">
              <button className="btn-pill" onClick={check}>Controleren {I.arrowRight}</button>
            </div>
          }
          {submitted && <ExFeedback isCorrect={isCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {submitted &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── EXERCISE: HOTSPOT (klik AI-fouten aan) ──────────── */
function ExHotspot({ exercise, exIdx, exTotal, onResult, onNext }) {
  const [clicks, setClicks] = useState([]); // {x, y, hotspotIdx | null}
  const [submitted, setSubmitted] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);
  const imgRef = React.useRef(null);

  const TOLERANCE_PCT = 8; // click within 8% of hotspot center counts

  const handleClick = (e) => {
    if (submitted) return;
    if (clicks.length >= (exercise.maxClicks || exercise.hotspots.length + 1)) return;
    const rect = imgRef.current.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width * 100;
    const y = (e.clientY - rect.top) / rect.height * 100;
    // Did we hit a not-yet-found hotspot?
    const foundIdxs = new Set(clicks.filter((c) => c.hotspotIdx != null).map((c) => c.hotspotIdx));
    let hit = null;
    exercise.hotspots.forEach((h, i) => {
      if (foundIdxs.has(i)) return;
      const dx = x - h.x,dy = y - h.y;
      if (Math.sqrt(dx * dx + dy * dy) <= TOLERANCE_PCT) hit = i;
    });
    setClicks((cs) => [...cs, { x, y, hotspotIdx: hit }]);
  };

  const foundCount = clicks.filter((c) => c.hotspotIdx != null).length;
  const totalHotspots = exercise.hotspots.length;

  const check = () => {
    if (submitted) return;
    const ok = foundCount >= (exercise.minToWin || totalHotspots);
    setIsCorrect(ok);
    setSubmitted(true);
    onResult(ok);
  };
  const timer = useExerciseTimer(exercise, submitted, () => check());

  return (
    <>
      <div className="canvas">
        <div className="ex-frame">
          <ExHead exercise={exercise} exIdx={exIdx} exTotal={exTotal} timer={timer} />
          <div className="ex-q">{exercise.q}</div>
          <div className="dd-hint">{exercise.hint}</div>
          <div className="hs-counter">
            Gevonden: <b>{foundCount} / {totalHotspots}</b>
          </div>
          <div className="hs-frame">
            <div className={"hs-stage" + (submitted ? " submitted" : "")}>
              <img ref={imgRef} src={exercise.image} alt="" onClick={handleClick} draggable="false" />
              {clicks.map((c, i) => {
                if (c.hotspotIdx != null) {
                  // correct click
                  const h = exercise.hotspots[c.hotspotIdx];
                  return (
                    <div key={i} className="hs-marker correct" style={{ left: h.x + "%", top: h.y + "%" }}>
                      ✓
                      {submitted && <span className="hs-label">{h.label}</span>}
                    </div>);

                }
                if (submitted) {
                  return (
                    <div key={i} className="hs-marker click-miss" style={{ left: c.x + "%", top: c.y + "%" }}>✕</div>);

                }
                // pre-submit miss-click: just a dim ring
                return (
                  <div key={i} className="hs-marker" style={{ left: c.x + "%", top: c.y + "%", opacity: 0.4 }}>?</div>);

              })}
              {submitted && exercise.hotspots.map((h, i) => {
                const foundIdxs = new Set(clicks.filter((c) => c.hotspotIdx != null).map((c) => c.hotspotIdx));
                if (foundIdxs.has(i)) return null;
                return (
                  <div key={"missed-" + i} className="hs-marker missed" style={{ left: h.x + "%", top: h.y + "%" }}>
                    !
                    <span className="hs-label">{h.label}</span>
                  </div>);

              })}
            </div>
          </div>
          {!submitted &&
          <div className="ex-submit">
              <button className={"btn-pill" + (foundCount > 0 ? "" : " disabled")} disabled={foundCount === 0} onClick={check}>
                {foundCount >= totalHotspots ? "Klaar om te controleren" : `Inleveren (${foundCount}/${totalHotspots})`} {I.arrowRight}
              </button>
            </div>
          }
          {submitted && <ExFeedback isCorrect={isCorrect} explain={exercise.explain} />}
        </div>
      </div>
      {submitted &&
      <div className="foot">
          <span className="foot-pager">Oefening {exIdx + 1} / {exTotal}</span>
          <button className="btn-pill" onClick={onNext}>
            {exIdx < exTotal - 1 ? "Volgende oefening" : "Resultaat module"} {I.arrowRight}
          </button>
        </div>
      }
    </>);

}

/* ──────────── SCREENS ──────────── */
function ScreenWelkom({ onNext }) {
  const vendor = C.branding && C.branding.vendor;
  const eyebrow = C.overview && C.overview.eyebrow || C.branding.appName + " · " + C.branding.moduleName;
  return <>
    <div className="canvas">
      <div className="welkom">
        <div className="hero-card">
          <NeuralMesh reward={true} />
          <div className="hero-grain" aria-hidden="true"></div>
          <div className="hero-head">
            <div className="hero-head-text">
              <div className="hero-eyebrow">
                <span className="hero-eyebrow-dot" aria-hidden="true"></span>
                {eyebrow}
              </div>
              <h1 className="hero-headline">{C.welcome.title}</h1>
            </div>
            {C.branding.heroLogo &&
            <img className="hero-logo" src={C.branding.heroLogo} alt={C.branding.brand && C.branding.brand.logoAlt || C.branding.appName} />}
          </div>
          <p className="hero-body">{C.welcome.body}</p>
          <div className="hero-cta">
            <span className="hero-cta-pill">{C.welcome.cta || "Bekijk de cursus"}</span>
            <button className="hero-cta-circle" onClick={onNext} aria-label={C.welcome.cta || "Bekijk de cursus"}>{I.arrowRight}</button>
          </div>
          <div className="hero-foot-bar">
            <div className="hero-progress"><div></div></div>
            <span className="hero-pager">{C.welcome.pager}</span>
          </div>
        </div>
      </div>
    </div>
    <div className="foot">
      {vendor ?
      <a className="powered-by" href={vendor.url || "#"} target="_blank" rel="noopener noreferrer">
        <span>Powered by</span>
        <img src={vendor.logo} alt={vendor.name} />
      </a> :
      <span className="foot-left">{C.welcome.estimate}</span>}
      <button className="btn-pill" onClick={onNext}>Ga verder {I.arrowRight}</button>
    </div>
  </>;
}

function ScreenTips({ onNext, onBack, onOpen }) {
  const notice = C.welcome && C.welcome.demoNotice;
  const [showNotice, setShowNotice] = useState(true);
  return <>
    <div className="canvas">
      <div className="tips-screen">
        <div className="speech-row">
          <div className="speech-mascot"><Mascot size={84} /></div>
          <div className="speech-bubble">{C.tips.speech}</div>
        </div>
        <div className="tip-grid">
          {TIPS.map((t, i) => {
            const clickable = !!t.action;
            return (
              <div className={"tip-card" + (clickable ? " tip-link" : "")} key={i}
              role={clickable ? "button" : undefined}
              tabIndex={clickable ? 0 : undefined}
              onClick={clickable ? () => onOpen(t.action) : undefined}
              onKeyDown={clickable ? (e) => {if (e.key === "Enter" || e.key === " ") {e.preventDefault();onOpen(t.action);}} : undefined}>
              <div className="tip-icon">{TIP_ICONS[t.icon]}</div>
              <div className="tip-body">
                <div className="tip-title">{t.title}</div>
                <div className="tip-desc">{t.desc}</div>
                {clickable && t.cta && <div className="tip-cta">{t.cta} {I.arrowRight}</div>}
              </div>
            </div>);
          })}
        </div>
        {notice && showNotice &&
        <div className="demo-notice demo-notice--block" role="note">
          <span className="demo-notice-badge">{notice.badge || "Demo"}</span>
          <p className="demo-notice-text"><strong>{notice.title}</strong> {notice.body}</p>
          <button className="demo-notice-close" aria-label="Melding sluiten" onClick={() => setShowNotice(false)}>×</button>
        </div>}
      </div>
    </div>
    <div className="foot">
      <button className="btn-ghost" onClick={onBack}>{I.arrowLeft} Vorige</button>
      <button className="btn-pill" onClick={onNext}>Naar de modules {I.arrowRight}</button>
    </div>
  </>;
}

/* Profielscherm — toont verdiende Sparks (per module) */
function ScreenProfile({ sparks, maxSparks, moduleSparks, progress, onBack }) {
  const lessons = MODULES.map((m, i) => ({ m, i })).filter((x) => x.m.kind === "lesson" && !x.m.locked);
  const pct = maxSparks === 0 ? 0 : Math.round(sparks / maxSparks * 100);
  return <>
    <div className="canvas">
      <div className="profile">
        <div className="profile-head">
          <span className="profile-avatar">{C.branding.userInitials}</span>
          <div>
            <div className="profile-eyebrow">Jouw profiel</div>
            <h2 className="profile-name">{C.branding.userName}</h2>
          </div>
        </div>
        <div className="profile-spark-card">
          <div className="profile-spark-num"><span className="ps-icon">⚡</span>{sparks}</div>
          <div className="profile-spark-label">Sparks verdiend{maxSparks ? ` van ${maxSparks}` : ""}</div>
          <div className="profile-spark-bar"><div style={{ width: `${pct}%` }}></div></div>
        </div>
        <div className="profile-breakdown">
          <div className="pb-title">Per module</div>
          {lessons.map(({ m, i }) =>
          <div className="pb-row" key={m.id}>
            <span className="pb-mod">{m.title}</span>
            <span className={"pb-val" + (progress[i] === "done" ? " done" : "")}>
              {progress[i] === "done" ? <>{moduleSparks[i]} <span className="ps-icon">⚡</span></> : "nog niet"}
            </span>
          </div>
          )}
        </div>
      </div>
    </div>
    <div className="foot">
      <button className="btn-ghost" onClick={onBack}>{I.arrowLeft} Terug</button>
    </div>
  </>;
}

function ScreenOverview({ progress, sparks, onPickModule, onBack }) {
  const doneCount = progress.filter((p) => p === "done").length;
  const nextIdx = MODULES.findIndex((m, i) => !m.locked && progress[i] !== "done");
  const donePlayable = MODULES.filter((m, i) => !m.locked && progress[i] === "done").length;
  const playableCount = MODULES.filter((m) => !m.locked).length;
  const allPlayableDone = nextIdx === -1;
  return <>
    <div className="canvas">
      <div className="overview">
        <DotSymbol name="spark" className="deco-dots lg bright-fill faint" style={{ top: -40, right: -80 }} />
        <DotSymbol name="prompt" className="deco-dots sm dark-fill dim" style={{ bottom: 20, left: -30 }} />
        <div className="overview-head">
          <div className="eyebrow">{C.overview.eyebrow}</div>
          <h2>{C.overview.title}</h2>
          <div className="overview-progress">
            <span className="pn">{doneCount}/{MODULES.length}</span>
            <div className="bar"><div style={{ width: `${doneCount / MODULES.length * 100}%` }} /></div>
          </div>
        </div>
        <div className="mod-list">
          {MODULES.map((m, i) => {
            const locked = !!m.locked;
            const status = locked ? "locked" : progress[i] === "done" ? "done" : i === nextIdx ? "active" : "todo";
            return (
              <div className={"mod-row " + status} key={m.id}
              onClick={locked ? undefined : () => onPickModule(i)}
              role={locked ? undefined : "button"}
              aria-disabled={locked ? "true" : undefined}>
                <div className="mod-num">{locked ? I.lock : progress[i] === "done" ? I.check : i + 1}</div>
                <div>
                  <div className="mod-title">{m.title}</div>
                  <div className="mod-meta">{m.meta}</div>
                </div>
                {locked ?
                <span className="mod-lock-tag">Niet in demo</span> :
                <span style={{ color: "var(--bright)", opacity: 0.5 }}>{I.chev}</span>}
              </div>);

          })}
        </div>
      </div>
    </div>
    <div className="foot">
      <button className="btn-ghost" onClick={onBack}>{I.arrowLeft} Vorige</button>
      <button className="btn-pill" onClick={() => onPickModule(allPlayableDone ? -1 : nextIdx)}>
        {donePlayable === 0 ? "Module 1 starten" : allPlayableDone ? "Resultaat bekijken" : "Doorgaan"} {I.arrowRight}
      </button>
    </div>
  </>;
}

function ScreenVideo({ module, onNext, onBack }) {
  const [played, setPlayed] = useState(false);
  const videoRef = React.useRef(null);
  // stop de video (en dus het geluid) zodra je het videoscherm verlaat
  useEffect(() => () => {
    const v = videoRef.current;
    if (v) {try {v.pause();v.removeAttribute("src");v.load();} catch (e) {}}
  }, []);
  return <>
    <div className="canvas">
      <div className="video-screen">
        <div className="video-frame">
          {module.video ?
          <video ref={videoRef} src={module.video} controls playsInline preload="metadata" onPlay={() => setPlayed(true)} /> :

          <div className="video-play-btn" onClick={() => setPlayed(true)}>
              <div className="circle">{I.play}</div>
            </div>
          }
        </div>
        <div className="video-info">
          <h2>{module.videoTitle}<span className="info-icon">{I.info}</span></h2>
          <p>{module.videoBody}</p>
        </div>
      </div>
    </div>
    <div className="foot">
      <button className="btn-ghost" onClick={onBack}>{I.arrowLeft} Terug naar overzicht</button>
      <button className={"btn-pill" + (played ? "" : " disabled")} disabled={!played} onClick={onNext}>
        Naar de oefeningen {I.arrowRight}
      </button>
    </div>
  </>;
}

function ScreenReward({ module, sparksEarned, totalEx, sparks, onNext }) {
  const isAllCorrect = sparksEarned === totalEx && totalEx > 0;
  const sparkPts = useMemo(() => Array.from({ length: sparksEarned > 0 ? isAllCorrect ? 40 : 22 : 0 }, (_, i) => ({
    dx: (Math.random() - 0.5) * (isAllCorrect ? 800 : 500),
    dy: -120 - Math.random() * 240,
    delay: i * 0.04
  })), [sparksEarned, isAllCorrect]);
  const heading = isAllCorrect ? "Geweldig!" : sparksEarned > 0 ? "Goed bezig." : "Goed geprobeerd.";
  useEffect(() => {if (sparksEarned > 0) SFX.reward(sparksEarned);}, []);
  return <>
    <div className="canvas">
      <div className={"reward-screen" + (isAllCorrect ? " all-correct" : "")}>
        {sparksEarned > 0 && <Confetti count={isAllCorrect ? 140 : 60} />}
        <RewardHalo enabled={sparksEarned > 0} />
        <div className="reward-check">{I.check}</div>
        <h2 className="reward-h">{heading}</h2>
        <p className="reward-sub">
          Module {module.id} voltooid. {sparksEarned} van de {totalEx} oefeningen correct.
        </p>
        {sparksEarned > 0 &&
        <div className="reward-spark-pill">
          <span className="pill-spark-icon">⚡</span>
          +{sparksEarned} {sparksEarned === 1 ? "Spark" : "Sparks"} verdiend
        </div>
        }
        {sparkPts.map((s, i) =>
        <div className="spark-burst" key={i}
        style={{ left: "50%", top: "44%", "--dx": `${s.dx}px`, "--dy": `${s.dy}px`, animationDelay: `${s.delay}s` }}></div>
        )}
      </div>
    </div>
    <div className="foot">
      <span className="foot-pager" style={{ color: "rgba(255,255,255,0.55)" }}>Totaal: {sparks} {sparks === 1 ? "Spark" : "Sparks"}</span>
      <button className="btn-pill" onClick={onNext}>Volgende module {I.arrowRight}</button>
    </div>
  </>;
}

function ScreenPoll({ module, answers, onAnswer, onSubmit, onBack }) {
  const allAnswered = module.poll.every((_, i) => answers[i] != null);
  return <>
    <div className="canvas">
      <div className="poll-screen">
        <div className="poll-intro">
          <span className="badge">Anoniem · geen goed of fout</span>
          <h2>{module.title}</h2>
          <p>{module.pollIntro}</p>
        </div>
        {module.poll.map((q, i) =>
        <div className="poll-q" key={i}>
            <div className="q-num">Vraag {i + 1} van {module.poll.length}</div>
            <div className="q-text">{q.q}</div>
            <div className="poll-options">
              {q.options.map((opt, j) =>
            <button key={j} className={"poll-opt " + (answers[i] === j ? "picked" : "")}
            onClick={() => onAnswer(i, j)}>{opt}</button>
            )}
            </div>
          </div>
        )}
      </div>
    </div>
    <div className="foot">
      <button className="btn-ghost" onClick={onBack}>{I.arrowLeft} Terug naar overzicht</button>
      <button className={"btn-pill" + (allAnswered ? "" : " disabled")} disabled={!allAnswered} onClick={onSubmit}>
        Antwoorden indienen {I.arrowRight}
      </button>
    </div>
  </>;
}

function ScreenPollThanks({ onNext }) {
  return <>
    <div className="canvas">
      <div className="reward-screen">
        <div className="reward-check">{I.checkBig}</div>
        <h2 className="reward-h">{C.poll && C.poll.thanks && C.poll.thanks.title || "Bedankt."}</h2>
        <p className="reward-sub">{C.poll && C.poll.thanks && C.poll.thanks.body || "Je antwoorden stromen anoniem door naar het juiste team."}</p>
      </div>
    </div>
    <div className="foot">
      <span className="foot-pager" style={{ color: "rgba(255,255,255,0.55)" }}>Poll-modules leveren geen Sparks op</span>
      <button className="btn-pill" onClick={onNext}>Volgende module {I.arrowRight}</button>
    </div>
  </>;
}

function ScreenCompletion({ sparks, maxSparks, onRestart }) {
  const pct = maxSparks === 0 ? 0 : Math.round(sparks / maxSparks * 100);
  return <>
    <div className="canvas">
      <div className="completion">
        <DotSymbol name="neural" className="deco-dots lg reward-fill" style={{ top: 40, left: 40 }} />
        <DotSymbol name="spark" className="deco-dots md reward-fill" style={{ top: 80, right: 80 }} />
        <DotSymbol name="chip" className="deco-dots md reward-fill" style={{ bottom: 120, left: 80 }} />
        <DotSymbol name="chat" className="deco-dots md reward-fill" style={{ bottom: 60, right: 120 }} />
        <Confetti count={90} />
        <div className="trophy">{I.trophy}</div>
        <h1>{C.completion.title}</h1>
        <p className="sub">{C.completion.body}</p>
        <div className="score-grid">
          <div className="score-cell correct"><div className="num">{sparks}/{maxSparks}</div><div className="lbl">correct</div></div>
          <div className="score-cell spark"><div className="num">{sparks}</div><div className="lbl">Sparks</div></div>
          <div className="score-cell pct"><div className="num">{pct}%</div><div className="lbl">score</div></div>
        </div>
      </div>
    </div>
    <div className="foot">
      <button className="btn-ghost" onClick={onRestart}>Pad opnieuw starten</button>
      <button className="btn-pill">{C.completion.cta || "Terug naar het LMS"} {I.arrowRight}</button>
    </div>
  </>;
}

/* ──────────── APP ──────────── */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "energy": "standard",
  "bg": "bloom",
  "mascotAnim": true,
  "rewardAnim": true,
  "confetti": true,
  "sound": true
} /*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [screen, setScreen] = useState("welkom");
  const [moduleIdx, setModuleIdx] = useState(0);
  const [phase, setPhase] = useState("video"); // lesson: video | ex (with exIdx) | reward | poll: poll | thanks
  const [exIdx, setExIdx] = useState(0);
  const [progress, setProgress] = useState(Array(MODULES.length).fill("todo"));
  const [moduleSparks, setModuleSparks] = useState(Array(MODULES.length).fill(0)); // sparks earned per module
  const [currentModuleSparks, setCurrentModuleSparks] = useState(0); // accumulator during module
  const [pollAnswers, setPollAnswers] = useState({});
  const [profileFrom, setProfileFrom] = useState("tips");
  const goProfile = () => {setProfileFrom(screen);setScreen("profile");};

  const sparks = moduleSparks.reduce((a, b) => a + b, 0);
  const maxSparks = MODULES.filter((m) => m.kind === "lesson" && !m.locked).reduce((a, m) => a + m.exercises.length, 0);

  const currentModule = MODULES[moduleIdx];

  const overallProgress = useMemo(() => {
    if (screen === "welkom" || screen === "tips") return 0;
    if (screen === "overview") return progress.filter((p) => p === "done").length / MODULES.length * 100;
    if (screen === "completion") return 100;
    const baseDone = progress.filter((p) => p === "done").length;
    let sub = 0;
    if (phase === "video" || phase === "poll") sub = 0.2;else
    if (phase === "ex") sub = 0.4 + 0.4 * (exIdx / (currentModule.exercises?.length || 1));else
    sub = 1; // reward/thanks
    return Math.min(100, (baseDone + sub) / MODULES.length * 100);
  }, [screen, phase, exIdx, progress, currentModule]);

  const goPickModule = (i) => {
    if (i === -1) {
      const allPlayableDone = MODULES.every((m, idx) => m.locked || progress[idx] === "done");
      if (allPlayableDone) setScreen("completion");
      return;
    }
    if (MODULES[i].locked) return; // vergrendeld in de demo
    setModuleIdx(i);
    setExIdx(0);
    setCurrentModuleSparks(0);
    const mod = MODULES[i];
    setPhase(mod.kind === "lesson" ? mod.noVideo ? "ex" : "video" : "poll");
    setScreen("module");
  };

  const finishModule = (sparksEarned) => {
    setProgress((prev) => {const n = [...prev];n[moduleIdx] = "done";return n;});
    setModuleSparks((prev) => {const n = [...prev];n[moduleIdx] = sparksEarned;return n;});
  };

  const nextAfterModule = () => {
    const allPlayableDone = MODULES.every((m, i) => m.locked || i === moduleIdx || progress[i] === "done");
    if (allPlayableDone) setScreen("completion");else
    setScreen("overview");
  };

  const onExerciseResult = (isCorrect) => {
    if (isCorrect) setCurrentModuleSparks((c) => c + 1);
  };
  const onExerciseNext = () => {
    if (exIdx < currentModule.exercises.length - 1) {
      setExIdx(exIdx + 1);
    } else {
      finishModule(currentModuleSparks);
      setPhase("reward");
    }
  };

  const renderModule = () => {
    if (currentModule.kind === "lesson") {
      if (phase === "video") return <ScreenVideo module={currentModule} onBack={() => setScreen("overview")} onNext={() => {setPhase("ex");setExIdx(0);}} />;
      if (phase === "ex") {
        const ex = currentModule.exercises[exIdx];
        const props = { exercise: ex, exIdx, exTotal: currentModule.exercises.length, onResult: onExerciseResult, onNext: onExerciseNext, key: `${moduleIdx}-${exIdx}` };
        if (ex.type === "quiz") return <ExQuiz {...props} />;
        if (ex.type === "blank") return <ExBlank {...props} />;
        if (ex.type === "dragdrop") return <ExDragDrop {...props} />;
        if (ex.type === "match") return <ExMatch {...props} />;
        if (ex.type === "flashcard") return <ExFlashcard {...props} />;
        if (ex.type === "finderror") return <ExFindError {...props} />;
        if (ex.type === "order") return <ExOrder {...props} />;
        if (ex.type === "hotspot") return <ExHotspot {...props} />;
      }
      if (phase === "reward") {
        return <ScreenReward module={currentModule} sparksEarned={moduleSparks[moduleIdx]} totalEx={currentModule.exercises.length} sparks={sparks} onNext={nextAfterModule} />;
      }
    } else {
      if (phase === "poll") return <ScreenPoll module={currentModule} answers={pollAnswers}
      onAnswer={(qi, oi) => setPollAnswers((p) => ({ ...p, [qi]: oi }))}
      onBack={() => setScreen("overview")}
      onSubmit={() => {finishModule(0);setPhase("thanks");}} />;
      if (phase === "thanks") return <ScreenPollThanks onNext={nextAfterModule} />;
    }
    return null;
  };

  const renderScreen = () => {
    switch (screen) {
      case "welkom":return <ScreenWelkom onNext={() => setScreen("tips")} />;
      case "tips":return <ScreenTips onNext={() => setScreen("overview")} onBack={() => setScreen("welkom")} onOpen={(a) => a === "profile" ? goProfile() : setScreen("overview")} />;
      case "overview":return <ScreenOverview progress={progress} sparks={sparks} onBack={() => setScreen("tips")} onPickModule={goPickModule} />;
      case "profile":return <ScreenProfile sparks={sparks} maxSparks={maxSparks} moduleSparks={moduleSparks} progress={progress} onBack={() => setScreen(profileFrom)} />;
      case "module":return renderModule();
      case "completion":return <ScreenCompletion sparks={sparks} maxSparks={maxSparks}
        onRestart={() => {setProgress(Array(MODULES.length).fill("todo"));setModuleSparks(Array(MODULES.length).fill(0));setPollAnswers({});setCurrentModuleSparks(0);setScreen("welkom");}} />;
      default:return null;
    }
  };

  const onDarkBg = screen === "completion" || screen === "module" && (phase === "reward" || phase === "thanks");

  // Apply tweak data attributes to :root so CSS can react.
  useEffect(() => {
    const r = document.documentElement;
    r.dataset.energy = t.energy;
    r.dataset.bg = t.bg;
    r.dataset.mascotAnim = t.mascotAnim ? "on" : "off";
    r.dataset.rewardAnim = t.rewardAnim ? "on" : "off";
    r.dataset.confetti = t.confetti ? "on" : "off";
  }, [t.energy, t.bg, t.mascotAnim, t.rewardAnim, t.confetti]);
  useEffect(() => {SFX.setEnabled(t.sound);}, [t.sound]);
  const screenLabel = screen === "module" ?
  `Module ${currentModule.id} · ${phase}${phase === "ex" ? " " + (exIdx + 1) : ""}` :
  screen.charAt(0).toUpperCase() + screen.slice(1);
  const currentModuleTitle = screen === "module" ? `Module ${currentModule.id} · ${currentModule.title}` : null;

  return (
    <div className={"desktop-app" + (onDarkBg ? " reward-bg" : "")} data-screen-label={screenLabel}>
      <BgDeco variant={t.bg} reward={onDarkBg} />
      <TopBar
        progress={overallProgress}
        score={sparks}
        flash={screen === "module" && phase === "reward" && moduleSparks[moduleIdx] > 0}
        currentModuleTitle={currentModuleTitle}
        onProfile={goProfile} />
      
      <div className="content" key={screen + ":" + phase + ":" + exIdx}>
        {renderScreen()}
      </div>

      <TweaksPanel title="Tweaks">
        <TweakSection label="Energie" />
        <TweakRadio
          label="Niveau"
          value={t.energy}
          options={["calm", "standard", "playful"]}
          optionLabels={["Rustig", "Standaard", "Speels"]}
          onChange={(v) => setTweak("energy", v)} />
        
        <TweakSection label="Achtergrond" />
        <TweakRadio
          label="Patroon"
          value={t.bg}
          options={["none", "dots", "bloom"]}
          optionLabels={["Geen", "Stippen", "Bloom"]}
          onChange={(v) => setTweak("bg", v)} />
        
        <TweakSection label="Animaties" />
        <TweakToggle
          label="Mascotte beweegt"
          value={t.mascotAnim}
          onChange={(v) => setTweak("mascotAnim", v)} />
        
        <TweakToggle
          label="Reward-effecten"
          value={t.rewardAnim}
          onChange={(v) => setTweak("rewardAnim", v)} />
        
        <TweakToggle
          label="Confetti bij voltooien"
          value={t.confetti}
          onChange={(v) => setTweak("confetti", v)} />
        
        <TweakToggle
          label="Geluid bij beloning"
          value={t.sound}
          onChange={(v) => setTweak("sound", v)} />
        
      </TweaksPanel>
    </div>);

}

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