/* ==========================================================================
   Next Gen — Unified JS Bundle
   (Burger + TOC + Kids Mode + Print + TracePad v4.3 + DragDrop v2 + Quiz v2
    + Autosave v1.1 + Per-lesson Reset + Story Understanding v2 + Mini-Book Pager v3)
   Build: 2025-11-08-9
   ========================================================================== */

/* ========== 1) Global UI (auth toggles + burger + CSRF) ================== */
(function () {
  var authed  = document.body.dataset.auth === 'true';
  var signin  = document.getElementById('nav-signin');
  var account = document.getElementById('nav-account');
  if (signin && account) {
    signin.style.display  = authed ? 'none' : '';
    account.style.display = authed ? '' : 'none';
  }

  var csrf = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
  var logoutInput = document.querySelector('#logout-form input[name="csrf-token"]');
  if (logoutInput) logoutInput.value = csrf;

  // ✅ CLEAN BURGER LOGIC
  var burger = document.querySelector('.burger');
  var menu   = document.getElementById('main-menu'); // same as .nav-links

  function setMenuOpen(isOpen) {
    if (!menu || !burger) return;
    menu.classList.toggle('open', isOpen);
    burger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
  }

  if (burger && menu) {
    burger.addEventListener('click', function () {
      var isOpen = menu.classList.contains('open');
      setMenuOpen(!isOpen);
    });
  }
})();

/* ========== 1a) TOC toggle (Kids Mode removed — always one theme) ============================ */
(function () {
  // TOC toggle (unchanged)
  document.querySelectorAll('.toc-toggle').forEach(function (t) {
    t.addEventListener('click', function () {
      var container = t.closest('.layout');
      var toc = container ? container.querySelector('.toc') : document.querySelector('.toc');
      if (toc) toc.classList.toggle('open');
    });
  });

  // Remove any leftover saved theme in case it exists
  try {
    localStorage.removeItem('ngl-theme');
  } catch (_) {}
  
  // No theme switching, no extra CSS injection.
  // The site permanently uses nextgen.css as the only style source.
})();

/* ========== 3) Tracing Pad v4.3 (Levels 1–3) ============================= */
(function () {

  // Reusable initializer so we can use the pad inside overlays too
  window.nglInitTracePad = function (pad) {
    if (!pad) return;

    var canvas = pad.querySelector('#trace');
    if (!canvas) return;

    var ctx        = canvas.getContext('2d', { alpha: true, desynchronized: true });
    var linePick   = pad.querySelector('#linePick');
    var letterPick = pad.querySelector('#letterPick');
    var sizePick   = pad.querySelector('#sizePick');
    var clearBtn   = pad.querySelector('#clearPad');
    var colorBtns  = Array.prototype.slice.call(pad.querySelectorAll('.color-dot'));

    var drawing = false, lastX = 0, lastY = 0, moved = false, dpr = 1, penColor = '#111';
    canvas.style.touchAction = 'none';

    // Default pen size ≈ 90%
    if (sizePick) {
      var min = +sizePick.min || 2, max = +sizePick.max || 12;
      sizePick.value = Math.round((max - min) * 0.9 + min);
    }

    function setPen() {
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
      ctx.strokeStyle = penColor;
      ctx.lineWidth = sizePick ? (+sizePick.value || 11) : 11;
    }

    function fillWhite(W, H) {
      ctx.save();
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, W, H);
      ctx.restore();
    }

  // ================== GUIDES + GHOST LETTER ==================
  function drawGuides() {
    var W = canvas.width / dpr;
    var H = canvas.height / dpr;

    // Clear background
    fillWhite(W, H);

    var t = linePick && linePick.value;
    var writingTop, writingMid, writingBase;

    if (t === 'grid') {
      // Light grid
      ctx.save();
      ctx.strokeStyle = 'rgba(0,0,0,.08)';
      ctx.lineWidth = 1;
      for (var x = 0; x < W; x += 20) {
        ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, H);
        ctx.stroke();
      }
      for (var y = 0; y < H; y += 20) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(W, y);
        ctx.stroke();
      }
      ctx.restore();

      // Band for ghost letters when in grid mode
      writingTop  = H * 0.28;
      writingMid  = H * 0.50;
      writingBase = H * 0.72;

    } else if (t === 'baseline') {
      // Single baseline, tighter band in the middle
      writingTop  = H * 0.30;
      writingMid  = H * 0.52;
      writingBase = H * 0.74;

      ctx.save();
      ctx.strokeStyle = 'rgba(0,0,0,.20)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(10, writingBase);
      ctx.lineTo(W - 10, writingBase);
      ctx.stroke();
      ctx.restore();

    } else {
      // "primary" — three handwriting lines in a tighter band
      writingTop  = H * 0.30;
      writingMid  = H * 0.52;
      writingBase = H * 0.74;

      ctx.save();
      ctx.strokeStyle = 'rgba(0,0,0,.14)';
      ctx.lineWidth = 2;
      [writingTop, writingMid, writingBase].forEach(function (y) {
        ctx.beginPath();
        ctx.moveTo(10, y);
        ctx.lineTo(W - 10, y);
        ctx.stroke();
      });
      ctx.restore();
    }

    // ---------- Ghost letter / word aligned to the band ----------
    var L = letterPick && letterPick.value;
    if (L) {
      var bandHeight = writingBase - writingTop;

      // Make ghost text clearly tall on all devices
      var fs;
      if (bandHeight < 70) {
        // very small pads (phones) → almost the full band, min 52px
        fs = Math.max(52, bandHeight * 1.05);
      } else {
        // normal / larger pads → very tall letters
        fs = Math.max(72, bandHeight * 1.02);
      }

      var letterCenter = W * 0.5;
      // tiny nudge so ascenders have room but stay big
      var letterBaseY = writingBase - bandHeight * 0.02;

      ctx.save();
      ctx.globalAlpha = 0.18;
      ctx.textBaseline = 'alphabetic';
      ctx.textAlign = 'center';
      ctx.fillStyle = '#000';
      ctx.font =
        '700 ' +
        fs +
        'px "Patrick Hand","Comic Sans MS",cursive,system-ui';

      ctx.fillText(L, letterCenter, letterBaseY);
      ctx.restore();
    }
  }

  // ================== RESIZE ==================
  function resize() {
    var rect = canvas.getBoundingClientRect();
    if (!rect.width || rect.width < 10) return;

    dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 3));

    var cssWidth  = rect.width;
    var cssHeight = cssWidth * (2 / 3); // 3:2 ratio

    // Cap height a bit to avoid huge pad in overlays
    if (cssHeight > 220) {
      cssHeight = 220;
    }

    canvas.width  = Math.round(cssWidth  * dpr);
    canvas.height = Math.round(cssHeight * dpr);

    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(dpr, dpr);

    setPen();
    drawGuides();

    setTimeout(function () {
      document.dispatchEvent(new Event('ngl-tracepad-ready'));
    }, 60);
  }

  // ================== DRAWING HANDLERS ==================
  function getPos(e) {
    var r = canvas.getBoundingClientRect();
    return { x: e.clientX - r.left, y: e.clientY - r.top };
  }

  function start(e) {
    if (e.button === 2) return; // ignore right-click
    setPen();
    drawing = true;
    moved = false;
    try { canvas.setPointerCapture(e.pointerId); } catch (_) {}
    var p = getPos(e); lastX = p.x; lastY = p.y;
    e.preventDefault();
  }

  function move(e) {
    if (!drawing) return;
    var p = getPos(e);
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(p.x, p.y);
    ctx.stroke();
    moved = true;
    lastX = p.x;
    lastY = p.y;
    document.dispatchEvent(
      new CustomEvent('ngl-tracepad-updated', { detail: { throttled: true } })
    );
    e.preventDefault();
  }

  function end() {
    if (!drawing) return;
    drawing = false;
    if (!moved) {
      ctx.save();
      ctx.fillStyle = penColor;
      var r = Math.max(1, (ctx.lineWidth || 4) / 2);
      ctx.beginPath();
      ctx.arc(lastX, lastY, r, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }
    document.dispatchEvent(
      new CustomEvent('ngl-tracepad-updated', { detail: {} })
    );
  }

  // ================== EVENTS & SETUP ==================
  if (sizePick) sizePick.addEventListener('input', setPen, { passive: true });
  if (linePick) linePick.addEventListener('change', function () {
    drawGuides();
    document.dispatchEvent(new Event('ngl-tracepad-updated'));
  }, { passive: true });

  if (letterPick) letterPick.addEventListener('change', function () {
    drawGuides();
    document.dispatchEvent(new Event('ngl-tracepad-updated'));
  }, { passive: true });

  if (clearBtn) clearBtn.addEventListener('click', function () {
    drawGuides();
    document.dispatchEvent(new Event('ngl-tracepad-updated'));
  }, { passive: true });

  colorBtns.forEach(function (btn) {
    btn.addEventListener('click', function () {
      colorBtns.forEach(function (b) { b.setAttribute('aria-checked', 'false'); });
      btn.setAttribute('aria-checked', 'true');
      penColor = btn.dataset.color || '#111';
      setPen();
    }, { passive: true });
  });

  canvas.addEventListener('pointerdown',  start, { passive: false });
  canvas.addEventListener('pointermove',  move,  { passive: false });
  canvas.addEventListener('pointerup',    end,   { passive: false });
  canvas.addEventListener('pointercancel', end,  { passive: false });
  canvas.addEventListener('pointerleave',  end,  { passive: false });

  if ('ResizeObserver' in window) {
    new ResizeObserver(function () {
      requestAnimationFrame(resize);
    }).observe(canvas);
  } else {
    window.addEventListener('resize', function () {
      requestAnimationFrame(resize);
    }, { passive: true });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', function () {
      requestAnimationFrame(resize);
    });
  } else {
    requestAnimationFrame(resize);
  }

  // Ensure handwriting font is applied once webfonts are ready
  if (document.fonts && document.fonts.ready) {
    document.fonts.ready.then(function () {
      drawGuides();
    });
  } else {
    window.addEventListener('load', function () {
      setTimeout(drawGuides, 400);
    });
  }

  // Snapshot helpers
  canvas.__ngl_getPNG = function () {
    try {
      var w = canvas.width, h = canvas.height;
      if (!w || !h) return '';
      var t = document.createElement('canvas'); t.width = w; t.height = h;
      var k = t.getContext('2d');
      k.fillStyle = '#fff'; k.fillRect(0, 0, w, h);
      k.drawImage(canvas, 0, 0);
      return t.toDataURL('image/png');
    } catch (_) { return ''; }
  };

    canvas.__ngl_paintPNG = function (png) {
      if (!png) return;
      var img = new Image();
      img.onload = function () {
        ctx.drawImage(img, 0, 0, canvas.width / dpr, canvas.height / dpr);
      };
      img.src = png;
    };

  }; // end of window.nglInitTracePad

  // Initialize the main page tracing pad
  window.nglInitTracePad(document.querySelector('.trace-pad'));

})();

