/* chem8_engine.js — общий движок интерактивных учебников «Химия 8».
 *
 * Воспроизводит каркас учебников физики: SPA с para-selector, ленивая сборка §,
 * карточки теории (makeCard), тренажёр задач (числовой ввод + MCQ), sidebar-шпаргалка,
 * прогресс/XP/уровни/достижения, серверная синхронизация прогресса, тема.
 *
 * Страница главы ОБЪЯВЛЯЕТ данные (до загрузки движка, инлайн-скриптом):
 *   window.CHEM8_CFG     = { slug, themeKey, xpKey, progKey, achKey, hubHref }
 *   window.PARAS         = [{id, num, name, sub, final?}]
 *   window.BUILDERS      = { p1: ()=>build_p1(), ... }   // наполняют #<id>-body
 *   window.POOLS         = { p1: [task,...], ... }        // task: {q,hint,unit,a,ex,tol} | {q,opts,a,ex}
 *   window.SIDEBARS      = { p1: {title, rows:[[k,v],...]}, ... }
 *   window.TIPS          = [{sec, html}, ...]
 *   window.CHEM8_WIDGETS = { p1: ()=>add_p1(), ... }      // монтаж виджетов §
 *   window.FLAG_MOUNTS   = { p6: ()=>mountFlag('p6'), ... } // флагман-интерактивы
 *   window.ACH_LABELS    = { start, p1_done, ... }
 *
 * Движок ЭКСПОРТИРУЕТ на window: goTo, checkNum, selectMcq, nextTask, goToTask,
 * resetTasks, makeCard, secNav, readButton, addXp, achievement, bumpProgress.
 * Инициализация — на DOMContentLoaded.
 */