/* ========== 4) Drag & Drop v2 (touch-friendly, magnet, 3-try reveal) ===== */
(function () {
  var root = document.getElementById('dragdrop');
  if (!root) return;

  var REVEAL_LIMIT = 3;
  var MAGNET_TOLERANCE_PX = 28;

  function sameSentence(a, b) { return a && b && a.closest('.dd-sentence') === b.closest('.dd-sentence'); }

  function ensureSlots() {
    root.querySelectorAll('.dd-sentence').forEach(function (s) {
      var bank = s.querySelector('.dd-bank');
      var targets = s.querySelector('.dd-targets');
      if (!bank || !targets) return;

      // NEW: allow optional override with data-slots (e.g. data-slots="1")
      var override = parseInt(s.dataset.slots || '0', 10);
      var n = override > 0
        ? override
        : bank.querySelectorAll('.dd-chip').length;

      targets.innerHTML = Array.from({ length: n })
        .map(function () { return '<span class="dd-slot"></span>'; })
        .join('');

      if (!s.dataset.tries) s.dataset.tries = '0';
      if (!s.dataset.prev)  s.dataset.prev = '';
      s.dataset.revealed = 'false';
    });
  }
  function shuffleBanks() {
    root.querySelectorAll('.dd-sentence').forEach(function (s) {
      var bank = s.querySelector('.dd-bank'); if (!bank) return;
      var chips = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'));
      for (var i = chips.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var tmp = chips[i]; chips[i] = chips[j]; chips[j] = tmp;
      }
      chips.forEach(function (ch) { bank.appendChild(ch); });
    });
  }
  function moveChipToSlot(chip, slot) {
    if (!chip || !slot) return;
    if (!sameSentence(chip, slot)) return;
    var s = slot.closest('.dd-sentence');
    var bank = s.querySelector('.dd-bank');
    var existing = slot.querySelector('.dd-chip');
    if (existing) bank.appendChild(existing);
    slot.innerHTML = ''; slot.appendChild(chip);
    if (navigator.vibrate) { try { navigator.vibrate(10); } catch (_) {} }
    document.dispatchEvent(new CustomEvent('ngl-dd-updated', { detail: { sentence: s } }));
  }
  function moveChipToBank(chip) {
    if (!chip) return;
    var s = chip.closest('.dd-sentence'); var bank = s.querySelector('.dd-bank');
    bank.appendChild(chip);
    document.dispatchEvent(new CustomEvent('ngl-dd-updated', { detail: { sentence: s } }));
  }
  function getBuilt(sentence) {
    var slots = Array.prototype.slice.call(sentence.querySelectorAll('.dd-slot'));
    var words = slots.map(function (sl) { return (sl.querySelector('.dd-chip') || {}).textContent || ''; });
    return { built: words.join(' ').trim(), filled: words.every(function (w) { return w !== ''; }), words: words, slots: slots };
  }
function updateCorrectHighlights(sentence) {
  var ansRaw = (sentence.dataset.answer || '').trim();
  if (!ansRaw) return;

  var gb    = getBuilt(sentence);
  var slots = gb.slots || [];
  var words = gb.words || [];

  // Clear previous highlights
  slots.forEach(function (sl) {
    sl.classList.remove('correct');
  });

  if (!slots.length) return;

  var slotCount     = slots.length;
  var declaredSlots = parseInt(sentence.dataset.slots || '', 10);

  // NEW: if data-answer-slots is present, use it (pipe-separated per slot)
  var slotAnsRaw = (sentence.dataset.answerSlots || '').trim();
  if (slotAnsRaw) {
    var slotAnswers = slotAnsRaw.split('|').map(function (s) { return s.trim(); });
    slots.forEach(function (sl, i) {
      var w      = (words[i] || '').trim();
      var target = (slotAnswers[i] || '').trim();
      if (w && target && w === target) {
        sl.classList.add('correct');
      }
    });
    return;
  }

  // Case 1: one-box style (bead bars etc.)
  if (slotCount === 1 && declaredSlots === 1) {
    var built = (gb.built || '').replace(/\s+/g, ' ').trim();
    var ans   = ansRaw.replace(/\s+/g, ' ').trim();
    if (built && built === ans) {
      slots[0].classList.add('correct');
    }
    return;
  }

  // Case 2: classic word-by-word for single-word chips
  var ansWords = ansRaw.split(/\s+/);
  slots.forEach(function (sl, i) {
    var w      = words[i] || '';
    var target = ansWords[i] || '';
    if (w && target && w === target) {
      sl.classList.add('correct');
    }
  });
}

  function nearestSlotWithinTolerance(x, y, sentence) {
    var slots = Array.prototype.slice.call(sentence.querySelectorAll('.dd-slot'));
    var best = null, bestD = Infinity;
    for (var i = 0; i < slots.length; i++) {
      var sl = slots[i], r = sl.getBoundingClientRect();
      var cx = r.left + r.width / 2, cy = r.top + r.height / 2;
      var d = Math.hypot(cx - x, cy - y);
      if (d < bestD) { bestD = d; best = sl; }
    }
    return bestD <= MAGNET_TOLERANCE_PX ? best : null;
  }
function fillAnswer(sentence) {
  var slots = sentence.querySelectorAll('.dd-slot');
  var bank  = sentence.querySelector('.dd-bank');
  if (!slots.length || !bank) return;

  // Move any chips back to the bank first
  sentence.querySelectorAll('.dd-slot .dd-chip').forEach(function (ch) {
    bank.appendChild(ch);
  });

  // 1) Preferred path: use data-answer-slots when present (pipe-separated, one value per slot)
  var slotAnsRaw = (sentence.dataset.answerSlots || '').trim(); // data-answer-slots="..."
  if (slotAnsRaw) {
    var slotAnswers = slotAnsRaw.split('|').map(function (s) { return s.trim(); });

    slotAnswers.forEach(function (label, i) {
      if (!label || !slots[i]) return;

      var chip = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'))
        .find(function (c) {
          return (c.textContent || '').trim() === label;
        });

      if (chip) {
        moveChipToSlot(chip, slots[i]);
      }
    });

    updateCorrectHighlights(sentence);
    return;
  }

  // 2) Fallback: original word-by-word behavior (for Lessons 1–16)
  var ansTokens = (sentence.dataset.answer || '').trim().split(/\s+/);

  ansTokens.forEach(function (token, i) {
    if (!token || !slots[i]) return;

    var chip = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'))
      .find(function (c) {
        return (c.textContent || '').trim() === token;
      });

    if (chip) {
      moveChipToSlot(chip, slots[i]);
    }
  });

  updateCorrectHighlights(sentence);
}
  // Init
  ensureSlots(); shuffleBanks();
  root.querySelectorAll('.dd-chip').forEach(function (ch) {
    ch.removeAttribute('draggable');
    ch.style.touchAction = 'none';
  });
  root.addEventListener('dragstart', function (e) {
    if (e.target && e.target.closest('.dd-chip')) e.preventDefault();
  });

  var draggingChip = null, dragGhost = null, dragOffsetX = 0, dragOffsetY = 0, lastPoint = { x: 0, y: 0 }, hoverSlot = null;

  function makeGhost(fromEl) {
    var g = document.createElement('div');
    g.className = 'drag-ghost';
    g.textContent = fromEl.textContent;
    var cs = getComputedStyle(fromEl);
    g.style.position = 'fixed'; g.style.left = '0'; g.style.top = '0'; g.style.pointerEvents = 'none';
    g.style.padding = cs.padding || '10px 14px';
    g.style.border = '1px solid rgba(0,0,0,.2)';
    g.style.borderRadius = '999px'; g.style.background = '#fff';
    g.style.boxShadow = '0 8px 20px rgba(0,0,0,.22)';
    g.style.transform = 'translate(-9999px,-9999px)';
    g.style.zIndex = '9999';
    g.style.font = cs.font;
    document.body.appendChild(g);
    return g;
  }
  function setGhostPos(x, y) {
    if (!dragGhost) return;
    dragGhost.style.transform =
      'translate(' + Math.round(x - dragOffsetX) + 'px,' + Math.round(y - dragOffsetY) + 'px)';
  }
  function elementFromPointSafe(x, y) {
    var prev = '';
    if (dragGhost) { prev = dragGhost.style.display; dragGhost.style.display = 'none'; }
    var el = document.elementFromPoint(x, y);
    if (dragGhost) dragGhost.style.display = prev || '';
    return el;
  }
  function slotUnderPoint(x, y) {
    var el = elementFromPointSafe(x, y);
    var slot = (el && el.closest && el.closest('.dd-slot')) ||
      (el && el.closest && el.closest('.dd-chip') && el.closest('.dd-chip').closest('.dd-slot'));
    return slot && sameSentence(slot, draggingChip) ? slot : null;
  }
  function bankUnderPoint(x, y) {
    var el = elementFromPointSafe(x, y);
    var bank = el && el.closest && el.closest('.dd-bank');
    return bank && sameSentence(bank, draggingChip) ? bank : null;
  }
  function setHover(slot) {
    if (hoverSlot === slot) return;
    if (hoverSlot) hoverSlot.classList.remove('active');
    hoverSlot = slot;
    if (hoverSlot) hoverSlot.classList.add('active');
  }

  root.addEventListener('pointerdown', function (e) {
    if (!e.isPrimary) return;
    var chip = e.target.closest('.dd-chip'); if (!chip) return;
    var s = chip.closest('.dd-sentence'); if (s && s.dataset.revealed === 'true') return;
    draggingChip = chip; draggingChip.classList.add('dragging');
    dragGhost = makeGhost(chip);
    var r = chip.getBoundingClientRect();
    dragOffsetX = e.clientX - r.left; dragOffsetY = e.clientY - r.top;
    lastPoint = { x: e.clientX, y: e.clientY }; setGhostPos(e.clientX, e.clientY);
    try { chip.setPointerCapture(e.pointerId); } catch (_) {}
    e.preventDefault();
  }, { passive: false });

  root.addEventListener('pointermove', function (e) {
    if (!e.isPrimary || !draggingChip) return;
    lastPoint = { x: e.clientX, y: e.clientY };
    setGhostPos(e.clientX, e.clientY);
    var slot = slotUnderPoint(lastPoint.x, lastPoint.y);
    if (!slot) slot = nearestSlotWithinTolerance(
      lastPoint.x,
      lastPoint.y,
      draggingChip.closest('.dd-sentence')
    );
    setHover(slot);
    e.preventDefault();
  }, { passive: false });

  root.addEventListener('pointerup', function (e) {
    if (!e.isPrimary || !draggingChip) return;
    var slot = slotUnderPoint(lastPoint.x, lastPoint.y);
    if (!slot) slot = nearestSlotWithinTolerance(
      lastPoint.x,
      lastPoint.y,
      draggingChip.closest('.dd-sentence')
    );
    if (slot) moveChipToSlot(draggingChip, slot);
    else {
      var bank = bankUnderPoint(lastPoint.x, lastPoint.y);
      if (bank) moveChipToBank(draggingChip); else moveChipToBank(draggingChip);
    }
    var s = draggingChip.closest('.dd-sentence'); if (s) updateCorrectHighlights(s);
    draggingChip.classList.remove('dragging'); draggingChip = null;
    if (dragGhost) { dragGhost.remove(); dragGhost = null; }
    setHover(null);
    e.preventDefault();
  }, { passive: false });

  root.addEventListener('pointercancel', function () {
    if (draggingChip) {
      moveChipToBank(draggingChip);
      draggingChip.classList.remove('dragging');
      if (dragGhost) { dragGhost.remove(); dragGhost = null; }
      draggingChip = null;
    }
    setHover(null);
  }, { passive: false });

  root.addEventListener('contextmenu', function (e) {
    if (e.target.closest('.dd-chip')) e.preventDefault();
  });
  root.addEventListener('selectstart', function (e) {
    if (e.target.closest('.dd-chip')) e.preventDefault();
  });

  var btn = document.getElementById('checkDD');
  if (btn) {
    btn.addEventListener('click', function () {
      var ok = 0, t = 0;
      root.querySelectorAll('.dd-sentence').forEach(function (s) {
        t++;
        var res = s.querySelector('.dd-result');
        var exp = s.querySelector('.dd-explain');
        var answer = (s.dataset.answer || '').trim();
        var explain = s.dataset.explain || '';
        var gb = getBuilt(s), built = gb.built, filled = gb.filled;
        var revealed = s.dataset.revealed === 'true';
        updateCorrectHighlights(s);

        if (built === answer) {
          if (res) { res.textContent = '✅ Correct'; res.style.color = 'var(--ok)'; }
          if (exp) { exp.textContent = ''; }
          s.dataset.tries = '0'; s.dataset.prev = ''; ok++; return;
        }
        if (revealed) {
          if (res) { res.textContent = 'ℹ️ Answer shown'; res.style.color = 'var(--info)'; }
          if (exp) { exp.textContent = explain; }
          return;
        }
        if (!filled) {
          if (res) { res.textContent = '⚠️ Complete all slots before checking.'; res.style.color = 'var(--warn)'; }
          if (exp) { exp.textContent = ''; }
          return;
        }

        var prev = (s.dataset.prev || '');
        if (prev === built) {
          if (res) {
            res.textContent =
              '❌ No change since last attempt. Try a new order (' +
              (s.dataset.tries || 0) + '/' + REVEAL_LIMIT + ').';
            res.style.color = 'var(--err)';
          }
          if (exp) { exp.textContent = ''; }
          return;
        }

        var tries = parseInt(s.dataset.tries || '0', 10) + 1;
        s.dataset.tries = String(tries); s.dataset.prev = built;

        if (tries >= REVEAL_LIMIT) {
          fillAnswer(s); s.dataset.revealed = 'true';
          if (res) { res.textContent = 'ℹ️ Answer shown'; res.style.color = 'var(--info)'; }
          if (exp) { exp.textContent = explain; }
          s.querySelectorAll('.dd-chip').forEach(function (ch) { ch.style.cursor = 'default'; });
        } else {
          if (res) {
            res.textContent =
              '❌ Try again (attempt ' + tries + '/' + REVEAL_LIMIT + ')';
            res.style.color = 'var(--err)';
          }
          if (exp) { exp.textContent = ''; }
        }
      });
      var score = document.getElementById('ddScore');
      if (score) score.textContent = 'Sentences correct: ' + ok + ' / ' + t;
      document.dispatchEvent(new Event('ngl-dd-updated'));
    }, { passive: false });
  }
})();

/* ========== 4a) Reading Practice — Story Understanding (mini-book Qs) === */
(function () {
  // Powers sections like:
  // <section id="sounds"> ... .sound-card[data-answer][data-explain] .choice-chip[data-val] ... </section>
  var root = document.getElementById('sounds');
  if (!root) return;

  var REVEAL_LIMIT = 2;

  // Initialise per-card metadata
  root.querySelectorAll('.sound-card').forEach(function (card) {
    if (!card.dataset.tries)    card.dataset.tries    = '0';
    if (!card.dataset.prev)     card.dataset.prev     = '';
    if (!card.dataset.revealed) card.dataset.revealed = 'false';
  });

  // Handle chip selection
  root.addEventListener('click', function (e) {
    var chip = e.target.closest('.choice-chip');
    if (!chip) return;

    var card = chip.closest('.sound-card');
    if (!card) return;

    // If card is fully revealed, allow selection but it won't change correctness state
    card.querySelectorAll('.choice-chip').forEach(function (c) {
      c.setAttribute('aria-pressed', 'false');
    });

    chip.setAttribute('aria-pressed', 'true');
    card.dataset.choice = chip.dataset.val || '';
  });

  var btn = document.getElementById('checkSounds');
  if (!btn) return;

  btn.addEventListener('click', function () {
    var ok = 0, t = 0;

    root.querySelectorAll('.sound-card').forEach(function (card) {
      t++;

      var ans   = (card.dataset.answer  || '').trim();
      var expl  =  card.dataset.explain || '';
      var choice = (card.dataset.choice || '').trim();

      var res = card.querySelector('.sound-result');
      var exp = card.querySelector('.sound-explain');
      if (!exp) {
        exp = document.createElement('div');
        exp.className = 'sound-explain';
        card.appendChild(exp);
      }

      var revealed = card.dataset.revealed === 'true';

      // No choice picked
      if (!choice) {
        if (res) {
          res.textContent = '⚠️ Pick an answer before checking.';
          res.style.color = 'var(--warn)';
        }
        exp.textContent = '';
        return;
      }

      // Already revealed: just show status + explanation
      if (revealed) {
        if (choice === ans) {
          ok++;
          if (res) {
            res.textContent = '✅ Correct';
            res.style.color = 'var(--ok)';
          }
        } else {
          if (res) {
            res.textContent = 'ℹ️ Answer shown';
            res.style.color = 'var(--info)';
          }
        }
        exp.textContent = expl;
        return;
      }

      // Correct before reveal
      if (choice === ans) {
        ok++;
        if (res) {
          res.textContent = '✅ Correct';
          res.style.color = 'var(--ok)';
        }
        exp.textContent = '';
        card.dataset.tries = '0';
        card.dataset.prev  = '';
        return;
      }

      // Wrong answer; check if same as last attempt
      var prev = card.dataset.prev || '';
      if (prev === choice) {
        if (res) {
          res.textContent =
            '❌ Same choice as last time. Try a different option (' +
            (card.dataset.tries || 0) + '/' + REVEAL_LIMIT + ').';
          res.style.color = 'var(--err)';
        }
        exp.textContent = '';
        return;
      }

      // Increment tries
      var tries = parseInt(card.dataset.tries || '0', 10) + 1;
      card.dataset.tries = String(tries);
      card.dataset.prev  = choice;

      if (tries >= REVEAL_LIMIT) {
        // Reveal the answer
        card.dataset.revealed = 'true';
        if (res) {
          res.textContent = 'ℹ️ Answer shown';
          res.style.color = 'var(--info)';
        }
        exp.textContent = expl || 'This is the correct answer.';

        // Visually mark the correct chip
        card.querySelectorAll('.choice-chip').forEach(function (c) {
          c.style.outline = '';
        });

        var selectorVal = ans.replace(/"/g, '\\"');
        var correctChip = card.querySelector('.choice-chip[data-val="' + selectorVal + '"]');
        if (correctChip) {
          correctChip.style.outline = '2px solid var(--info)';
        }
      } else {
        if (res) {
          res.textContent =
            '❌ Try again (attempt ' + tries + '/' + REVEAL_LIMIT + ')';
          res.style.color = 'var(--err)';
        }
        exp.textContent = '';
      }
    });

    var score = document.getElementById('soundScore');
    if (score) {
      score.textContent = 'Score: ' + ok + ' / ' + t;
    }
  });
})();

/* ========== 4b) Mini-Book Pager v3 (dots + tap left/right) =============== */
(function () {
  var books = document.querySelectorAll('.mini-book');
  if (!books.length) return;

  function setupBook(book) {
    var pages = Array.prototype.slice.call(
      book.querySelectorAll('.mini-book-page')
    );
    if (!pages.length) return;

    var dotsWrap = book.querySelector('.mini-book-dots');
    var counter  = book.querySelector('.mini-book-page-counter'); // ✅ matches HTML
    var dots     = null;
    var current  = 0;
    var totalPages = pages.length * 2; // each spread = 2 pages

    // Build dots if needed
    if (dotsWrap) {
      var existing = dotsWrap.querySelectorAll('.mini-book-dot');
      if (!existing.length) {
        pages.forEach(function (_, idx) {
          var b = document.createElement('button');
          b.type = 'button';
          b.className = 'mini-book-dot';
          b.dataset.page = String(idx);
          b.textContent = String(idx + 1);
          b.setAttribute('aria-pressed', 'false');
          dotsWrap.appendChild(b);
        });
      }
      dots = Array.prototype.slice.call(
        dotsWrap.querySelectorAll('.mini-book-dot')
      );
    }

    function show(idx) {
      if (idx < 0 || idx >= pages.length) return;
      current = idx;

      pages.forEach(function (p, i) {
        p.classList.toggle('is-active', i === current);
      });

      if (dots) {
        dots.forEach(function (d) {
          var isActive = String(current) === (d.dataset.page || '');
          d.setAttribute('aria-pressed', isActive ? 'true' : 'false');
        });
      }

      if (counter) {
        var start = current * 2 + 1;
        var end   = Math.min(totalPages, start + 1);
        counter.textContent = 'Pages ' + start + '–' + end + ' of ' + totalPages;
      }
    }

    // Dots click
    if (dotsWrap) {
      dotsWrap.addEventListener('click', function (e) {
        var dot = e.target.closest('.mini-book-dot');
        if (!dot) return;
        var idx = parseInt(dot.dataset.page || '0', 10);
        if (!isNaN(idx)) show(idx);
      });
    }

    // Tap left/right on spread to navigate
    pages.forEach(function (p) {
      p.addEventListener('click', function (e) {
        var rect = p.getBoundingClientRect();
        var midX = rect.left + rect.width / 2;
        if (e.clientX < midX) {
          // left half -> previous
          if (current > 0) show(current - 1);
        } else {
          // right half -> next
          if (current < pages.length - 1) show(current + 1);
        }
      });
    });

    // Initial state
    show(0);
  }

  books.forEach(setupBook);
})();

/* ========== 5) MCQ v2 (2 tries then reveal) ============================== */
(function () {
  var btn = document.getElementById('checkQuiz');
  var score = document.getElementById('quizScore');
  if (!btn) return;

  var REVEAL_LIMIT = 2;

  function getPickedVal(q) {
    var picked = q.querySelector('input[type="radio"]:checked');
    return ((picked && picked.value) || '').trim().toLowerCase();
  }

  document.querySelectorAll('.q').forEach(function (q) {
    if (!q.dataset.tries) q.dataset.tries = '0';
    if (!q.dataset.prev)  q.dataset.prev = '';
  });

  document.addEventListener('change', function (e) {
    if (e.target && e.target.matches('.q input[type="radio"]')) {
      document.dispatchEvent(new Event('ngl-inputs-updated'));
    }
  });

  btn.addEventListener('click', function () {
    var ok = 0, t = 0;
    document.querySelectorAll('.q').forEach(function (q) {
      t++;
      var correct = (q.dataset.answer || '').trim().toLowerCase();
      var pickedVal = getPickedVal(q);
      var res = q.querySelector('.result');
      var exp = q.querySelector('.explain') || document.createElement('div');
      exp.className = 'explain'; if (!exp.parentNode) q.appendChild(exp);

      var revealed = q.dataset.revealed === 'true';

      if (!pickedVal) {
        if (res) { res.textContent = '⚠️ Pick an answer before checking.'; res.style.color = 'var(--warn)'; }
        exp.textContent = '';
        return;
      }

      if (revealed) {
        if (pickedVal === correct) {
          ok++;
          if (res) { res.textContent = '✅ Correct'; res.style.color = 'var(--ok)'; }
        } else {
          if (res) { res.textContent = 'ℹ️ Answer shown'; res.style.color = 'var(--info)'; }
        }
        exp.textContent = q.dataset.explain || '';
        return;
      }

      if (pickedVal === correct) {
        ok++;
        if (res) { res.textContent = '✅ Correct'; res.style.color = 'var(--ok)'; }
        exp.textContent = '';
        q.dataset.tries = '0'; q.dataset.prev = '';
        return;
      }

      var prev = (q.dataset.prev || '');
      if (prev === pickedVal) {
        if (res) {
          res.textContent =
            '❌ Same choice as last time. Try a different option (' +
            (q.dataset.tries || 0) + '/' + REVEAL_LIMIT + ').';
          res.style.color = 'var(--err)';
        }
        exp.textContent = '';
        return;
      }

      var tries = parseInt(q.dataset.tries || '0', 10) + 1;
      q.dataset.tries = String(tries);
      q.dataset.prev = pickedVal;

      if (tries >= REVEAL_LIMIT) {
        q.dataset.revealed = 'true';
        if (res) { res.textContent = 'ℹ️ Answer shown'; res.style.color = 'var(--info)'; }
        exp.textContent = q.dataset.explain || 'This is the correct answer.';
        var correctInput = q.querySelector('input[type="radio"][value="' + correct + '"]');
        if (correctInput) {
          var parent = correctInput.closest('label');
          if (parent) { parent.style.outline = '2px solid var(--info)'; parent.style.borderRadius = '8px'; }
        }
      } else {
        if (res) {
          res.textContent =
            '❌ Try again (attempt ' + tries + '/' + REVEAL_LIMIT + ')';
          res.style.color = 'var(--err)';
        }
        exp.textContent = '';
      }
    });
    if (score) score.textContent = 'Score: ' + ok + ' / ' + t;
    document.dispatchEvent(new Event('ngl-quiz-updated'));
  });
})();

/* ========== 6) AUTOSAVE v1.2
      (inputs + DnD + tap grids + number lines + quiz meta + trace pad)
   ================================================================ */
(function () {
  // --- Reset guard so autosave won't fire during hard resets
  var RESETTING = false;
  Object.defineProperty(window, '__NGL_RESETTING', {
    get: function(){ return RESETTING; },
    set: function(v){ RESETTING = !!v; }
  });

  var PAGE_KEY = (document.body.dataset.page || location.pathname)
    .replace(/[?#].*$/, '')
    .replace(/\/+$/, '/')
    .replace(/\/+/g, '/');

  function keyFor(suffix) { return 'ngl:' + PAGE_KEY + ':' + suffix; }

  function throttle(fn, wait) {
    var t = 0, queued = false;
    return function () {
      if (window.__NGL_RESETTING) return;
      var now = Date.now();
      if (now - t >= wait) { t = now; fn(); }
      else if (!queued) {
        queued = true;
        setTimeout(function () {
          if (!window.__NGL_RESETTING) {
            queued = false; t = Date.now(); fn();
          }
        }, wait - (now - t));
      }
    };
  }

  function domKey(el) {
    if (el.name) return 'name:' + el.name;
    if (el.id)   return 'id:' + el.id;
    var n = el, segs = [];
    while (n && n !== document.body) {
      var tag = n.tagName && n.tagName.toLowerCase();
      if (!tag) break;
      var idx = 1, sib = n;
      while ((sib = sib.previousElementSibling) != null) {
        if (sib.tagName && sib.tagName.toLowerCase() === tag) idx++;
      }
      segs.push(tag + ':nth-of-type(' + idx + ')');
      if (n.matches && n.matches('form, .card, main, section, article')) break;
      n = n.parentElement;
    }
    return 'path:' + segs.reverse().join('>') + ':' + (el.type || el.tagName);
  }

  // --- Inputs (MCQ radios, checkboxes, selects, etc.) ---------------------
  function gatherInputs() {
    var data = {};
    document.querySelectorAll('input, textarea, select').forEach(function (el) {
      if (el.closest('.trace-pad')) return;
      var key = domKey(el);
      if (el.type === 'radio') {
        if (!data[key]) data[key] = { type: 'radio', value: null, name: el.name || '' };
        if (el.checked) data[key].value = el.value;
      } else if (el.type === 'checkbox') {
        if (!data[key]) data[key] = { type: 'checkbox', checked: false, value: el.value || 'on' };
        data[key].checked = !!el.checked;
      } else {
        data[key] = { type: 'value', value: el.value };
      }
    });
    return data;
  }

  function applyInputs(data) {
    if (!data || typeof data !== 'object') return;
    var esc = (window.CSS && CSS.escape) ? CSS.escape : function (s) { return String(s).replace(/"/g, '\\"'); };
    Object.keys(data).forEach(function (key) {
      var info = data[key];
      if (info.type === 'radio') {
        var el = null;
        if (info.name && info.value != null) {
          el = document.querySelector(
            'input[type="radio"][name="' + esc(info.name) + '"][value="' + esc(info.value) + '"]'
          );
        }
        if (!el && info.value != null) {
          el = document.querySelector('input[type="radio"][value="' + esc(info.value) + '"]');
        }
        if (el) el.checked = true;
      } else if (info.type === 'checkbox') {
        var candidates = Array.prototype.slice.call(
          document.querySelectorAll('input[type="checkbox"]')
        );
        var target = null;
        for (var i = 0; i < candidates.length; i++) {
          var c = candidates[i];
          var ck = domKey(c);
          if (ck === key) { target = c; break; }
          if (!target && (c.value || 'on') === (info.value || 'on')) target = c;
        }
        if (target) target.checked = !!info.checked;
      } else {
        var el2 = null;
        if (key.indexOf('id:') === 0) {
          el2 = document.getElementById(key.slice(3));
        } else if (key.indexOf('name:') === 0) {
          el2 = document.querySelector('[name="' + esc(key.slice(5)) + '"]');
        }
        if (!el2) {
          var all = Array.prototype.slice.call(
            document.querySelectorAll('input,textarea,select')
          );
          el2 = all.find(function (e) { return domKey(e) === key; }) || null;
        }
        if (el2) el2.value = (info.value == null ? '' : info.value);
      }
    });
  }

  var saveInputs = throttle(function () {
    if (window.__NGL_RESETTING) return;
    try { localStorage.setItem(keyFor('inputs'), JSON.stringify(gatherInputs())); } catch (_) {}
  }, 250);

  // --- Drag & Drop -------------------------------------------------------
  function gatherDD() {
    var out = [];
    document.querySelectorAll('#dragdrop .dd-sentence').forEach(function (s, idx) {
      var words = Array.prototype.slice.call(s.querySelectorAll('.dd-slot'))
        .map(function (sl) { return ((sl.querySelector('.dd-chip') || {}).textContent) || ''; });
      var bank = Array.prototype.slice.call(s.querySelectorAll('.dd-bank .dd-chip'))
        .map(function (ch) { return ch.textContent || ''; });
      out.push({ idx: idx, words: words, bank: bank });
    });
    return out;
  }

  function applyDD(saved) {
    if (!Array.isArray(saved)) return;
    var sentences = Array.prototype.slice.call(
      document.querySelectorAll('#dragdrop .dd-sentence')
    );
    saved.forEach(function (row) {
      var s = sentences[row.idx]; if (!s) return;
      var bank = s.querySelector('.dd-bank');
      var slots = Array.prototype.slice.call(s.querySelectorAll('.dd-slot'));
      s.querySelectorAll('.dd-slot .dd-chip').forEach(function (ch) { bank.appendChild(ch); });
      (row.words || []).forEach(function (w, i) {
        if (!w) return;
        var chip = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'))
          .find(function (c) { return c.textContent === w; });
        if (chip && slots[i]) { slots[i].innerHTML = ''; slots[i].appendChild(chip); }
      });
      if (Array.isArray(row.bank) && row.bank.length) {
        var chips = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'));
        row.bank.forEach(function (w2) {
          var ch = chips.find(function (c) { return c.textContent === w2; });
          if (ch) bank.appendChild(ch);
        });
      }
    });
    // Re-highlight correct slots
    document.querySelectorAll('#dragdrop .dd-sentence').forEach(function (s) {
      var ans = (s.dataset.answer || '').trim().split(/\s+/);
      var slots = Array.prototype.slice.call(s.querySelectorAll('.dd-slot'));
      var words = slots.map(function (sl) {
        return ((sl.querySelector('.dd-chip') || {}).textContent) || '';
      });
      slots.forEach(function (sl, i) {
        sl.classList.remove('correct');
        if (words[i] && ans[i] && words[i] === ans[i]) sl.classList.add('correct');
      });
    });
  }

  var saveDD = throttle(function () {
    if (window.__NGL_RESETTING) return;
    try { localStorage.setItem(keyFor('dd'), JSON.stringify(gatherDD())); } catch (_) {}
  }, 200);

  // --- Tap-grid (calendar etc.) -----------------------------------------
  function gatherTapGrids() {
    var out = [];
    document.querySelectorAll('.tap-grid-block').forEach(function (block, idx) {
      var grid = block.querySelector('.tap-grid');
      if (!grid) return;

      var selectedCells = grid.querySelectorAll('.tap-cell.is-selected');
      var selected = Array.prototype.map.call(selectedCells, function (cell) {
        return ((cell.dataset.value || cell.textContent || '') + '').trim();
      }).filter(Boolean);

      var result = block.querySelector('.tap-result');

      out.push({
        idx: idx,
        selected: selected,
        result: result ? (result.textContent || '') : ''
      });
    });
    return out;
  }

  function applyTapGrids(saved) {
    if (!Array.isArray(saved)) return;
    var blocks = Array.prototype.slice.call(
      document.querySelectorAll('.tap-grid-block')
    );

    saved.forEach(function (row) {
      var block = blocks[row.idx];
      if (!block) return;

      var grid = block.querySelector('.tap-grid');
      if (!grid) return;

      var cells = Array.prototype.slice.call(
        grid.querySelectorAll('.tap-cell')
      );

      // Clear old selection
      cells.forEach(function (cell) {
        cell.classList.remove('is-selected');
        cell.setAttribute('aria-pressed', 'false');
      });

      // Restore selection
      (row.selected || []).forEach(function (val) {
        var match = cells.find(function (cell) {
          var v = ((cell.dataset.value || cell.textContent || '') + '').trim();
          return v === val;
        });
        if (match) {
          match.classList.add('is-selected');
          match.setAttribute('aria-pressed', 'true');
        }
      });

      // Restore feedback text if any
      if (row.result) {
        var resultEl = block.querySelector('.tap-result');
        if (resultEl) resultEl.textContent = row.result;
      }
    });
  }

  var saveTapGrids = throttle(function () {
    if (window.__NGL_RESETTING) return;
    try { localStorage.setItem(keyFor('tapgrid'), JSON.stringify(gatherTapGrids())); } catch (_) {}
  }, 200);

  // --- Number lines (month ticks etc.) -----------------------------------
  function gatherNumberlines() {
    var out = [];
    document.querySelectorAll('.numberline-q').forEach(function (q, idx) {
      var selectedVal = '';
      var selectedTick = q.querySelector('.nl-tick.is-selected');
      if (selectedTick) {
        selectedVal = (selectedTick.dataset.value || '').trim();
      }
      var result = q.querySelector('.numberline-result');
      out.push({
        idx: idx,
        selected: selectedVal,
        result: result ? (result.textContent || '') : ''
      });
    });
    return out;
  }

  function applyNumberlines(saved) {
    if (!Array.isArray(saved)) return;
    var qs = Array.prototype.slice.call(document.querySelectorAll('.numberline-q'));

    saved.forEach(function (row) {
      var q = qs[row.idx];
      if (!q) return;

      var ticks = Array.prototype.slice.call(q.querySelectorAll('.nl-tick'));
      var resultEl = q.querySelector('.numberline-result');

      // Clear state
      ticks.forEach(function (b) {
        b.classList.remove('is-selected', 'is-correct', 'is-wrong');
      });
      q.classList.remove('correct', 'incorrect');

      // Restore selected tick
      if (row.selected) {
        var match = ticks.find(function (b) {
          return ((b.dataset.value || '').trim() === row.selected);
        });
        if (match) {
          match.classList.add('is-selected');
        }
      }

      // Restore feedback text
      if (row.result && resultEl) {
        resultEl.textContent = row.result;
      }
    });
  }

  var saveNumberlines = throttle(function () {
    if (window.__NGL_RESETTING) return;
    try { localStorage.setItem(keyFor('numberline'), JSON.stringify(gatherNumberlines())); } catch (_) {}
  }, 200);

  // --- Quiz meta --------------------------------------------------------
  function gatherQuiz() {
    var arr = [];
    document.querySelectorAll('.q').forEach(function (q, idx) {
      arr.push({
        idx: idx,
        tries: q.dataset.tries || '0',
        prev:  q.dataset.prev || '',
        revealed: q.dataset.revealed === 'true'
      });
    });
    return arr;
  }

  function applyQuiz(saved) {
    if (!Array.isArray(saved)) return;
    var qs = Array.prototype.slice.call(document.querySelectorAll('.q'));
    saved.forEach(function (row) {
      var q = qs[row.idx]; if (!q) return;
      q.dataset.tries = String(row.tries || '0');
      q.dataset.prev  = row.prev || '';
      if (row.revealed) q.dataset.revealed = 'true';
    });
  }

  var saveQuiz = throttle(function () {
    if (window.__NGL_RESETTING) return;
    try { localStorage.setItem(keyFor('quiz'), JSON.stringify(gatherQuiz())); } catch (_) {}
  }, 250);

  // --- Trace pad snapshot -----------------------------------------------
  function saveTrace() {
    if (window.__NGL_RESETTING) return;
    var c = document.getElementById('trace');
    var png = (c && c.__ngl_getPNG) ? c.__ngl_getPNG() : '';
    if (!png) return;
    try { localStorage.setItem(keyFor('tracePNG'), png); } catch (_) {}
  }

  function applyTrace() {
    var png = localStorage.getItem(keyFor('tracePNG'));
    var c = document.getElementById('trace');
    if (png && c && c.__ngl_paintPNG) {
      requestAnimationFrame(function () { c.__ngl_paintPNG(png); });
    }
  }

  var saveTraceThrottled = throttle(saveTrace, 600);

  // --- Restore everything on load ---------------------------------------
  function restoreAll() {
    try {
      var inputs = JSON.parse(localStorage.getItem(keyFor('inputs') || 'null') || 'null');
      applyInputs(inputs);

      var quiz = JSON.parse(localStorage.getItem(keyFor('quiz') || 'null') || 'null');
      applyQuiz(quiz);

      var dd = JSON.parse(localStorage.getItem(keyFor('dd') || 'null') || 'null');
      applyDD(dd);

      var tap = JSON.parse(localStorage.getItem(keyFor('tapgrid') || 'null') || 'null');
      applyTapGrids(tap);

      var nl = JSON.parse(localStorage.getItem(keyFor('numberline') || 'null') || 'null');
      applyNumberlines(nl);

      document.addEventListener('ngl-tracepad-ready', applyTrace, { once: true });
      setTimeout(applyTrace, 120);
    } catch (_) {}
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', restoreAll);
  } else {
    restoreAll();
  }

  // --- Wire events -------------------------------------------------------
  document.addEventListener('input',  function (e) {
    if (e.target && e.target.matches('input, textarea, select')) saveInputs();
  }, { passive: true });

  document.addEventListener('change', function (e) {
    if (e.target && e.target.matches('input, textarea, select')) saveInputs();
  }, { passive: true });

  document.addEventListener('ngl-dd-updated',      saveDD,          { passive: true });
  document.addEventListener('ngl-tapgrid-updated', saveTapGrids,    { passive: true });
  document.addEventListener('ngl-numberline-updated', saveNumberlines, { passive: true });

  document.addEventListener('ngl-quiz-updated', function () {
    saveQuiz();
    saveInputs();
  }, { passive: true });

  document.addEventListener('ngl-tracepad-updated', function (e) {
    e && e.detail && e.detail.throttled ? saveTraceThrottled() : saveTrace();
  }, { passive: true });

  window.addEventListener('beforeunload', function () {
    if (window.__NGL_RESETTING) return;
    saveInputs();
    saveDD();
    saveTapGrids();
    saveNumberlines();
    saveQuiz();
    saveTrace();
  });
})();

/* ========== 7) Reset THIS lesson only (aggressive, backward-compatible) === */
(function () {
  var btn = document.getElementById('resetProgressBtn');
  if (!btn) return;

  // Build multiple page keys to match old/new storage formats
  var DATA_PAGE = (document.body && document.body.dataset && document.body.dataset.page) || '';
  var PATHNAME  = (location && location.pathname || '')
    .replace(/[?#].*$/,'')
    .replace(/\/+$/,'/')
    .replace(/\/+/g,'/');
  var LAST_SEG  = PATHNAME.split('/').filter(Boolean).slice(-1)[0] || '';

  // All possible patterns we’ve ever used
  var CANDIDATE_PAGE_KEYS = Array.from(new Set([
    DATA_PAGE,                      // e.g., l1-lesson-3
    PATHNAME,                       // e.g., /levels/level-1/lessons/lesson-3-....html
    '/' + LAST_SEG + '/',           // e.g., /lesson-3-....html/
    LAST_SEG                        // e.g., lesson-3-....html
  ].filter(Boolean)));

  // Prefix families we’ve used historically
  var PREFIX_FAMILIES = [
    'ngl:',              // current generic
    'ngl:dnd:',          // old DnD format
    'ngl:quiz:',         // old quiz meta
    'ngl:check:',        // old misc
    'ngl:exit:',         // old exit ticket
    'ngl:trace:',        // old trace pad
  ];

  function matchesLessonKey(k) {
    if (!k) return false;
    for (var i = 0; i < CANDIDATE_PAGE_KEYS.length; i++) {
      var pg = CANDIDATE_PAGE_KEYS[i];
      if (k.indexOf('ngl:' + pg + ':') === 0) return true; // exact scoped (new)
      for (var f = 0; f < PREFIX_FAMILIES.length; f++) {
        var pref = PREFIX_FAMILIES[f] + pg + ':';
        if (k.indexOf(pref) === 0) return true;
      }
      if (k.indexOf(':' + pg + ':') !== -1) return true; // heuristic
      if (k.indexOf(pg + ':') !== -1) return true;
    }
    return false;
  }

function clearUIState() {
  // Uncheck radios/checkboxes and empty result labels
  document.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(function (el) {
    el.checked = false;
  });

  document.querySelectorAll(
    '.dd-result,.dd-explain,.result,.explain,' +
    '#ddScore,#quizScore,#soundScore,' +
    '.sound-result,.sound-explain'
  ).forEach(function (n) { 
    n.textContent = ''; 
  });

  // Reset Story Understanding cards
  var soundsRoot = document.getElementById('sounds');
  if (soundsRoot) {
    soundsRoot.querySelectorAll('.sound-card').forEach(function (card) {
      card.dataset.tries = '0';
      card.dataset.prev = '';
      card.dataset.revealed = 'false';
      card.dataset.choice = '';
      card.querySelectorAll('.choice-chip').forEach(function (chip) {
        chip.setAttribute('aria-pressed', 'false');
        chip.style.outline = '';
      });
    });
  }

  // Return DnD chips to their banks
  document.querySelectorAll('#dragdrop .dd-sentence').forEach(function (s) {
    var bank = s.querySelector('.dd-bank');
    if (!bank) return;

    // Move chips back into the bank
    s.querySelectorAll('.dd-slot .dd-chip').forEach(function (ch) { 
      bank.appendChild(ch); 
    });

    // Clear slots + visual state
    s.querySelectorAll('.dd-slot').forEach(function (sl) {
      sl.classList.remove('correct', 'incorrect', 'active');
      sl.innerHTML = '';
    });

    // Rebuild empty slots to match number of chips
    var targets = s.querySelector('.dd-targets');
    if (targets) {
      var n = bank.querySelectorAll('.dd-chip').length;
      targets.innerHTML = Array.from({ length: n })
        .map(function () { return '<span class="dd-slot"></span>'; })
        .join('');
    }

    // Reset attempt tracking
    s.dataset.tries = '0';
    s.dataset.prev = '';
    s.dataset.revealed = 'false';
  });

  // Clear trace snapshot
  var snap = document.getElementById('tracePrint');
  if (snap) snap.removeAttribute('src');
}

  function clearStorageForLesson() {
    try {
      // Remove known suffixes in the current schema
      var PAGE_KEY = (DATA_PAGE || PATHNAME);
      ['inputs','dd','quiz','tracePNG'].forEach(function (sfx) {
        try { localStorage.removeItem('ngl:' + PAGE_KEY + ':' + sfx); } catch(_) {}
      });

      // Sweep all keys for any legacy/variant matches
      for (var i = localStorage.length - 1; i >= 0; i--) {
        var k = localStorage.key(i);
        if (matchesLessonKey(k)) {
          localStorage.removeItem(k);
        }
      }
    } catch (_) {}
  }

  btn.addEventListener('click', function (e) {
    e.preventDefault();
    var ok = true;
    try {
      ok = window.confirm('Reset saved progress for this lesson?');
    } catch (_) {}
    if (!ok) return;

    // Stop any autosave during reset
    window.__NGL_RESETTING = true;

    clearStorageForLesson();
    clearUIState();
    clearCanvasNow();

    // Reload for a pristine state (beforeunload savers are skipped)
    setTimeout(function () {
      location.reload();
    }, 0);
  }, { passive: false });
})();


/* ==========================================================================
   MATH INTERACTIONS — Tap grids + number lines + slider questions
   (Standalone & safe alongside existing Next Gen JS)
   ========================================================================== */

(function () {

  /* ------------ Helper: compare two sets of string values -------------- */
  function sameSet(aArr, bArr) {
    if (aArr.length !== bArr.length) return false;
    var a = aArr.slice().sort();
    var b = bArr.slice().sort();
    for (var i = 0; i < a.length; i++) {
      if (a[i] !== b[i]) return false;
    }
    return true;
  }

/* =============================
   1) Tap Grids
   ============================= */
function initTapGrids() {
  var grids = document.querySelectorAll('.tap-grid');
  if (!grids.length) return;

  // Toggle cells
  grids.forEach(function (grid) {
    grid.addEventListener('click', function (e) {
      var cell = e.target.closest('.tap-cell');
      if (!cell || !grid.contains(cell)) return;
      var isSelected = cell.classList.toggle('is-selected');
      cell.setAttribute('aria-pressed', isSelected ? 'true' : 'false');

      // 🔔 notify autosave that tap-grid state changed
      document.dispatchEvent(new CustomEvent('ngl-tapgrid-updated', {
        detail: { grid: grid }
      }));
    });
  });

  // Check button per block
  var checkButtons = document.querySelectorAll('.tap-grid-block [data-tap-check]');
  checkButtons.forEach(function (btn) {
    btn.addEventListener('click', function () {
      var block = btn.closest('.tap-grid-block');
      if (!block) return;

      var grid = block.querySelector('.tap-grid');
      var result = block.querySelector('.tap-result');
      if (!grid || !result) return;

      var correctAttr = (grid.dataset.correct || '').trim();
      if (!correctAttr) {
        result.textContent = 'No correct answers set for this grid.';
        result.classList.remove('ok', 'error');
        return;
      }

      // Correct values array
      var correctValues = correctAttr.split(',')
        .map(function (v) { return v.trim(); })
        .filter(Boolean);

      // Selected values
      var selectedCells = grid.querySelectorAll('.tap-cell.is-selected');
      var selectedValues = Array.prototype.map.call(selectedCells, function (cell) {
        return (cell.dataset.value || cell.textContent || '').trim();
      }).filter(Boolean);

      var ok = sameSet(correctValues, selectedValues);

      if (ok) {
        result.textContent = 'Nice! Your selection is correct.';
        result.classList.add('ok');
        result.classList.remove('error');
      } else {
        result.textContent = 'Not quite yet — check again.';
        result.classList.add('error');
        result.classList.remove('ok');
      }

      // 🔔 also notify autosave after checking
      document.dispatchEvent(new CustomEvent('ngl-tapgrid-updated', {
        detail: { grid: grid }
      }));
    });
  });
}

/* =============================
   2) Real Number Line — Tap Ticks
   ============================= */
function initNumberlines() {
  var ticks = document.querySelectorAll('.numberline-q .nl-tick');
  if (!ticks.length) return;

  ticks.forEach(function (btn) {
    btn.addEventListener('click', function () {
      var q = this.closest('.numberline-q');
      if (!q) return;

      var correct = (q.dataset.correct || '').trim();
      var value   = (this.dataset.value || '').trim();
      var result  = q.querySelector('.numberline-result');

      // Reset state
      q.querySelectorAll('.nl-tick').forEach(function (b) {
        b.classList.remove('is-selected', 'is-correct', 'is-wrong');
      });
      q.classList.remove('correct', 'incorrect');

      // Mark current
      this.classList.add('is-selected');

      if (result && correct) {
        if (value === correct) {
          this.classList.add('is-correct');
          q.classList.add('correct');
          result.textContent = '✅ Correct — that tick shows ' + correct + '.';
        } else {
          this.classList.add('is-wrong');
          q.classList.add('incorrect');
          result.textContent = '❌ Not quite. Try another tick.';
        }
      }

      // 🔁 Tell autosave that a number line / month line changed
      document.dispatchEvent(new Event('ngl-numberline-updated'));
    });
  });
}

  /* =============================
     3) Slider Questions (if used anywhere)
     ============================= */
  function initSliderQuestions() {
    var blocks = document.querySelectorAll('.slider-q');
    if (!blocks.length) return;

    blocks.forEach(function (block) {
      var slider = block.querySelector('input[type="range"]');
      if (!slider) return;

      var valueEl = block.querySelector('.slider-value');
      var correctStr = slider.dataset.correctValue || '';
      var correct = correctStr === '' ? null : parseFloat(correctStr);

      function updateValueText() {
        if (valueEl) valueEl.textContent = 'Current: ' + slider.value;
      }

      updateValueText();

      slider.addEventListener('input', function () {
        updateValueText();
        block.classList.remove('correct', 'incorrect');
      });

      slider.addEventListener('change', function () {
        if (correct === null || isNaN(correct)) {
          updateValueText();
          return;
        }

        var current = parseFloat(slider.value);
        if (current === correct) {
          block.classList.add('correct');
          block.classList.remove('incorrect');
          if (valueEl) valueEl.textContent = 'Current: ' + slider.value + ' ✓';
        } else {
          block.classList.add('incorrect');
          block.classList.remove('correct');
          if (valueEl) valueEl.textContent = 'Current: ' + slider.value + ' — try again';
        }
      });
    });
  }
// =============================================
// Mission Mode helper for number lines
// (one question at a time inside mission overlay)
// =============================================
window.nglInitMissionNumberline = function (qEl, onAnswered) {
  if (!qEl) return;

  var correct = (qEl.dataset.correct || '').trim();
  var ticks = Array.prototype.slice.call(qEl.querySelectorAll('.nl-tick'));
  var resultEl = qEl.querySelector('.numberline-result');

  // Reset clean state for mission context
  ticks.forEach(function (b) {
    b.classList.remove('is-selected', 'is-correct', 'is-wrong');
  });
  qEl.classList.remove('correct', 'incorrect');
  if (resultEl) resultEl.textContent = '';

  function handleClick() {
    var value = (this.dataset.value || '').trim();

    ticks.forEach(function (b) {
      b.classList.remove('is-selected', 'is-correct', 'is-wrong');
    });

    this.classList.add('is-selected');

    var isCorrect = (value === correct);

    if (isCorrect) {
      this.classList.add('is-correct');
      qEl.classList.add('correct');
      if (resultEl) resultEl.textContent = 'Correct!';
    } else {
      this.classList.add('is-wrong');
      qEl.classList.add('incorrect');
      if (resultEl) resultEl.textContent = 'Try again.';
    }

    if (typeof onAnswered === 'function') {
      onAnswered(isCorrect, { value: value });
    }
  }

  ticks.forEach(function (btn) {
    btn.removeEventListener('click', handleClick);
    btn.addEventListener('click', handleClick);
  });
};

  /* =============================
     4) Attach to DOM ready
     ============================= */
  function initAllMath() {
    initTapGrids();
    initNumberlines();
    initSliderQuestions();
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initAllMath);
  } else {
    initAllMath();
  }

})();
/* ========== 7) Letter–Sound Pad v1 (pure phonemes) ====================== */
(function () {
  // Support one or more pads if you ever add more later
  var pads = document.querySelectorAll('#letterSounds, .letter-sound-pad');
  if (!pads.length) return;

  pads.forEach(function (root) {
    root.addEventListener('click', function (e) {
      var btn = e.target.closest('button[data-sound]');
      if (!btn) return;

      var src = btn.dataset.sound;
      if (!src) return;

      try {
        var audio = new Audio(src);
        audio.play().catch(function () {});
      } catch (_) {}
    });
  });
})();
/* ========== 8) Guided / Practice lesson audio buttons (play–pause toggle) ====== */
(function () {
  var currentAudio = null;
  var currentBtn = null;

  document.addEventListener('click', function (e) {
    var btn = e.target.closest('.lesson-audio');
    if (!btn) return;

    var src = btn.getAttribute('data-sound');
    if (!src) return;

    // If clicking same button again → toggle play/pause
    if (currentBtn === btn && currentAudio) {
      if (currentAudio.paused) {
        currentAudio.play().catch(function(){});
        btn.classList.add('is-playing');
        btn.textContent = '⏸️ Pause';
      } else {
        currentAudio.pause();
        btn.classList.remove('is-playing');
        btn.textContent = '🔊 Play';
      }
      return;
    }

    // New button clicked → stop previous audio
    if (currentAudio) {
      currentAudio.pause();
      currentAudio.currentTime = 0;
      if (currentBtn) {
        currentBtn.classList.remove('is-playing');
        currentBtn.textContent = '🔊 Play';
      }
    }

    // Create and play new audio
    currentAudio = new Audio(src);
    currentBtn = btn;

    currentAudio.play().then(function () {
      btn.classList.add('is-playing');
      btn.textContent = '⏸️ Pause';
    }).catch(function () {});

    // Reset when finished
    currentAudio.addEventListener('ended', function () {
      if (currentBtn) {
        currentBtn.classList.remove('is-playing');
        currentBtn.textContent = '🔊 Play';
      }
      currentAudio = null;
      currentBtn = null;
    });
  });
})();

/* ========== Sound Modal ======================= */

const soundModal = document.querySelector("#soundPadModal");

if (soundModal) {
  document.addEventListener("click", (e) => {
    // Open pad
    if (e.target.closest("#openSoundPad")) {
      soundModal.setAttribute("aria-hidden", "false");
      return;
    }

    // Close pad (button)
    if (e.target.closest(".close-soundpad")) {
      soundModal.setAttribute("aria-hidden", "true");
      return;
    }

    // Close by clicking backdrop only
    if (e.target === soundModal) {
      soundModal.setAttribute("aria-hidden", "true");
      return;
    }

    // Play sound
    if (e.target.matches(".sound-btn")) {
      const file = e.target.dataset.sound;
      if (file) {
        new Audio(file).play().catch(() => {});
      }
    }
  });

  // ESC key closes modal
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && soundModal.getAttribute("aria-hidden") === "false") {
      soundModal.setAttribute("aria-hidden", "true");
    }
  });
}
/* =========================================
   Hundred chart modal
   ========================================= */
(function () {
  const openBtn = document.querySelector('.numberchart-floating-btn');
  const modal = document.querySelector('.numberchart-modal');
  if (!openBtn || !modal) return; // lesson without chart

  const closeBtn = modal.querySelector('.numberchart-close');
  const body = document.body;

  function openChart() {
    modal.setAttribute('aria-hidden', 'false');
    modal.classList.add('is-active');
    body.classList.add('has-mission-open');
  }

  function closeChart() {
    modal.setAttribute('aria-hidden', 'true');
    modal.classList.remove('is-active');
    body.classList.remove('has-mission-open');
  }

  openBtn.addEventListener('click', openChart);
  if (closeBtn) closeBtn.addEventListener('click', closeChart);

  // Click backdrop to close
  modal.addEventListener('click', function (e) {
    if (e.target === modal) closeChart();
  });

  // Esc key closes chart
  document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && modal.classList.contains('is-active')) {
      closeChart();
    }
  });
})();
/* ===============================
   Shared overlay manager (Mission / Briefing / Training)
   =============================== */