(function (W) {
  'use strict';

  // Конфиг резолвится лениво в init() — страница задаёт window.CHEM8_CFG
  // в body-скрипте, который при defer выполняется до движка, но не полагаемся на это.
  var CFG = {}, SLUG = 'chemistry-8';
  var K = { theme: 'chemistry8_theme', xp: 'chemistry8_xp', prog: 'chemistry-8_progress', ach: 'chemistry-8_ach' };
  function resolveCfg() {
    CFG = W.CHEM8_CFG || {};
    SLUG = CFG.slug || 'chemistry-8';
    K = {
      theme: CFG.themeKey || 'chemistry8_theme',
      xp:    CFG.xpKey    || 'chemistry8_xp',
      prog:  CFG.progKey  || (SLUG + '_progress'),
      ach:   CFG.achKey   || (SLUG + '_ach')
    };
  }
  function PARAS()  { return W.PARAS || []; }
  function POOLS()  { return W.POOLS || {}; }
  function BUILDERS(){ return W.BUILDERS || {}; }
  function ACHL()   { return W.ACH_LABELS || {}; }

  var STATE = { current: null, progress: {}, achievements: new Map(), xp: 0, level: 1 };
  var SEC = {};   // STATE задач по секциям

  /* ── XP / уровни ───────────────────────────────────────────────── */
  function calcLevel(xp) { return Math.floor(Math.sqrt((xp || 0) / 100)) + 1; }
  function xpForLevel(lv) { return (lv - 1) * (lv - 1) * 100; }

  function loadProgress() {
    try {
      var s = localStorage.getItem(K.prog); if (s) Object.assign(STATE.progress, JSON.parse(s));
      var a = localStorage.getItem(K.ach);
      if (a) { var p = JSON.parse(a); if (p && typeof p === 'object') for (var id in p) STATE.achievements.set(id, p[id]); }
      STATE.xp = parseInt(localStorage.getItem(K.xp) || '0', 10) || 0;
      STATE.level = calcLevel(STATE.xp);
    } catch (e) {}
  }
  function saveProgress() {
    try {
      localStorage.setItem(K.prog, JSON.stringify(STATE.progress));
      localStorage.setItem(K.ach, JSON.stringify(mapToObj(STATE.achievements)));
      localStorage.setItem(K.xp, String(STATE.xp));
    } catch (e) {}
  }
  function mapToObj(m) { var o = {}; m.forEach(function (v, k) { o[k] = v; }); return o; }

  function addXp(n, src) {
    if (!n) return;
    var prev = STATE.level;
    STATE.xp = Math.max(0, (STATE.xp || 0) + n); STATE.level = calcLevel(STATE.xp);
    saveProgress(); refreshUI();
    try { if (W.LS && W.LS.xp && W.LS.xp.add) W.LS.xp.add(n, SLUG + '-' + (src || 'x')); } catch (e) {}
    if (STATE.level > prev) popup('Уровень ' + STATE.level + '!');
  }
  function bumpProgress(key, delta) {
    STATE.progress[key] = Math.max(0, Math.min(100, (STATE.progress[key] || 0) + delta));
    saveProgress(); refreshUI();
    if (STATE.progress[key] >= 50) markServerRead(key);
  }
  function achievement(id, text) {
    if (STATE.achievements.has(id)) return;
    var label = text || ACHL()[id] || id;
    STATE.achievements.set(id, label); saveProgress();
    popup(label, true);
    addXp(20, 'ach-' + id);
  }

  /* ── серверная синхронизация ───────────────────────────────────── */
  var _marked = {}, _pending = null, _timer = null;
  function _flush() {
    var body = _pending; _pending = null; if (!body) return;
    var tok = (W.LS && W.LS.getToken) ? W.LS.getToken() : ''; if (!tok) return;
    fetch('/api/textbooks/' + SLUG + '/progress', {
      method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + tok },
      body: JSON.stringify(body), keepalive: true
    }).catch(function () {});
  }
  function _queue(p) { _pending = Object.assign(_pending || {}, p); if (_timer) clearTimeout(_timer); _timer = setTimeout(_flush, 600); }
  function markServerRead(id) { if (_marked[id] || /^final/.test(id)) return; _marked[id] = 1; _queue({ mark_read: id }); }
  function markLastPara(id) { _queue({ last_para: id }); }
  function loadServerReadState() {
    var tok = (W.LS && W.LS.getToken) ? W.LS.getToken() : ''; if (!tok) return;
    fetch('/api/textbooks/' + SLUG, { headers: { 'Authorization': 'Bearer ' + tok } })
      .then(function (r) { return r.ok ? r.json() : null; })
      .then(function (d) {
        if (!d || !d.progress || !d.progress.read) return;
        d.progress.read.forEach(function (k) { _marked[k] = 1; if ((STATE.progress[k] || 0) < 50) STATE.progress[k] = 100; });
        saveProgress(); refreshUI();
      }).catch(function () {});
  }
  W.addEventListener('beforeunload', _flush);

  /* ── popup ачивки / уровня ─────────────────────────────────────── */
  function popup(text, gold) {
    var pop = document.getElementById('ach-popup'); if (!pop) return;
    var t = document.getElementById('ach-text'); if (t) t.textContent = text;
    pop.classList.toggle('gold', !!gold);
    pop.classList.add('show'); setTimeout(function () { pop.classList.remove('show'); }, 3000);
    if (gold) { try { if (W.confetti) W.confetti({ particleCount: 160, spread: 95, origin: { y: .65 } }); } catch (e) {} }
  }

  /* ── para-selector + hero ──────────────────────────────────────── */
  function buildParaSelector() {
    var g = document.getElementById('psel-grid'); if (!g) return;
    g.innerHTML = '';
    PARAS().forEach(function (p) {
      var card = document.createElement('div');
      card.className = 'psel-card' + (p.final ? ' final' : '');
      card.dataset.id = p.id; card.dataset.progCard = p.id;
      card.innerHTML = '<div class="psel-num">' + p.num + '</div><div class="psel-name">' + p.name + '</div>'
        + (p.sub ? '<div class="psel-sub">' + p.sub + '</div>' : '')
        + '<div class="psel-prog"><div class="psel-prog-fill"></div></div>'
        + '<span class="psel-done"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>';
      card.addEventListener('click', function () { goTo(p.id); });
      g.appendChild(card);
    });
    if (W.renderMathInElement) try { renderMath(g); } catch (e) {}
  }

  function refreshUI() {
    var total = PARAS().length || 1;
    var sum = 0; PARAS().forEach(function (p) { sum += (STATE.progress[p.id] || 0); });
    var pct = Math.round(sum / total);
    var hf = document.getElementById('hero-hp-fill'); if (hf) hf.style.width = pct + '%';
    var ht = document.getElementById('hero-hp-text'); if (ht) ht.textContent = pct + '%';
    var xb = document.getElementById('hero-xp-badge');
    if (xb) xb.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. ' + STATE.level + ' \xb7 ' + (STATE.xp || 0) + ' XP';
    document.querySelectorAll('.psel-card').forEach(function (c) {
      var id = c.dataset.id; var pp = STATE.progress[id] || 0;
      var fl = c.querySelector('.psel-prog-fill'); if (fl) fl.style.width = pp + '%';
      c.classList.toggle('done', pp >= 50);
    });
    if (STATE.current && document.getElementById('sidebar-content')) { try { buildSidebar(STATE.current); } catch (e) {} }
  }

  /* ── ленивая сборка § + инъекция задач ─────────────────────────── */
  var BUILT = {};
  function ensureBuilt(id) {
    if (BUILT[id]) return;
    var fn = BUILDERS()[id];
    if (fn) { try { fn(); } catch (e) { if (W.console) console.warn('build ' + id, e.message); } BUILT[id] = 1; }
    _injectTasks(id);
    _mountWidgets(id);
  }
  function _mountWidgets(id) {
    setTimeout(function () {
      try { if (W.CHEM8_WIDGETS && W.CHEM8_WIDGETS[id]) W.CHEM8_WIDGETS[id](); } catch (e) { if (W.console) console.warn('widget ' + id, e.message); }
      try { if (W.FLAG_MOUNTS && W.FLAG_MOUNTS[id]) W.FLAG_MOUNTS[id](); } catch (e) { if (W.console) console.warn('flag ' + id, e.message); }
    }, 40);
  }
  function _makeTaskBlock(sec) {
    return '<div class="legacy-tasks" id="ptab-' + sec + '">'
      + '<div class="lt-head"><span class="lt-title">Задачи параграфа</span>'
      + '<span class="chip chip-ok"><span id="ok' + sec + '">0</span> верно</span>'
      + '<span class="chip chip-tot"><span id="cur' + sec + '">0</span>/<span id="max' + sec + '">?</span></span>'
      + '<button class="btn lt-reset" onclick="resetTasks(\'' + sec + '\')">Заново</button></div>'
      + '<div class="prog-wrap"><div id="prog' + sec + '" class="prog-fill"></div></div>'
      + '<div class="nav-dots" id="navDots' + sec + '"></div>'
      + '<div id="taskArea' + sec + '"></div>'
      + '<div class="feedback" id="fb' + sec + '"></div>'
      + '<div class="lt-foot"><button class="btn primary" id="nextBtn' + sec + '" onclick="nextTask(\'' + sec + '\')" style="display:none">Следующая &rarr;</button></div>'
      + '<div class="summary" id="sum' + sec + '"><div class="sum-t">Параграф пройден!</div><div class="big-score" id="sumScore' + sec + '"></div><div class="sum-grade" id="sumGrade' + sec + '"></div></div>'
      + '</div>';
  }
  function _injectTasks(id) {
    var pool = POOLS()[id]; if (!pool) return;
    var body = document.getElementById(id + '-body'); if (!body || body.querySelector('.legacy-tasks')) return;
    if (!SEC[id]) SEC[id] = { idx: 0, results: pool.map(function () { return null; }), selections: pool.map(function () { return null; }), answered: false };
    body.insertAdjacentHTML('beforeend', _makeTaskBlock(id));
    setTimeout(function () { try { renderTask(id); } catch (e) {} }, 50);
  }

  /* ── навигация по § ────────────────────────────────────────────── */
  function goTo(id) {
    STATE.current = id; ensureBuilt(id);
    document.querySelectorAll('.sec').forEach(function (s) { s.classList.remove('active'); });
    var el = document.getElementById('sec-' + id); if (el) el.classList.add('active');
    document.querySelectorAll('.psel-card').forEach(function (c) { c.classList.toggle('active', c.dataset.id === id); });
    buildSidebar(id);
    try { W.scrollTo({ top: 0, behavior: 'smooth' }); } catch (e) {}
    if ((STATE.progress[id] || 0) < 10) bumpProgress(id, 10);
    if (W.renderMathInElement && el) setTimeout(function () { renderMath(el); }, 0);
    markLastPara(id);
  }

  /* ── sidebar ───────────────────────────────────────────────────── */
  function buildSidebar(id) {
    var box = document.getElementById('sidebar-content'); if (!box) return;
    var SB = W.SIDEBARS || {}; var sb = SB[id] || SB[(PARAS()[0] || {}).id] || { title: '', rows: [] };
    var xpLv = xpForLevel(STATE.level), xpNext = xpForLevel(STATE.level + 1);
    var pct = (xpNext - xpLv) > 0 ? Math.round((STATE.xp - xpLv) / (xpNext - xpLv) * 100) : 100;
    var html = '<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. ' + STATE.level + '</span></div>'
      + '<div class="xp-bar"><div class="xp-fill" style="width:' + pct + '%"></div></div>'
      + '<div class="xp-nums"><span>' + STATE.xp + ' XP</span><span>' + xpNext + ' XP</span></div></div>';
    html += '<div class="sidecard"><h4>' + sb.title + '</h4>';
    sb.rows.forEach(function (r) { html += '<div class="sidecard-row"><b>' + r[0] + '</b>' + (r[1] ? ' — ' + r[1] : '') + '</div>'; });
    html += '</div>';
    var tips = W.TIPS || []; var tip = tips.filter(function (t) { return t.sec === id; })[0] || tips[0];
    if (tip) html += '<div class="sidecard tip"><h4><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 22 20 2 20"/></svg> Подсказка</h4><div class="sidecard-row" style="font-size:.84rem;line-height:1.55">' + tip.html + '</div></div>';
    if (STATE.achievements.size > 0) {
      html += '<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">' + STATE.achievements.size + '</span></h4>';
      var vals = []; STATE.achievements.forEach(function (v) { vals.push(v); });
      vals.slice(-4).forEach(function (t) { html += '<div class="sidecard-row done">✓ ' + t + '</div>'; });
      html += '</div>';
    }
    box.innerHTML = html;
    if (W.renderMathInElement) try { renderMath(box); } catch (e) {}
  }

  /* ── карточки / навигация / кнопка прочтения ───────────────────── */
  var ICONS = {
    theory: '<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
    example: '<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
    rule: '<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
    lab: '<svg class="ic" viewBox="0 0 24 24"><path d="M10 2v7.5L4.5 19a2 2 0 0 0 1.7 3h11.6a2 2 0 0 0 1.7-3L14 9.5V2"/><line x1="9" y1="2" x2="15" y2="2"/></svg>'
  };
  function makeCard(kind, title, num, body) {
    var labels = { theory: 'Теория', example: 'Пример', rule: 'Правило', lab: 'Практика' };
    return '<div class="card"><div class="card-header"><div class="card-icon ' + kind + '">' + (ICONS[kind] || ICONS.theory) + '</div>'
      + '<div class="card-title">' + (labels[kind] || '') + (title && title !== labels[kind] ? ' \xb7 ' + title : '') + '</div>'
      + (num ? '<div class="card-num">' + num + '</div>' : '') + '</div><div class="card-body">' + body + '</div></div>';
  }
  function paraName(id) { var p = PARAS().filter(function (x) { return x.id === id; })[0]; return p ? p.num : id; }
  function secNav(prev, next) {
    var h = '<div class="sec-nav">';
    h += prev ? '<button class="btn" onclick="goTo(\'' + prev + '\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> ' + paraName(prev) + '</button>' : '<span></span>';
    h += next ? '<button class="btn primary" onclick="goTo(\'' + next + '\')">' + paraName(next) + ' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>' : '<span></span>';
    return h + '</div>';
  }
  function readButton(paraId) {
    var p = PARAS().filter(function (x) { return x.id === paraId; })[0];
    var tail = p && p.final ? 'финал' : (p ? p.num : '?');
    return '<div class="read-wrap"><button class="btn primary" id="' + paraId + '-read-btn">'
      + '<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg> Я изучил — ' + tail + ' (+10 XP)</button></div>';
  }
  function wireReadBtn(paraId) {
    var btn = document.getElementById(paraId + '-read-btn'); if (!btn || btn._wired) return; btn._wired = 1;
    btn.addEventListener('click', function () {
      addXp(10, paraId + '-read'); bumpProgress(paraId, 30);
      btn.textContent = 'Изучено! +10 XP'; btn.disabled = true; btn.style.opacity = .6;
      var aId = paraId + '_done'; if (ACHL()[aId]) achievement(aId);
    });
  }

  function renderMath(root) {
    if (!W.renderMathInElement) return;
    try { W.renderMathInElement(root, { delimiters: [{ left: '$$', right: '$$', display: true }, { left: '$', right: '$', display: false }], throwOnError: false }); } catch (e) {}
  }
  function doRender(el) { renderMath(el); }

  /* ── ДВИЖОК ЗАДАЧ ──────────────────────────────────────────────── */
  function renderTask(sec) {
    var pool = POOLS()[sec], s = SEC[sec];
    var area = document.getElementById('taskArea' + sec), fb = document.getElementById('fb' + sec), sum = document.getElementById('sum' + sec);
    if (!area || !fb || !sum || !pool || !s) return;
    sum.classList.remove('show');
    var q = pool[s.idx], done = s.results[s.idx] !== null, isMcq = !!q.opts;
    s.answered = done;
    if (isMcq) {
      var selIdx = s.selections[s.idx];
      area.innerHTML = '<div class="task-card"><div class="task-num">Задача ' + (s.idx + 1) + ' из ' + pool.length + ' · Тест</div>'
        + '<div class="task-text">' + q.q + '</div><div class="mcq-opts">'
        + q.opts.map(function (opt, i) {
            var cls = 'mcq-opt'; if (done) { if (i === q.a) cls += ' mcq-cor'; else if (i === selIdx) cls += ' mcq-wrong'; }
            return '<button class="' + cls + '" id="mcqOpt' + sec + '_' + i + '" onclick="' + (done ? '' : 'selectMcq(\'' + sec + '\',' + i + ')') + '" ' + (done ? 'disabled' : '') + '><span class="mcq-let">' + String.fromCharCode(65 + i) + '.</span>' + opt + '</button>';
          }).join('') + '</div></div>';
    } else {
      area.innerHTML = '<div class="task-card"><div class="task-num">Задача ' + (s.idx + 1) + ' из ' + pool.length + '</div>'
        + '<div class="task-text">' + q.q + '</div>'
        + (q.hint ? '<div class="task-hint"><svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg><span>' + q.hint + '</span></div>' : '')
        + '<div class="ans-row"><label>Ответ:</label><input class="ans-inp" type="text" id="ainp' + sec + '" placeholder="?" autocomplete="off"' + (done ? ' disabled' : '') + '>'
        + '<span class="unit-lbl">' + (q.unit || '') + '</span>'
        + (done ? '' : '<button class="btn primary" onclick="checkNum(\'' + sec + '\')">Проверить</button>') + '</div></div>';
    }
    if (done) {
      var ok = s.results[s.idx];
      fb.className = 'feedback show ' + (ok ? 'fb-ok' : 'fb-fail');
      fb.innerHTML = isMcq
        ? (ok ? 'Верно! ' + (q.ex || '') : 'Неверно. Правильный ответ: <b>' + q.opts[q.a] + '</b>. ' + (q.ex || ''))
        : (ok ? 'Верно! ' + (q.ex || '') : 'Неверно. Правильный ответ: <b>' + q.a + ' ' + (q.unit || '') + '</b>. ' + (q.ex || ''));
      var nb = document.getElementById('nextBtn' + sec); if (nb) nb.style.display = 'inline-flex';
      doRender(fb);
    } else { fb.className = 'feedback'; var nb2 = document.getElementById('nextBtn' + sec); if (nb2) nb2.style.display = 'none'; }
    updateScoreBar(sec); renderNav(sec); doRender(area);
    if (!done && !isMcq) {
      var inp = document.getElementById('ainp' + sec);
      // preventScroll: иначе фокус прокручивает страницу к блоку задач (внизу §)
      setTimeout(function () { if (inp) { try { inp.focus({ preventScroll: true }); } catch (e) { inp.focus(); } } }, 80);
      if (inp) inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') checkNum(sec); });
    }
  }

  function selectMcq(sec, i) {
    var s = SEC[sec]; if (!s || s.answered) return;
    var q = POOLS()[sec][s.idx], ok = i === q.a;
    s.results[s.idx] = ok; s.selections[s.idx] = i; s.answered = true;
    if (ok) maybeAwardTask(sec);
    q.opts.forEach(function (_, j) {
      var btn = document.getElementById('mcqOpt' + sec + '_' + j); if (!btn) return;
      btn.disabled = true; if (j === q.a) btn.classList.add('mcq-cor'); else if (j === i && !ok) btn.classList.add('mcq-wrong');
    });
    var fb = document.getElementById('fb' + sec);
    fb.className = 'feedback show ' + (ok ? 'fb-ok' : 'fb-fail');
    fb.innerHTML = ok ? 'Верно! ' + (q.ex || '') : 'Неверно. Правильный ответ: <b>' + q.opts[q.a] + '</b>. ' + (q.ex || '');
    doRender(fb);
    var nb = document.getElementById('nextBtn' + sec); if (nb) nb.style.display = 'inline-flex';
    updateScoreBar(sec); renderNav(sec); finishCheck(sec);
  }

  function checkNum(sec) {
    var s = SEC[sec]; if (!s || s.answered) return;
    var q = POOLS()[sec][s.idx], inp = document.getElementById('ainp' + sec), fb = document.getElementById('fb' + sec);
    var val = (inp.value || '').trim().replace(',', '.'), num = parseFloat(val);
    if (!val || isNaN(num)) { fb.className = 'feedback show fb-fail'; fb.innerHTML = 'Введите числовой ответ!'; return; }
    s.answered = true;
    var tol = q.tol !== undefined ? q.tol : 0.03;
    var ok = q.a === 0 ? Math.abs(num) < 0.05 : Math.abs((num - q.a) / q.a) < tol;
    s.results[s.idx] = ok; if (ok) maybeAwardTask(sec);
    inp.disabled = true; inp.style.borderColor = ok ? 'var(--ok)' : 'var(--fail)';
    fb.className = 'feedback show ' + (ok ? 'fb-ok' : 'fb-fail');
    fb.innerHTML = ok ? 'Верно! ' + (q.ex || '') : 'Неверно. Правильный ответ: <b>' + q.a + ' ' + (q.unit || '') + '</b>. ' + (q.ex || '');
    doRender(fb);
    var nb = document.getElementById('nextBtn' + sec); if (nb) nb.style.display = 'inline-flex';
    updateScoreBar(sec); renderNav(sec); finishCheck(sec);
  }

  function maybeAwardTask(sec) {
    var s = SEC[sec]; if (s._awarded === undefined) s._awarded = {};
    if (s._awarded[s.idx]) return; s._awarded[s.idx] = 1; addXp(5, sec + '-task');
  }
  function finishCheck(sec) {
    var s = SEC[sec];
    if (s.results.every(function (r) { return r !== null; })) setTimeout(function () { showSummary(sec); }, 1600);
  }

  function nextTask(sec) {
    var s = SEC[sec], pool = POOLS()[sec];
    var next = -1;
    for (var k = 1; k <= pool.length; k++) { var j = (s.idx + k) % pool.length; if (s.results[j] === null) { next = j; break; } }
    if (next === -1) { showSummary(sec); return; }
    s.idx = next; s.answered = s.results[next] !== null; renderTask(sec);
  }
  function goToTask(sec, idx) { var s = SEC[sec]; s.idx = idx; s.answered = s.results[idx] !== null; renderTask(sec); }
  function resetTasks(sec) {
    var pool = POOLS()[sec];
    SEC[sec] = { idx: 0, results: pool.map(function () { return null; }), selections: pool.map(function () { return null; }), answered: false, _awarded: {} };
    var sum = document.getElementById('sum' + sec); if (sum) sum.classList.remove('show');
    renderTask(sec);
  }

  function renderNav(sec) {
    var s = SEC[sec], pool = POOLS()[sec], nd = document.getElementById('navDots' + sec); if (!nd) return;
    nd.innerHTML = pool.map(function (_, i) {
      var cls = 'nav-dot'; if (i === s.idx) cls += ' nd-cur'; if (s.results[i] === true) cls += ' nd-ok'; else if (s.results[i] === false) cls += ' nd-fail';
      return '<button class="' + cls + '" onclick="goToTask(\'' + sec + '\',' + i + ')">' + (i + 1) + '</button>';
    }).join('');
  }
  function updateScoreBar(sec) {
    var s = SEC[sec], pool = POOLS()[sec];
    var ok = s.results.filter(function (r) { return r === true; }).length;
    var ans = s.results.filter(function (r) { return r !== null; }).length;
    setTxt('ok' + sec, ok); setTxt('cur' + sec, ans); setTxt('max' + sec, pool.length);
    var pf = document.getElementById('prog' + sec); if (pf) pf.style.width = Math.round(ans / pool.length * 100) + '%';
  }
  function showSummary(sec) {
    var s = SEC[sec], pool = POOLS()[sec], sum = document.getElementById('sum' + sec); if (!sum) return;
    var ok = s.results.filter(function (r) { return r === true; }).length;
    setTxt('sumScore' + sec, ok + ' / ' + pool.length);
    var grade = ok === pool.length ? 'Отлично! Все задачи решены.' : ok >= pool.length * 0.6 ? 'Хорошо! Можно повторить ошибки.' : 'Стоит повторить параграф.';
    setTxt('sumGrade' + sec, grade);
    sum.classList.add('show');
    if (ok === pool.length) { bumpProgress(sec, 60); var aId = sec + '_tasks'; if (ACHL()[aId]) achievement(aId); }
  }
  function setTxt(id, v) { var e = document.getElementById(id); if (e) e.textContent = v; }

  /* ── тема ──────────────────────────────────────────────────────── */
  function initTheme() {
    var t = localStorage.getItem(K.theme) || localStorage.getItem('theme') || 'light';
    if (t === 'dark') document.documentElement.classList.add('dark');
    var lab = document.getElementById('theme-lab'); if (lab) lab.textContent = t === 'dark' ? 'Светлая' : 'Тёмная';
    var btn = document.getElementById('theme-btn'); if (!btn) return;
    btn.addEventListener('click', function () {
      document.documentElement.classList.toggle('dark');
      var d = document.documentElement.classList.contains('dark');
      localStorage.setItem(K.theme, d ? 'dark' : 'light'); localStorage.setItem('theme', d ? 'dark' : 'light');
      if (lab) lab.textContent = d ? 'Светлая' : 'Тёмная';
    });
  }

  /* ── init ──────────────────────────────────────────────────────── */
  function init() {
    resolveCfg();
    loadProgress(); initTheme(); buildParaSelector(); refreshUI();
    if (ACHL().start) achievement('start');
    var first = (PARAS()[0] || {}).id; if (first) goTo(first);
    refreshUI(); loadServerReadState();
    W.addEventListener('focus', loadServerReadState);
  }

  /* экспорт */
  W.goTo = goTo; W.ensureBuilt = ensureBuilt;
  W.checkNum = checkNum; W.selectMcq = selectMcq; W.nextTask = nextTask; W.goToTask = goToTask; W.resetTasks = resetTasks;
  W.renderTask = renderTask;
  W.makeCard = makeCard; W.secNav = secNav; W.readButton = readButton; W.wireReadBtn = wireReadBtn;
  W.addXp = addXp; W.achievement = achievement; W.bumpProgress = bumpProgress; W.chem8RenderMath = renderMath;

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();
})(window);