(function () {
  var body = document.body;
  if (!body) return;

  var registered = [];
  var KEY_ESC = 'Escape';

  function registerOverlay(overlay) {
    if (!overlay) return;
    if (registered.indexOf(overlay) === -1) {
      registered.push(overlay);

      // Backdrop click
      overlay.addEventListener('click', function (e) {
        if (e.target === overlay) {
          closeOverlay(overlay);
        }
      });
    }
  }

  function openOverlay(overlay) {
    if (!overlay) return;
    overlay.classList.add('is-active');
    overlay.setAttribute('aria-hidden', 'false');
    body.classList.add('has-mission-open');
  }

  function closeOverlay(overlay) {
    if (!overlay) return;
    overlay.classList.remove('is-active');
    overlay.setAttribute('aria-hidden', 'true');

    // If no overlays are active, remove body flag
    var anyActive = registered.some(function (ov) {
      return ov.classList.contains('is-active');
    });
    if (!anyActive) {
      body.classList.remove('has-mission-open');
    }
  }

  // ESC closes whichever overlays are open
  document.addEventListener('keydown', function (e) {
    if (e.key !== KEY_ESC) return;
    registered.forEach(function (overlay) {
      if (overlay.classList.contains('is-active')) {
        closeOverlay(overlay);
      }
    });
  });

  // Expose tiny helper on window
  window.nglMissionOverlays = {
    register: registerOverlay,
    open: openOverlay,
    close: closeOverlay
  };
})();
/* ===============================
   Mission Sounds Helper
   =============================== */
(function () {
  var cache = {};

  function getSound(kind) {
    var src;
    switch (kind) {
      case 'correct':
        src = '/assets/audio/mission-correct.mp3';
        break;
      case 'wrong':
        src = '/assets/audio/mission-wrong.mp3';
        break;
      case 'move':
        src = '/assets/audio/mission-move.mp3';
        break;
      case 'tick':
        src = '/assets/audio/mission-tick.mp3';
        break;
      default:
        return null;
    }

    if (!cache[src]) {
      var a = new Audio(src);
      a.preload = 'auto';
      cache[src] = a;
    }
    // clone so multiple sounds can overlap
    return cache[src].cloneNode();
  }

  window.nglMissionPlaySound = function (kind) {
    try {
      var a = getSound(kind);
      if (!a) return;
      a.currentTime = 0;
      a.play().catch(function () { /* ignore autoplay issues */ });
    } catch (_) {}
  };
})();

/* ==========================================================
   Mission Question Initializer
   Used by Mission Runner v2 (Lesson 81)
   Supports:
   - MCQ (.q / .mission-mcq)
   - Number line (.numberline-q)
   - Slider (.slider-q)
   - Drag & Drop (.dd-sentence) with pointer-based drag
   ========================================================== */
window.nglInitMissionQuestion = function (sourceEl, done) {
  var container = document.getElementById('missionChoices');
  if (!container) {
    if (typeof done === 'function') done(false);
    return;
  }
  container.innerHTML = '';

  // 🔹 Helper: shuffle the order of .dd-chip elements inside a bank
  function shuffleBank(bank) {
    if (!bank) return;
    var chips = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'));
    if (chips.length < 2) return;

    for (var i = chips.length - 1; i > 0; i--) {
      var j = Math.floor(Math.random() * (i + 1));
      var tmp = chips[i];
      chips[i] = chips[j];
      chips[j] = tmp;
    }

    chips.forEach(function (chip) {
      bank.appendChild(chip);
    });
  }

  // --- Detect type ---------------------------------------------------------
  var type;
  if (sourceEl.classList.contains('mission-mcq') || sourceEl.classList.contains('q')) {
    type = 'mcq';
  } else if (sourceEl.classList.contains('numberline-q')) {
    type = 'numberline';
  } else if (sourceEl.classList.contains('slider-q')) {
    type = 'slider';
  } else if (sourceEl.classList.contains('dd-sentence')) {
    type = 'dnd';
  } else {
    type = 'unknown';
  }

  var answered = false;
  function finish(isCorrect) {
    if (answered) return;
    answered = true;
    if (typeof done === 'function') done(!!isCorrect);
  }

  /* -------------------------
     A) Multiple-choice (MCQ)
     ------------------------- */
  if (type === 'mcq') {
    var correctValue = (sourceEl.getAttribute('data-answer') || '').trim();

    var labels = Array.prototype.slice.call(sourceEl.querySelectorAll('label'));
    if (!labels.length) {
      finish(false);
      return;
    }

    labels.forEach(function (label) {
      var input = label.querySelector('input[type="radio"]');
      if (!input) return;

      var choiceValue = (input.value || '').trim();
      var labelText   = (label.textContent || '').trim();

      var btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'mission-choice-btn';
      btn.textContent = labelText;

      btn.addEventListener('click', function () {
        if (answered) return;

        var all = container.querySelectorAll('.mission-choice-btn');
        all.forEach(function (b) {
          b.disabled = true;
          b.classList.add('disabled');
        });

        var isCorrect = (choiceValue === correctValue);
        if (isCorrect) {
          btn.classList.add('correct');
        } else {
          btn.classList.add('incorrect');
        }

        finish(isCorrect);
      });

      container.appendChild(btn);
    });

    return;
  }

  /* -------------------------
     B) Number line question
     ------------------------- */
  if (type === 'numberline') {
    var correct = (sourceEl.dataset.correct || '').trim();
    var nl = sourceEl.querySelector('.numberline');
    if (!nl || !correct) {
      finish(false);
      return;
    }

    // Clone just the number line visual (your CSS already hides extra headings)
    var clone = nl.cloneNode(true);
    container.appendChild(clone);

    var ticks = Array.prototype.slice.call(clone.querySelectorAll('.nl-tick'));

    ticks.forEach(function (tick) {
      tick.addEventListener('click', function () {
        if (answered) return;

        ticks.forEach(function (b) {
          b.classList.remove('is-selected', 'is-correct', 'is-wrong');
        });

        var val = (tick.dataset.value || '').trim();
        tick.classList.add('is-selected');

        var isCorrect = (val === correct);
        if (isCorrect) {
          tick.classList.add('is-correct');
        } else {
          tick.classList.add('is-wrong');
        }

        finish(isCorrect);
      });
    });

    return;
  }

  /* -------------------------
     C) Slider question
     ------------------------- */
  if (type === 'slider') {
    // Clone the whole block (your CSS already makes it match the MCQ style)
    var blockClone = sourceEl.cloneNode(true);
    container.appendChild(blockClone);

    var slider  = blockClone.querySelector('input[type="range"]');
    var valueEl = blockClone.querySelector('.slider-value');

    if (!slider) {
      finish(false);
      return;
    }

    var correctStr = slider.dataset.correctValue || '';
    var correctVal = correctStr === '' ? null : parseFloat(correctStr);

    function updateValueText() {
      if (valueEl) {
        valueEl.textContent = 'Current: ' + slider.value;
      }
    }

    updateValueText();

    slider.addEventListener('input', function () {
      updateValueText();
      blockClone.classList.remove('correct', 'incorrect');
    });

    slider.addEventListener('change', function () {
      if (answered) return;
      if (correctVal === null || isNaN(correctVal)) {
        finish(false);
        return;
      }

      var current = parseFloat(slider.value);
      var isCorrect = current === correctVal;

      if (valueEl) {
        valueEl.textContent =
          'Current: ' +
          slider.value +
          (isCorrect ? ' ✓' : ' — try again');
      }

      finish(isCorrect);
    });

    return;
  }

  /* =========================
     D) Drag & Drop (pointer drag)
     ========================= */
  if (type === 'dnd') {
    var sentence = sourceEl.cloneNode(true);
    container.appendChild(sentence);

    var bank      = sentence.querySelector('.dd-bank');
    var targets   = sentence.querySelector('.dd-targets');
    var resultEl  = sentence.querySelector('.dd-result');
    var explainEl = sentence.querySelector('.dd-explain');
    var answer    = (sourceEl.getAttribute('data-answer') || '').trim();
    var explain   = sourceEl.getAttribute('data-explain') || '';
    var MAX_TRIES = 2;
    var tries     = 0;

    if (resultEl) resultEl.textContent = '';
    if (explainEl) explainEl.textContent = '';

    if (!bank || !targets || !answer) {
      finish(false);
      return;
    }

    // Ensure we have slots
    if (!targets.querySelector('.dd-slot')) {
      var chipsInit = Array.prototype.slice.call(bank.querySelectorAll('.dd-chip'));
      targets.innerHTML = '';
      chipsInit.forEach(function () {
        var sl = document.createElement('span');
        sl.className = 'dd-slot';
        targets.appendChild(sl);
      });
    }

    // 🔹 NEW: shuffle the bank so chips start mixed, not in answer order
    shuffleBank(bank);

    function getState() {
      var slots = Array.prototype.slice.call(sentence.querySelectorAll('.dd-slot'));
      var words = slots.map(function (sl) {
        var ch = sl.querySelector('.dd-chip');
        return ch ? (ch.textContent || '').trim() : '';
      });
      var filled = words.every(function (w) { return w !== ''; });
      return {
        slots:  slots,
        words:  words,
        filled: filled,
        built:  words.join(' ').trim()
      };
    }

    // --- Simple pointer-based drag inside this one sentence ---------------
    var draggingChip = null;
    var dragGhost    = null;
    var offsetX = 0;
    var offsetY = 0;

    function makeGhost(chip) {
      var g  = document.createElement('div');
      var cs = window.getComputedStyle(chip);

      g.className = 'drag-ghost';
      g.textContent = chip.textContent;
      g.style.position = 'fixed';
      g.style.left = '0';
      g.style.top  = '0';
      g.style.pointerEvents = 'none';
      g.style.padding = cs.padding || '8px 12px';
      g.style.borderRadius = cs.borderRadius || '999px';
      g.style.border = cs.border || '1px solid rgba(0,0,0,.15)';
      g.style.background = cs.backgroundColor || '#fff';
      g.style.boxShadow = '0 10px 24px rgba(0,0,0,.25)';
      g.style.font = cs.font;
      g.style.zIndex = '9999';
      g.style.transform = 'translate(-9999px,-9999px)';
      document.body.appendChild(g);
      return g;
    }

    function setGhostPos(x, y) {
      if (!dragGhost) return;
      dragGhost.style.transform =
        'translate(' + (x - offsetX) + 'px,' + (y - offsetY) + 'px)';
    }

    function elementFromPointSafe(x, y) {
      if (!dragGhost) return document.elementFromPoint(x, y);
      var prev = dragGhost.style.display;
      dragGhost.style.display = 'none';
      var el = document.elementFromPoint(x, y);
      dragGhost.style.display = prev;
      return el;
    }

    function slotUnderPointer(x, y) {
      var el = elementFromPointSafe(x, y);
      if (!el) return null;
      var slot = el.closest ? el.closest('.dd-slot') : null;
      return slot && sentence.contains(slot) ? slot : null;
    }

    function bankUnderPointer(x, y) {
      var el = elementFromPointSafe(x, y);
      if (!el) return null;
      var b = el.closest ? el.closest('.dd-bank') : null;
      return b && sentence.contains(b) ? b : null;
    }

    sentence.addEventListener('pointerdown', function (e) {
      if (!e.isPrimary || answered) return;
      var chip = e.target.closest('.dd-chip');
      if (!chip || !sentence.contains(chip)) return;

      draggingChip = chip;
      var r = chip.getBoundingClientRect();
      offsetX = e.clientX - r.left;
      offsetY = e.clientY - r.top;
      dragGhost = makeGhost(chip);
      setGhostPos(e.clientX, e.clientY);

      try { chip.setPointerCapture(e.pointerId); } catch (err) {}
      e.preventDefault();
    }, { passive: false });

    sentence.addEventListener('pointermove', function (e) {
      if (!e.isPrimary || !draggingChip) return;
      setGhostPos(e.clientX, e.clientY);
      e.preventDefault();
    }, { passive: false });

    function endDrag(e) {
      if (!draggingChip) return;

      var x = e.clientX;
      var y = e.clientY;

      var slot = slotUnderPointer(x, y);
      if (slot) {
        var existing = slot.querySelector('.dd-chip');
        if (existing) bank.appendChild(existing);
        slot.appendChild(draggingChip);
      } else {
        var b = bankUnderPointer(x, y);
        if (b) bank.appendChild(draggingChip);
        else   bank.appendChild(draggingChip);
      }

      if (dragGhost) {
        dragGhost.remove();
        dragGhost = null;
      }
      draggingChip = null;
    }

    sentence.addEventListener('pointerup', function (e) {
      if (!e.isPrimary) return;
      endDrag(e);
    }, { passive: false });

    sentence.addEventListener('pointercancel', function (e) {
      if (!e.isPrimary) return;
      endDrag(e);
    }, { passive: false });

    sentence.addEventListener('contextmenu', function (e) {
      if (e.target.closest('.dd-chip')) e.preventDefault();
    });
    sentence.addEventListener('selectstart', function (e) {
      if (e.target.closest('.dd-chip')) e.preventDefault();
    });

    // --- Check button for this DnD question -------------------------------
    var checkBtn = document.createElement('button');
    checkBtn.type = 'button';
    checkBtn.className = 'btn primary';
    checkBtn.textContent = 'Check answer';
    container.appendChild(checkBtn);

    checkBtn.addEventListener('click', function () {
      if (answered) return;

      var state = getState();
      if (!state.filled) {
        if (resultEl) {
          resultEl.textContent = '⚠️ Fill all slots before checking.';
        }
        return;
      }

      tries += 1;
      var isCorrect = (state.built === answer);

      if (isCorrect) {
        if (resultEl) resultEl.textContent = '';
        if (explainEl) explainEl.textContent = '';
        setTimeout(function () { finish(true); }, 800);
        return;
      }

      if (tries < MAX_TRIES) {
        if (resultEl) resultEl.textContent = '❌ Not quite. Try a different order.';
        if (explainEl) explainEl.textContent = '';
        return;
      }

      if (resultEl) resultEl.textContent = 'ℹ️ Here is the correct order.';
      if (explainEl && explain) explainEl.textContent = explain;

      var allChips = Array.prototype.slice.call(sentence.querySelectorAll('.dd-chip'));
      allChips.forEach(function (ch) { bank.appendChild(ch); });

      var tokens = answer.split(/\s+/);
      var slots = state.slots;
      tokens.forEach(function (tok, i) {
        var chip = allChips.find(function (c) {
          return (c.textContent || '').trim() === tok;
        });
        if (chip && slots[i]) {
          var existing = slots[i].querySelector('.dd-chip');
          if (existing) bank.appendChild(existing);
          slots[i].appendChild(chip);
        }
      });

      setTimeout(function () { finish(false); }, 1000);
    });

    return;
  }

  /* -------------------------
     Fallback — unknown type
     ------------------------- */
  var fallback = document.createElement('p');
  fallback.textContent = 'This mission item is not available yet. Tap Next to continue.';
  var nextBtn = document.createElement('button');
  nextBtn.type = 'button';
  nextBtn.className = 'btn primary';
  nextBtn.textContent = 'Next';
  nextBtn.addEventListener('click', function () {
    finish(false);
  });
  container.appendChild(fallback);
  container.appendChild(nextBtn);
};

/* =============================================
   Mission UI Decorator — sounds + animations
   (no changes to mission logic)
   ============================================= */
(function () {
  var overlay = document.getElementById('missionOverlay');
  if (!overlay || !window.nglInitMissionQuestion) return;

  var originalInit = window.nglInitMissionQuestion;

  // Wrap the existing initializer so we can inject sound + flash
  window.nglInitMissionQuestion = function (sourceEl, done) {
    return originalInit(sourceEl, function (isCorrect) {
      // 1) Play correct / wrong sound
      if (window.nglMissionPlaySound) {
        window.nglMissionPlaySound(isCorrect ? 'correct' : 'wrong');
      }

      // 2) Flash overlay (CSS handles animations)
      overlay.classList.remove('mission-correct', 'mission-incorrect');
      // force reflow so animation can restart
      void overlay.offsetWidth;
      overlay.classList.add(isCorrect ? 'mission-correct' : 'mission-incorrect');

      // 3) Continue with the normal mission flow
      if (typeof done === 'function') done(isCorrect);
    });
  };

  // Extra: low-key sounds for interactions inside the overlay
  overlay.addEventListener('pointerup', function (e) {
    if (e.target.closest('.dd-chip') && window.nglMissionPlaySound) {
      window.nglMissionPlaySound('move');   // DnD drop
    }
  });

  overlay.addEventListener('click', function (e) {
    if (e.target.closest('.nl-tick') && window.nglMissionPlaySound) {
      window.nglMissionPlaySound('tick');   // number line tap
    }
  });

  overlay.addEventListener('change', function (e) {
    var slider = e.target;
    if (
      slider &&
      slider.matches('.mission-choices input[type="range"]') &&
      window.nglMissionPlaySound
    ) {
      window.nglMissionPlaySound('tick');   // slider answer
    }
  });
})();

/* ==========================================================
   MISSION RUNNER v3 — with autosave & "wrong-only" replay
   (Lesson 81 — Counting to 100)
   ========================================================== */
(function () {
  var overlay = document.getElementById('missionOverlay');
  var body    = document.body;
  if (!overlay || !body) return;
  if (body.getAttribute('data-page') !== 'l1-lesson-81-math') return;

  // Local storage key for this mission
  var STORAGE_KEY = 'ngl:mission:l1-lesson-81-math:v3';

  /* ---------------------------------------------------------
     DOM references
  --------------------------------------------------------- */
  var qTextEl        = document.getElementById('missionQuestionText');
  var qChoicesEl     = document.getElementById('missionChoices');
  var qFeedbackEl    = document.getElementById('missionFeedback');
  var progressTextEl = document.getElementById('missionProgressText');
  var progressFillEl = document.getElementById('missionProgressFill');
  var completeMsgEl  = document.getElementById('missionCompleteMsg');

  var retryBtn  = document.getElementById('missionRetryBtn');
  var closeBtn  = document.getElementById('missionCloseBtn');
  var closeIcon = document.getElementById('missionCloseIcon');

  var startBtn           = document.querySelector('[data-mission-start], #startMissionBtn');
  var resetHeroBtn       = document.querySelector('[data-mission-reset], #resetMissionBtn');
  var heroProgressTextEl = document.getElementById('missionHeroProgressText');
  var heroProgressFillEl = document.getElementById('missionHeroProgressFill');

  // NEW: hero tier badge + status line
  var heroTierPill   = document.getElementById('missionHeroTierPill');
  var heroStatusText = document.getElementById('missionHeroStatusText');

  /* ---------------------------------------------------------
     Register overlay with global overlay manager
  --------------------------------------------------------- */
  if (window.nglMissionOverlays) {
    window.nglMissionOverlays.register(overlay);
  }

  /* ---------------------------------------------------------
     Collect ALL mission questions in the defined order
     (.q, .slider-q, .numberline-q, .dd-sentence)
  --------------------------------------------------------- */
  var missionQuestions = Array.from(
    document.querySelectorAll(
      '#missionExercises .mission-step .q, ' +
      '#missionExercises .mission-step .slider-q, ' +
      '#missionExercises .mission-step .numberline-q, ' +
      '#missionExercises .mission-step .dd-sentence'
    )
  );

  // Tag MCQs as mission-mcq to trigger MCQ handling
  missionQuestions.forEach(function (q) {
    if (q.classList.contains('q')) {
      q.classList.add('mission-mcq');
    }
  });

  var totalQuestions = missionQuestions.length;
  if (!totalQuestions) return;

  /* ---------------------------------------------------------
     Mission state
     missionState[i] = 0 (not done / wrong) or 1 (correct)
  --------------------------------------------------------- */
  var missionState = [];   // one slot per missionQuestions index
  var currentIndex = 0;    // index into missionQuestions
  var correctCount = 0;    // number of questions marked correct
  var isLocked     = false;
  
  /* ---------------------------------------------------------
     Tier helpers + celebration visuals
     --------------------------------------------------------- */

  // Decide which tier the student reached based on % correct
  function getMissionTier(percent) {
    if (percent >= 90) return 3;  // Galaxy Ace
    if (percent >= 60) return 2;  // Star Explorer
    return 1;                     // Space Cadet
  }

  // Core message string for the tier (used inside the badge block)
  function getTierMessage(tier, correct, total) {
    var percent = total > 0 ? Math.round((correct / total) * 100) : 0;

    if (tier === 3) {
      return 'Galaxy Ace! You answered ' + correct + '/' + total +
             ' correctly (' + percent + '%). Mission 81 is fully complete!';
    }
    if (tier === 2) {
      return 'Star Explorer! You answered ' + correct + '/' + total +
             ' correctly (' + percent +
             '%). Great job — you can replay the mission to aim for Galaxy Ace.';
    }
    return 'Space Cadet! You answered ' + correct + '/' + total +
           ' correctly (' + percent +
           '%). Keep practising — every mission makes you stronger.';
  }

  // Render a big badge + friendly text in the completion message area
  function renderTierCompletion(tier, correct, total, targetEl) {
    if (!targetEl) return;

    var baseMsg   = getTierMessage(tier, correct, total);
    var badgeText = '';
    var badgeIcon = '';
    var badgeClass = '';

    if (tier === 3) {
      badgeText  = 'Galaxy Ace';
      badgeIcon  = '🌟';
      badgeClass = 'mission-tier-badge--ace';
    } else if (tier === 2) {
      badgeText  = 'Star Explorer';
      badgeIcon  = '🚀';
      badgeClass = 'mission-tier-badge--star';
    } else {
      badgeText  = 'Space Cadet';
      badgeIcon  = '✨';
      badgeClass = 'mission-tier-badge--cadet';
    }

    targetEl.innerHTML =
      '<div class="mission-complete-wrap">' +
        '<div class="mission-tier-badge ' + badgeClass + '">' +
          '<span class="mission-tier-badge-icon">' + badgeIcon + '</span>' +
          '<span class="mission-tier-badge-text">' + badgeText + '</span>' +
        '</div>' +
        '<div class="mission-tier-text">' + baseMsg + '</div>' +
        '<div class="mission-tier-hint">' +
          'Use "Reset mission" if you want to try again from the beginning.' +
        '</div>' +
      '</div>';
  }

  // Add a permanent glow + twinkling stars. No fade-out.
  function celebrateMission(tier) {
    var panel = overlay.querySelector('.mission-panel');
    if (!panel) return;

    panel.classList.add('mission-panel--celebrate');

    // Frame color per tier
    if (tier === 3) {
      panel.classList.add('mission-panel--tier-3');
    } else if (tier === 2) {
      panel.classList.add('mission-panel--tier-2');
    } else {
      panel.classList.add('mission-panel--tier-1');
    }

    // Add starfield overlay only once
    if (!panel.querySelector('.mission-starfield')) {
      var starfield = document.createElement('div');
      starfield.className = 'mission-starfield';

      // A handful of stars with different positions + delays
      for (var i = 0; i < 18; i++) {
        var s = document.createElement('span');
        s.className = 'mission-star';
        s.style.left = (Math.random() * 100) + '%';
        s.style.top  = (Math.random() * 100) + '%';
        s.style.animationDelay = (Math.random() * 4) + 's';
        starfield.appendChild(s);
      }

      panel.appendChild(starfield);
    }

    // Optional: soft "celebration" sound hook (only if you add an audio element)
    /*
    var audio = document.getElementById('missionCelebrateSound');
    if (audio && typeof audio.play === 'function') {
      try { audio.currentTime = 0; audio.play(); } catch (e) {}
    }
    */
  }

  /* ---------------------------------------------------------
     Persistence helpers
  --------------------------------------------------------- */
  function loadState() {
    try {
      var raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) return null;
      var obj = JSON.parse(raw);
      if (!obj || !Array.isArray(obj.q) || obj.q.length !== totalQuestions) return null;
      return obj;
    } catch (e) {
      return null;
    }
  }

  function saveState() {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify({ q: missionState }));
    } catch (e) {
      // ignore
    }
  }

  function initState() {
    var saved = loadState();
    if (saved) {
      missionState = saved.q.map(function (v) {
        return v === 1 ? 1 : 0;
      });
    } else {
      missionState = new Array(totalQuestions).fill(0);
    }

    correctCount = missionState.reduce(function (sum, v) {
      return v === 1 ? sum + 1 : sum;
    }, 0);
  }

  initState();

  /* ---------------------------------------------------------
     Helpers for navigation through unsolved questions
  --------------------------------------------------------- */
  function firstUnsolvedIndex() {
    for (var i = 0; i < totalQuestions; i++) {
      if (missionState[i] !== 1) return i;
    }
    return -1;
  }

  function nextUnsolvedIndex(afterIndex) {
    for (var i = afterIndex + 1; i < totalQuestions; i++) {
      if (missionState[i] !== 1) return i;
    }
    return -1;
  }

  function countAnswered() {
    var c = 0;
    for (var i = 0; i < missionState.length; i++) {
      if (missionState[i] !== 0) c++;
    }
    return c;
  }

   /* ---------------------------------------------------------
     Progress UI
  --------------------------------------------------------- */
  function updateHeroProgress() {
    if (heroProgressTextEl && heroProgressFillEl) {
      heroProgressTextEl.textContent =
        correctCount + '/' + totalQuestions + ' correct';
      var pct = (correctCount / totalQuestions) * 100;
      heroProgressFillEl.style.width = pct + '%';
    }

    // NEW: keep the hero badge in sync
    updateHeroTierUI();
  }

  function updateOverlayProgress() {
    if (!progressTextEl || !progressFillEl) return;
    var answeredSoFar = countAnswered();
    var currentNum = Math.min(answeredSoFar + 1, totalQuestions);
    progressTextEl.textContent = 'Question ' + currentNum + ' of ' + totalQuestions;
    var pct = (answeredSoFar / totalQuestions) * 100;
    progressFillEl.style.width = pct + '%';
  }

  /* ---------------------------------------------------------
     Hero badge UI — shows current tier in the mission hero
  --------------------------------------------------------- */
  function updateHeroTierUI() {
    if (!heroTierPill || !heroStatusText) return;

    var answered = countAnswered();
    var percent  = totalQuestions > 0
      ? Math.round((correctCount / totalQuestions) * 100)
      : 0;

    // Not started at all
    if (correctCount === 0 && answered === 0) {
      heroTierPill.className = 'hero-mission-tier hero-mission-tier--none';
      heroTierPill.innerHTML =
        '<span class="hero-mission-tier-icon">🌑</span>' +
        '<span class="hero-mission-tier-label">Not started</span>';

      heroStatusText.textContent =
        'Tap “Start mission” to begin this galaxy challenge.';
      return;
    }

    // Some progress → map to tier
    var tier   = getMissionTier(percent);
    var label  = '';
    var hint   = '';
    var klass  = '';
    var icon   = '';

    if (tier === 3) {
      label = 'Galaxy Ace';
      icon  = '🌟';
      hint  = 'Mission complete! You can still replay to stay sharp.';
      klass = 'hero-mission-tier--ace';
    } else if (tier === 2) {
      label = 'Star Explorer';
      icon  = '🚀';
      hint  = 'You’re close to Galaxy Ace — replay to boost your score.';
      klass = 'hero-mission-tier--star';
    } else {
      label = 'Space Cadet';
      icon  = '✨';
      hint  = 'Keep practising this mission to earn more stars.';
      klass = 'hero-mission-tier--cadet';
    }

    heroTierPill.className = 'hero-mission-tier ' + klass;
    heroTierPill.innerHTML =
      '<span class="hero-mission-tier-icon">' + icon + '</span>' +
      '<span class="hero-mission-tier-label">' + label + '</span>';

    heroStatusText.textContent = hint;
  }
  
  /* ---------------------------------------------------------
     Feedback renderer (icon + message)
  --------------------------------------------------------- */
  function showFeedback(isCorrect) {
    if (!qFeedbackEl) return;
    qFeedbackEl.innerHTML = '';

    var icon = document.createElement('span');
    icon.className =
      'mission-feedback-icon ' +
      (isCorrect ? 'mission-feedback-icon--correct' : 'mission-feedback-icon--incorrect');
    icon.textContent = isCorrect ? '✔' : '✖';

    var msg = document.createElement('span');
    msg.textContent = isCorrect
      ? 'Correct!'
      : 'Not quite.';

    qFeedbackEl.appendChild(icon);
    qFeedbackEl.appendChild(msg);
  }

  /* ---------------------------------------------------------
     Core: Render current question
     --------------------------------------------------------- */
  function renderQuestion() {
    var qEl = missionQuestions[currentIndex];
    if (!qEl) return;

    isLocked = false;

    // Reset overlay content
    if (qChoicesEl) qChoicesEl.innerHTML = '';
    if (qFeedbackEl) qFeedbackEl.textContent = '';
    if (completeMsgEl) completeMsgEl.textContent = '';

    // Basic prompt: use h4, or h3, or first <p>
    var prompt =
      qEl.querySelector('h4') ||
      qEl.querySelector('h3') ||
      qEl.querySelector('p');

    if (qTextEl) {
      qTextEl.textContent = prompt ? prompt.textContent.trim() : '';
    }

    updateOverlayProgress();

    // Use the shared initializer to wire interaction for this question
    if (!window.nglInitMissionQuestion) {
      if (qChoicesEl) {
        qChoicesEl.textContent = 'Mission question engine is not available.';
      }
      return;
    }

    window.nglInitMissionQuestion(qEl, function (isCorrect) {
      if (isLocked) return;
      isLocked = true;

      // Update state for this question: 1 = correct, 0 = still wrong/unfinished
      var wasCorrect = missionState[currentIndex] === 1;
      missionState[currentIndex] = isCorrect ? 1 : 0;

      // Only bump correctCount if this question wasn't already correct before
      if (isCorrect && !wasCorrect) {
        correctCount += 1;
      }

      showFeedback(!!isCorrect);
      updateHeroProgress();
      saveState();

      // Find next unsolved question
      var nextIndex = nextUnsolvedIndex(currentIndex);

      if (nextIndex === -1) {
        // No more unsolved questions → mission complete
        var percent = totalQuestions > 0
          ? Math.round((correctCount / totalQuestions) * 100)
          : 0;
        var tier = getMissionTier(percent);

        if (progressTextEl) {
          progressTextEl.textContent =
            'Question ' + totalQuestions + ' of ' + totalQuestions;
        }
        if (progressFillEl) {
          progressFillEl.style.width = '100%';
        }

        if (completeMsgEl) {
          renderTierCompletion(tier, correctCount, totalQuestions, completeMsgEl);
        }

        // ✅ no retryBtn display here anymore
        celebrateMission(tier);
        saveState();
        return;
      }

      // Otherwise, move on to the next unsolved one
      setTimeout(function () {
        currentIndex = nextIndex;
        renderQuestion();
      }, 800);
    });
  }

  /* ---------------------------------------------------------
     Open & close mission overlay
     --------------------------------------------------------- */
  function openMission() {
    // Always start at first unsolved question
    var idx = firstUnsolvedIndex();

    if (idx === -1) {
      // Everything already correct → show final tier + celebration
      var percent = totalQuestions > 0
        ? Math.round((correctCount / totalQuestions) * 100)
        : 0;
      var tier = getMissionTier(percent);

      if (qTextEl) {
        qTextEl.textContent = 'All mission questions are done!';
      }
      if (qChoicesEl) qChoicesEl.innerHTML = '';
      if (qFeedbackEl) qFeedbackEl.textContent = '';

      if (progressTextEl) {
        progressTextEl.textContent =
          'Question ' + totalQuestions + ' of ' + totalQuestions;
      }
      if (progressFillEl) {
        progressFillEl.style.width = '100%';
      }

      if (completeMsgEl) {
        renderTierCompletion(tier, correctCount, totalQuestions, completeMsgEl);
      }
      if (retryBtn) retryBtn.style.display = 'none';

      celebrateMission(tier);

      if (window.nglMissionOverlays) {
        window.nglMissionOverlays.open(overlay);
      }
      return;
    }

    currentIndex = idx;
    renderQuestion();

    if (window.nglMissionOverlays) {
      window.nglMissionOverlays.open(overlay);
    }
  }

  function closeMission() {
    if (window.nglMissionOverlays) {
      window.nglMissionOverlays.close(overlay);
    }
  }

  /* ---------------------------------------------------------
     Wiring: buttons
  --------------------------------------------------------- */
  if (startBtn) {
    // Start mission → ONLY wrong / unfinished questions
    startBtn.addEventListener('click', function () {
      openMission();
    });
  }

  if (retryBtn) {
    // Retry button inside overlay behaves just like Start:
    // it keeps the saved state and replays only unsolved ones.
    retryBtn.addEventListener('click', function () {
      openMission();
    });
  }

  if (closeBtn) {
    closeBtn.addEventListener('click', closeMission);
  }
  if (closeIcon) {
    closeIcon.addEventListener('click', closeMission);
  }

  // Reset mission completely from hero
  if (resetHeroBtn) {
    resetHeroBtn.addEventListener('click', function () {
      missionState = new Array(totalQuestions).fill(0);
      correctCount = 0;
      saveState();

      // Reset hero UI + overlay progress text
      if (progressTextEl) {
        progressTextEl.textContent = 'Question 1 of ' + totalQuestions;
      }
      if (progressFillEl) {
        progressFillEl.style.width = '0%';
      }
      if (qFeedbackEl) qFeedbackEl.textContent = '';
      if (completeMsgEl) completeMsgEl.textContent = '';
      if (retryBtn) retryBtn.style.display = 'none';

      updateHeroProgress();
    });
  }

  // Initialize hero bar from saved state on page load
  updateHeroProgress();
})();

/* ===============================
   Mission Briefing & Training Overlays (Lesson 81)
   =============================== */
(function () {
  var body = document.body;
  if (!body || body.getAttribute('data-page') !== 'l1-lesson-81-math') return;
  if (!window.nglMissionOverlays) return;

  var briefingBtn = document.getElementById('missionBriefingBtn');
  var trainingBtn = document.getElementById('trainingRunBtn');

  // Create overlays
  var briefingOverlay = document.createElement('div');
  var trainingOverlay = document.createElement('div');

  briefingOverlay.className = 'mission-overlay';
  trainingOverlay.className = 'mission-overlay';

  briefingOverlay.setAttribute('aria-hidden', 'true');
  trainingOverlay.setAttribute('aria-hidden', 'true');

  briefingOverlay.innerHTML = `
    <div class="mission-panel" role="dialog" aria-label="Mission Briefing">
      <button type="button" class="mission-close" data-close-briefing aria-label="Close mission briefing">✖</button>
      <div class="mission-content" id="briefingContent"></div>
    </div>
  `;

  trainingOverlay.innerHTML = `
    <div class="mission-panel" role="dialog" aria-label="Training Run">
      <button type="button" class="mission-close" data-close-training aria-label="Close training run">✖</button>
      <div class="mission-content" id="trainingContent"></div>
    </div>
  `;

  body.appendChild(briefingOverlay);
  body.appendChild(trainingOverlay);

  // Register with shared manager
  window.nglMissionOverlays.register(briefingOverlay);
  window.nglMissionOverlays.register(trainingOverlay);

  var briefingContent = briefingOverlay.querySelector('#briefingContent');
  var trainingContent = trainingOverlay.querySelector('#trainingContent');

  // Find original sections
  var briefingSection = document.querySelector('section.section-card[data-lesson-part="mini"]');
  var trainingSection = document.querySelector('section.section-card[data-lesson-part="practice"]');

  // Clone into overlays + hide originals
  if (briefingSection && briefingContent) {
    var bc = briefingSection.cloneNode(true);
    bc.style.display = 'block';
    briefingContent.appendChild(bc);
    briefingSection.style.display = 'none';
  }

  if (trainingSection && trainingContent) {
    var tc = trainingSection.cloneNode(true);
    tc.style.display = 'block';
    trainingContent.appendChild(tc);
    trainingSection.style.display = 'none';

    // ✅ Re-init tracing pad INSIDE the training overlay (if present)
    var clonedPad = tc.querySelector('.trace-pad');
    if (clonedPad && window.nglInitTracePad) {
      window.nglInitTracePad(clonedPad);
    }
  }

  function openBriefing() {
    window.nglMissionOverlays.open(briefingOverlay);
  }
  function closeBriefing() {
    window.nglMissionOverlays.close(briefingOverlay);
  }

  function openTraining() {
    window.nglMissionOverlays.open(trainingOverlay);
  }
  function closeTraining() {
    window.nglMissionOverlays.close(trainingOverlay);
  }

  // Wire hero buttons
  if (briefingBtn) {
    briefingBtn.addEventListener('click', openBriefing);
  }
  if (trainingBtn) {
    trainingBtn.addEventListener('click', openTraining);
  }

  // Wire close buttons
  var briefingClose = briefingOverlay.querySelector('[data-close-briefing]');
  var trainingClose = trainingOverlay.querySelector('[data-close-training]');

  if (briefingClose) {
    briefingClose.addEventListener('click', closeBriefing);
  }
  if (trainingClose) {
    trainingClose.addEventListener('click', closeTraining);
  }
})();

