/* chem7_ch1_widgets.js — интерактивы главы 1 «Первоначальные химические понятия» (Химия 7).
 * Монтируются движком chem8_engine.js: window.CHEM8_WIDGETS[id] / window.FLAG_MOUNTS[id].
 * Используют window.Chem8 (chem8_svg.js): molarMass, elementCounts, arOf, fmt, equationBalancer.
 * Без эмоджи; KaTeX-рендер — через window.chem8RenderMath.
 */
(function (W) {
  'use strict';
  function C() { return W.Chem8 || {}; }
  function $(id) { return document.getElementById(id); }
  function esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }

  /* ── общий мини-классификатор: чипы → 2 корзины ──────────────────── */
  function classifier(mount, opts) {
    if (!mount || mount._built) return; mount._built = 1;
    var items = opts.items.slice();   // {t, b} t=подпись, b=индекс корзины
    var buckets = opts.buckets;       // [name0, name1]
    var placed = {};                  // idx -> bucket
    var pool = items.map(function (it, i) { return i; });
    function colorOf(ok){ return ok ? '#059669' : '#dc2626'; }
    function render() {
      var chips = pool.map(function (i) {
        return '<button class="c7-chip" data-i="' + i + '" style="padding:7px 12px;border:1.5px solid var(--border);border-radius:9px;background:var(--card);color:var(--text);font-weight:600;font-size:.9rem;cursor:pointer;margin:3px">' + esc(items[i].t) + '</button>';
      }).join('') || '<span style="color:var(--muted)">Все карточки распределены.</span>';
      var cols = buckets.map(function (name, bi) {
        var inb = Object.keys(placed).filter(function (k) { return placed[k] === bi; });
        var cells = inb.map(function (k) {
          var ok = items[k].b === bi;
          return '<div style="padding:5px 10px;margin:3px 0;border-radius:8px;font-size:.86rem;font-weight:600;color:#fff;background:' + colorOf(ok) + '">' + esc(items[k].t) + (ok ? ' &#10003;' : ' &#10007;') + '</div>';
        }).join('') || '<div style="color:var(--muted);font-size:.82rem">перетащите сюда…</div>';
        return '<div class="c7-bucket" data-b="' + bi + '" style="flex:1;min-width:140px;border:1.5px dashed var(--border);border-radius:11px;padding:10px;background:var(--pri-soft)"><div style="font-weight:700;margin-bottom:6px">' + esc(name) + '</div>' + cells + '</div>';
      }).join('');
      mount.innerHTML = '<div style="margin-bottom:10px">' + chips + '</div><div style="display:flex;gap:10px;flex-wrap:wrap">' + cols + '</div>'
        + '<div style="margin-top:8px;font-size:.84rem;color:var(--muted)">Кликни карточку, затем — корзину. Зелёный — верно, красный — ошибка.</div>';
      bind();
    }
    var sel = null;
    function bind() {
      mount.querySelectorAll('.c7-chip').forEach(function (b) {
        b.addEventListener('click', function () {
          mount.querySelectorAll('.c7-chip').forEach(function (x) { x.style.outline = ''; });
          sel = +b.dataset.i; b.style.outline = '2px solid var(--pri)';
        });
      });
      mount.querySelectorAll('.c7-bucket').forEach(function (col) {
        col.addEventListener('click', function () {
          if (sel == null) return;
          placed[sel] = +col.dataset.b;
          pool = pool.filter(function (x) { return x !== sel; });
          sel = null; render();
        });
      });
    }
    render();
  }

  /* §1 — классификатор «тело / вещество» */
  function mount_p1() {
    var m = $('p1-cls'); if (!m) return;
    classifier(m, {
      buckets: ['Физическое тело', 'Вещество'],
      items: [
        { t: 'стакан', b: 0 }, { t: 'вода', b: 1 }, { t: 'гвоздь', b: 0 }, { t: 'железо', b: 1 },
        { t: 'ложка', b: 0 }, { t: 'сахар', b: 1 }, { t: 'медь', b: 1 }, { t: 'линейка', b: 0 }
      ]
    });
  }

  /* §2 / ПР1 — разделитель смесей: выбери метод для смеси */
  var MIX = [
    { mix: 'Песок и вода', method: 'Фильтрование', kind: 'filter', why: 'Песок не растворяется — задерживается фильтром, вода проходит.' },
    { mix: 'Соль и вода', method: 'Выпаривание', kind: 'evaporate', why: 'Вода испаряется, соль остаётся на дне.' },
    { mix: 'Железные опилки и сера', method: 'Магнит', kind: 'magnet', why: 'Железо притягивается магнитом, сера — нет.' },
    { mix: 'Вода и растительное масло', method: 'Отстаивание (делительная воронка)', kind: 'settle', why: 'Масло легче воды и не смешивается — слои разделяют.' },
    { mix: 'Спирт и вода', method: 'Перегонка (дистилляция)', kind: 'distill', why: 'У спирта и воды разные температуры кипения.' }
  ];
  var METHODS = ['Фильтрование', 'Выпаривание', 'Магнит', 'Отстаивание (делительная воронка)', 'Перегонка (дистилляция)'];
  function mount_sep(mountId) {
    var m = $(mountId); if (!m || m._built) return; m._built = 1;
    var idx = 0, anim = null;
    function stopAnim() { if (anim) { anim.stop(); anim = null; } }
    function render() {
      stopAnim();
      var cur = MIX[idx];
      m.innerHTML = '<div class="fld"><label>Смесь</label><select id="' + mountId + '-pick">'
        + MIX.map(function (x, i) { return '<option value="' + i + '"' + (i === idx ? ' selected' : '') + '>' + esc(x.mix) + '</option>'; }).join('') + '</select></div>'
        + '<div style="margin:8px 0;font-weight:600">Каким способом разделить смесь «' + esc(cur.mix) + '»?</div>'
        + '<div style="display:flex;flex-wrap:wrap;gap:6px">' + METHODS.map(function (mt) {
            return '<button class="c7-m btn" data-m="' + esc(mt) + '">' + esc(mt) + '</button>';
          }).join('') + '</div>'
        + '<div class="out" id="' + mountId + '-out" style="margin-top:8px">Выбери способ разделения — при верном ответе увидишь анимацию.</div>'
        + '<div id="' + mountId + '-anim" style="margin-top:8px;display:flex;justify-content:center"></div>';
      $(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; render(); });
      var out = $(mountId + '-out');
      m.querySelectorAll('.c7-m').forEach(function (b) {
        b.addEventListener('click', function () {
          var ok = b.dataset.m === cur.method;
          out.className = 'out ' + (ok ? 'ok' : 'bad');
          out.innerHTML = ok
            ? '<b>Верно!</b> ' + esc(cur.method) + '. ' + esc(cur.why)
            : '<b>Не подходит.</b> Подумай, чем различаются вещества в смеси (растворимость, магнитные свойства, температура кипения, плотность).';
          stopAnim();
          var host = $(mountId + '-anim');
          if (ok && W.Chem7Anim && host) anim = W.Chem7Anim.separation(host, cur.kind);
          else if (host) host.innerHTML = '';
        });
      });
    }
    render();
  }
  function mount_p2() { mount_sep('p2-sep'); }
  function mount_pr1() { mount_sep('pr1-sep'); }

  /* §3 — каталог элементов + тренажёр «символ ↔ название» */
  var EL = {
    H: [1, 'Водород'], He: [2, 'Гелий'], Li: [3, 'Литий'], C: [6, 'Углерод'], N: [7, 'Азот'],
    O: [8, 'Кислород'], F: [9, 'Фтор'], Na: [11, 'Натрий'], Mg: [12, 'Магний'], Al: [13, 'Алюминий'],
    Si: [14, 'Кремний'], P: [15, 'Фосфор'], S: [16, 'Сера'], Cl: [17, 'Хлор'], K: [19, 'Калий'],
    Ca: [20, 'Кальций'], Fe: [26, 'Железо'], Cu: [29, 'Медь'], Zn: [30, 'Цинк'], Ag: [47, 'Серебро']
  };
  function mount_p3() {
    var grid = $('p3-el'), info = $('p3-elinfo');
    if (grid && !grid._built) {
      grid._built = 1;
      Object.keys(EL).forEach(function (s) {
        var ar = C().arOf ? C().arOf(s) : '';
        var c = document.createElement('div'); c.className = 'el-cell';
        c.innerHTML = '<span class="z">' + EL[s][0] + '</span><span class="s">' + s + '</span><span class="a">' + ar + '</span>';
        c.addEventListener('click', function () {
          grid.querySelectorAll('.el-cell').forEach(function (x) { x.classList.remove('on'); }); c.classList.add('on');
          if (info) info.innerHTML = '<b>' + EL[s][1] + '</b> (' + s + ') &middot; порядковый номер Z = ' + EL[s][0] + ' &middot; A_r = ' + ar;
        });
        grid.appendChild(c);
      });
    }
    /* drill: дан символ → выбери название */
    var d = $('p3-drill'); if (d && !d._built) {
      d._built = 1;
      var keys = Object.keys(EL), order = keys.slice(), qi = 0, score = 0, total = 0;
      function nextQ() {
        var sym = order[qi % order.length];
        var correct = EL[sym][1];
        var opts = [correct];
        while (opts.length < 4) { var r = EL[keys[(qi * 7 + opts.length * 13 + 3) % keys.length]][1]; if (opts.indexOf(r) < 0) opts.push(r); }
        // детерминированная перестановка
        opts = opts.sort(function (a, b) { return ((a.length + qi) % 3) - ((b.length + qi) % 3); });
        d.innerHTML = '<div style="font-weight:700;margin-bottom:6px">Какому элементу соответствует символ <span style="font-family:var(--mono);color:var(--pri-d);font-size:1.1rem">' + sym + '</span>?</div>'
          + '<div style="display:flex;flex-wrap:wrap;gap:6px">' + opts.map(function (o) { return '<button class="c7-d btn" data-o="' + esc(o) + '">' + esc(o) + '</button>'; }).join('') + '</div>'
          + '<div class="out" id="p3-drill-out" style="margin-top:8px">Счёт: ' + score + ' из ' + total + '</div>';
        var out = $('p3-drill-out');
        d.querySelectorAll('.c7-d').forEach(function (b) {
          b.addEventListener('click', function () {
            total++; var ok = b.dataset.o === correct; if (ok) score++;
            out.className = 'out ' + (ok ? 'ok' : 'bad');
            out.innerHTML = (ok ? '<b>Верно!</b> ' : '<b>Нет.</b> ') + sym + ' — это ' + correct + '. Счёт: ' + score + ' из ' + total;
            qi++;
            setTimeout(nextQ, 850);
          });
        });
      }
      nextQ();
    }
  }

  /* ── Волна 2 ── */

  /* §4 — «весы атомов»: во сколько раз один атом тяжелее другого */
  var ABEL = ['H','C','N','O','Na','Mg','Al','S','Cl','Ca','Fe','Cu','Zn','Ag'];
  function mount_p4() {
    var m = $('p4-bal'); if (!m || m._built) return; m._built = 1;
    function opts(sel){ return ABEL.map(function(e){ var ar=C().arOf?C().arOf(e):''; return '<option value="'+e+'"'+(e===sel?' selected':'')+'>'+e+' (A_r='+ar+')</option>'; }).join(''); }
    m.innerHTML = '<div class="fld"><label>Атом A</label><select id="p4-a">'+opts('S')+'</select>'
      +'<label>Атом B</label><select id="p4-b">'+opts('O')+'</select></div><div class="out" id="p4-out"></div>';
    function upd(){
      var a=$('p4-a').value, b=$('p4-b').value, ara=+C().arOf(a), arb=+C().arOf(b);
      var out=$('p4-out');
      if(!ara||!arb){ out.textContent='—'; return; }
      var big=Math.max(ara,arb), sm=Math.min(ara,arb), k=big/sm;
      var heavier=ara>=arb?a:b, lighter=ara>=arb?b:a;
      out.className='out ok';
      out.innerHTML='<span class="bd">A_r('+a+')='+ara+', A_r('+b+')='+arb+'<br>'
        +(ara===arb?('Атомы '+a+' и '+b+' имеют одинаковую массу.')
          :('Атом <b>'+heavier+'</b> тяжелее атома '+lighter+' в <b>'+(Math.round(k*100)/100).toString().replace('.',',')+'</b> раз.'))+'</span>';
    }
    $('p4-a').addEventListener('change',upd); $('p4-b').addEventListener('change',upd); upd();
  }

  /* рисуем молекулу как набор шариков-атомов */
  var COL = { H:'#cbd5e1', O:'#ef4444', N:'#3b82f6', C:'#334155', S:'#eab308', Cl:'#22c55e', Fe:'#b45309', Na:'#a78bfa' };
  var RAD = { H:11, O:16, N:15, C:16, S:18, Cl:17, Fe:18, Na:17 };
  function molBalls(atoms) {
    // atoms: [[el,n],...]; рисуем в ряд
    var balls = [], cx = 26;
    atoms.forEach(function(pair){ for(var i=0;i<pair[1];i++){ balls.push(pair[0]); } });
    var W0 = Math.max(120, cx*2 + balls.reduce(function(s,e){return s+(RAD[e]||14)*2+8;},0));
    var x = cx, svg = '';
    balls.forEach(function(el){
      var r = RAD[el]||14; x += r;
      svg += '<circle cx="'+x+'" cy="34" r="'+r+'" fill="'+(COL[el]||'#94a3b8')+'" stroke="rgba(0,0,0,.25)"/>'
        + '<text x="'+x+'" y="39" text-anchor="middle" font-size="13" font-weight="700" fill="#fff">'+el+'</text>';
      x += r + 8;
    });
    return '<svg viewBox="0 0 '+W0+' 68" width="100%" style="max-width:'+W0+'px;height:auto">'+svg+'</svg>';
  }
  function molCard(name, formula, atoms, note) {
    return '<div style="border:1.5px solid var(--border);border-radius:11px;padding:10px 12px;background:var(--card)">'
      +'<div style="font-weight:700;margin-bottom:2px">'+esc(name)+' &middot; '+(C().formula?C().formula(formula):formula)+'</div>'
      + molBalls(atoms)
      +'<div style="font-size:.82rem;color:var(--muted);margin-top:4px">'+esc(note)+'</div></div>';
  }

  /* 3D-модели молекул для §5/§6 (через Chem7Anim.molecule3d) */
  var MOL = {
    H2:  { atoms:[{el:'H',x:-0.7,y:0,z:0},{el:'H',x:0.7,y:0,z:0}], bonds:[[0,1]] },
    O2:  { atoms:[{el:'O',x:-0.75,y:0,z:0},{el:'O',x:0.75,y:0,z:0}], bonds:[[0,1]] },
    O3:  { atoms:[{el:'O',x:0,y:0.45,z:0},{el:'O',x:-1.05,y:-0.4,z:0},{el:'O',x:1.05,y:-0.4,z:0}], bonds:[[0,1],[0,2]] },
    N2:  { atoms:[{el:'N',x:-0.7,y:0,z:0},{el:'N',x:0.7,y:0,z:0}], bonds:[[0,1]] },
    H2O: { atoms:[{el:'O',x:0,y:0,z:0},{el:'H',x:-0.78,y:0.6,z:0},{el:'H',x:0.78,y:0.6,z:0}], bonds:[[0,1],[0,2]] },
    CO2: { atoms:[{el:'C',x:0,y:0,z:0},{el:'O',x:-1.15,y:0,z:0},{el:'O',x:1.15,y:0,z:0}], bonds:[[0,1],[0,2]] },
    CH4: { atoms:[{el:'C',x:0,y:0,z:0},{el:'H',x:0.63,y:0.63,z:0.63},{el:'H',x:-0.63,y:-0.63,z:0.63},{el:'H',x:-0.63,y:0.63,z:-0.63},{el:'H',x:0.63,y:-0.63,z:-0.63}], bonds:[[0,1],[0,2],[0,3],[0,4]] },
    NH3: { atoms:[{el:'N',x:0,y:0.32,z:0},{el:'H',x:0.94,y:-0.3,z:0},{el:'H',x:-0.47,y:-0.3,z:0.82},{el:'H',x:-0.47,y:-0.3,z:-0.82}], bonds:[[0,1],[0,2],[0,3]] }
  };
  function fmlName(k) { return C().formula ? C().formula(k) : k; }
  function molViewer(host, keys, caption) {
    if (!host || host._built) return; host._built = 1;
    var A = W.Chem7Anim;
    if (!A || !A.molecule3d) { host.innerHTML = '<div class="out">3D-модели недоступны.</div>'; return; }
    var cur = keys[0], handle = null;
    function render() {
      if (handle) handle.stop();
      host.innerHTML = '<div class="fld" style="flex-wrap:wrap;gap:6px">'
        + keys.map(function (k) { return '<button class="btn mv-b' + (k === cur ? ' primary' : '') + '" data-k="' + k + '">' + fmlName(k) + '</button>'; }).join('') + '</div>'
        + '<div id="' + host.id + '-stage" style="display:flex;justify-content:center;padding:8px 0"></div>'
        + '<div class="out" style="margin-top:4px">' + caption + ' Перетаскивай модель мышью, чтобы повернуть.</div>';
      handle = A.molecule3d($(host.id + '-stage'), MOL[cur]);
      host.querySelectorAll('.mv-b').forEach(function (b) { b.addEventListener('click', function () { cur = b.dataset.k; render(); }); });
    }
    render();
  }

  /* §5 — 3D-модели простых веществ */
  function mount_p5() { molViewer($('p5-gal'), ['H2', 'O2', 'O3', 'N2'], 'Простое вещество — атомы одного элемента.'); }

  /* §6 — классификатор простое/сложное + 3D-модели сложных веществ */
  function mount_p6() {
    var c = $('p6-cls');
    if (c) classifier(c, {
      buckets: ['Простое вещество', 'Сложное вещество'],
      items: [
        { t:'O₂', b:0 }, { t:'H₂O', b:1 }, { t:'Fe', b:0 }, { t:'CO₂', b:1 },
        { t:'N₂', b:0 }, { t:'NH₃', b:1 }, { t:'S', b:0 }, { t:'CH₄', b:1 }
      ]
    });
    molViewer($('p6-gal'), ['H2O', 'CO2', 'CH4', 'NH3'], 'Сложное вещество — атомы разных элементов.');
  }

  /* ── Волна 3 ── */

  /* §7 — разбор химической формулы на состав */
  function mount_p7() {
    var inp = $('p7-in'), out = $('p7-out'), go = $('p7-go'); if (!inp || inp._built) return; inp._built = 1;
    function calc() {
      var f = inp.value.trim(), cnt = C().elementCounts ? C().elementCounts(f) : null;
      if (!cnt || !Object.keys(cnt).length) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу. Проверь символы элементов (например, H2SO4).'; return; }
      var els = Object.keys(cnt), tot = els.reduce(function (s, e) { return s + cnt[e]; }, 0);
      out.className = 'out ok';
      out.innerHTML = '<span class="bd"><b>' + (C().formula ? C().formula(f) : f) + '</b><br>'
        + 'Элементов: <b>' + els.length + '</b> (' + (els.length === 1 ? 'простое' : 'сложное') + ' вещество)<br>'
        + els.map(function (e) { return e + ': ' + cnt[e] + ' ' + (cnt[e] === 1 ? 'атом' : 'атома(ов)'); }).join('<br>')
        + '<br>Всего атомов в формуле: <b>' + tot + '</b></span>';
    }
    go.addEventListener('click', calc);
    inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); });
    document.querySelectorAll('.p7-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); });
    calc();
  }

  /* §8 — калькулятор относительной молекулярной массы M_r */
  function mount_p8() {
    var inp = $('p8-in'), out = $('p8-out'), go = $('p8-go'); if (!inp || inp._built) return; inp._built = 1;
    function calc() {
      var f = inp.value.trim(), cnt = C().elementCounts ? C().elementCounts(f) : null, mr = C().molarMass ? C().molarMass(f) : NaN;
      if (!cnt || isNaN(mr)) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу.'; return; }
      out.className = 'out ok';
      out.innerHTML = '<span class="bd"><b>M_r(' + f + ') = ' + C().fmt(mr) + '</b><br>'
        + Object.keys(cnt).map(function (e) { return e + ': A_r=' + (C().arOf ? C().arOf(e) : '?') + ' × ' + cnt[e]; }).join(' &nbsp;|&nbsp; ')
        + '<br>Σ = ' + Object.keys(cnt).map(function (e) { return (C().arOf ? C().arOf(e) : '?') + '·' + cnt[e]; }).join(' + ') + ' = ' + C().fmt(mr) + '</span>';
    }
    go.addEventListener('click', calc);
    inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); });
    document.querySelectorAll('.p8-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); });
    calc();
  }

  /* §9 — конструктор формулы по валентности (НОК индексов) */
  function gcd(a, b) { return b ? gcd(b, a % b) : a; }
  var VA = [ ['Na', 1], ['K', 1], ['H', 1], ['Mg', 2], ['Ca', 2], ['Zn', 2], ['Cu', 2], ['Al', 3], ['C', 4] ];
  var VB = [ ['O', 2], ['Cl', 1], ['S', 2] ];
  var BCOL = { O:'#ef4444', Cl:'#22c55e', S:'#eab308' };
  function mount_p9() {
    var m = $('p9-bld'); if (!m || m._built) return; m._built = 1;
    var vanim = null;
    function optA(){ return VA.map(function(e,i){ return '<option value="'+i+'"'+(e[0]==='Al'?' selected':'')+'>'+e[0]+' (валентность '+'I'.repeat(e[1]).replace('IIII','IV')+')</option>'; }).join(''); }
    function optB(){ return VB.map(function(e,i){ return '<option value="'+i+'">'+e[0]+' (валентность '+'I'.repeat(e[1])+')</option>'; }).join(''); }
    m.innerHTML = '<div class="fld"><label>Элемент A</label><select id="p9-a">'+optA()+'</select>'
      +'<label>Элемент B</label><select id="p9-b">'+optB()+'</select></div>'
      +'<div id="p9-vis" style="margin:8px 0;display:flex;justify-content:center"></div>'
      +'<div class="out" id="p9-bout"></div>';
    function upd() {
      var a = VA[+$('p9-a').value], b = VB[+$('p9-b').value];
      var lcm = a[1] * b[1] / gcd(a[1], b[1]);
      var ia = lcm / a[1], ib = lcm / b[1];
      var raw = a[0] + (ia > 1 ? ia : '') + b[0] + (ib > 1 ? ib : '');
      if (vanim) { vanim.stop(); vanim = null; }
      if (W.Chem7Anim) vanim = W.Chem7Anim.valenceLink($('p9-vis'), {
        a: { el:a[0], val:a[1], n:ia, color:'#6366f1' },
        b: { el:b[0], val:b[1], n:ib, color:BCOL[b[0]] || '#ef4444' } });
      var out = $('p9-bout'); out.className = 'out ok';
      out.innerHTML = '<span class="bd">Валентности: ' + a[0] + ' = ' + 'I'.repeat(a[1]).replace('IIII','IV') + ', ' + b[0] + ' = ' + 'I'.repeat(b[1]) + '<br>'
        + 'Каждая чёрточка-связь соединена — все валентности заняты.<br>'
        + 'НОК валентностей = <b>' + lcm + '</b>; индексы: ' + a[0] + ' → ' + ia + ', ' + b[0] + ' → ' + ib + '<br>'
        + 'Формула: <b style="font-size:1.15rem">' + (C().formula ? C().formula(raw) : raw) + '</b></span>';
    }
    $('p9-a').addEventListener('change', upd); $('p9-b').addEventListener('change', upd); upd();
  }

  /* ── Волна 4 ── */

  /* §10 / ЛО1 — детектор признаков химической реакции */
  var DEMOS = [
    { name: 'Нагревание малахита', signs: ['изменение цвета: зелёный → чёрный', 'выделение газа (водяной пар и углекислый газ)'] },
    { name: 'Сливание растворов CuSO₄ и NaOH', signs: ['образование осадка (голубой)', 'изменение цвета раствора'] },
    { name: 'Горение серы', signs: ['выделение света и тепла (пламя)', 'появление резкого запаха'] },
    { name: 'Добавление соды в уксус', signs: ['выделение газа (пузырьки)'] }
  ];
  // анимация на каждый опыт (через Chem7Anim, CSS-хелперы)
  function demoAnim(idx, host) {
    var A = W.Chem7Anim; if (!A || !host) return null;
    if (idx === 0) return A.colorBlock(host, '#16a34a', '#1f2937', 'малахит → CuO + газы', 2000); // зелёный → чёрный
    if (idx === 1) return A.precipField(host, { color: '#38bdf8' });                              // голубой осадок
    if (idx === 2) return A.flameBox(host, { color: '#3b82f6', sparks: true });                   // синее пламя серы
    return A.bubbleField(host, { color: 'rgba(255,255,255,.85)' });                               // пузырьки газа
  }
  function mount_signs(mountId) {
    var m = $(mountId); if (!m || m._built) return; m._built = 1;
    var idx = 0, anim = null;
    function stopAnim() { if (anim) { anim.stop(); anim = null; } }
    function render() {
      stopAnim();
      m.innerHTML = '<div class="fld"><label>Опыт</label><select id="' + mountId + '-pick">'
        + DEMOS.map(function (d, i) { return '<option value="' + i + '"' + (i === idx ? ' selected' : '') + '>' + esc(d.name) + '</option>'; }).join('') + '</select>'
        + '<button class="btn primary" id="' + mountId + '-go">Провести опыт</button></div>'
        + '<div id="' + mountId + '-stage" style="margin:8px 0"></div>'
        + '<div class="out" id="' + mountId + '-out">Выбери опыт и нажми «Провести опыт».</div>';
      $(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; render(); });
      $(mountId + '-go').addEventListener('click', function () {
        var d = DEMOS[idx], out = $(mountId + '-out');
        stopAnim(); anim = demoAnim(idx, $(mountId + '-stage'));
        out.className = 'out ok';
        out.innerHTML = '<b>Наблюдаемые признаки реакции:</b><div style="margin-top:6px">'
          + d.signs.map(function (s) { return '<div style="padding:5px 10px;margin:3px 0;border-radius:8px;background:var(--pri-soft);font-weight:600">&#10003; ' + esc(s) + '</div>'; }).join('')
          + '</div><div style="font-size:.84rem;color:var(--muted);margin-top:6px">Эти признаки указывают, что произошла <b>химическая реакция</b> — образовались новые вещества.</div>';
      });
    }
    render();
  }
  function mount_p10() { mount_signs('p10-signs'); }
  function mount_lo1() { mount_signs('lo1-signs'); }

  /* §11 — весы сохранения массы */
  function mount_p11() {
    var m = $('p11-bal'); if (!m || m._built) return; m._built = 1;
    var mixed = false;
    function scale(level) {
      // level: 0 = равновесие
      return '<svg viewBox="0 0 320 130" width="100%" style="max-width:340px">'
        + '<line x1="160" y1="14" x2="160" y2="40" stroke="var(--muted)" stroke-width="3"/>'
        + '<line x1="60" y1="40" x2="260" y2="40" stroke="var(--muted)" stroke-width="3"/>'
        + '<circle cx="160" cy="14" r="6" fill="var(--pri)"/>'
        + '<rect x="30" y="55" width="80" height="34" rx="6" fill="var(--pri-soft)" stroke="var(--border)"/>'
        + '<rect x="210" y="55" width="80" height="34" rx="6" fill="var(--pri-soft)" stroke="var(--border)"/>'
        + '<line x1="60" y1="40" x2="70" y2="55" stroke="var(--muted)" stroke-width="2"/><line x1="110" y1="40" x2="100" y2="55" stroke="var(--muted)" stroke-width="2"/>'
        + '<line x1="210" y1="40" x2="220" y2="55" stroke="var(--muted)" stroke-width="2"/><line x1="260" y1="40" x2="250" y2="55" stroke="var(--muted)" stroke-width="2"/>'
        + '<text x="70" y="77" font-size="13" font-weight="700" fill="var(--text)">100 г</text>'
        + '<text x="250" y="77" font-size="13" font-weight="700" fill="var(--text)">100 г</text>'
        + '<text x="70" y="108" font-size="11" fill="var(--muted)">' + (mixed ? 'продукты' : 'реагенты') + '</text>'
        + '<text x="225" y="108" font-size="11" fill="var(--muted)">' + (mixed ? 'продукты' : 'реагенты') + '</text>'
        + '</svg>';
    }
    var anim = null;
    function render() {
      if (anim) { anim.stop(); anim = null; }
      m.innerHTML = scale()
        + '<div style="margin:6px 0;font-size:.92rem">' + (mixed
            ? 'После реакции: <b>осадок Cu(OH)₂ + раствор Na₂SO₄</b>. Стрелка весов <b>не сдвинулась</b> — масса сохранилась (100 г = 100 г).'
            : 'До реакции: <b>раствор CuSO₄ + раствор NaOH</b>, общая масса 100 г.') + '</div>'
        + '<div id="p11-stage" style="margin:6px 0"></div>'
        + '<button class="btn primary" id="p11-mix">' + (mixed ? 'Сбросить' : 'Смешать растворы') + '</button>';
      if (mixed && W.Chem7Anim) anim = W.Chem7Anim.precipField($('p11-stage'), { color: '#38bdf8', h: 96 });
      $('p11-mix').addEventListener('click', function () { mixed = !mixed; render(); });
    }
    render();
  }

  /* §12 — балансировщик + анимированный подсчёт атомов (слева/справа) */
  var ELC = { H:'#cbd5e1', O:'#ef4444', C:'#334155', N:'#3b82f6', S:'#eab308', Fe:'#b45309', P:'#f97316', Cl:'#22c55e', Mg:'#22c55e', Ca:'#a78bfa', Na:'#a78bfa', Cu:'#ea580c', Zn:'#64748b', Al:'#6366f1', K:'#a78bfa' };
  function mount_p12() {
    var pick = $('p12-pick'), mount = $('p12-mount'); if (!pick || pick._built || !C().equationBalancer) return; pick._built = 1;
    if (!$('p12-tally')) mount.insertAdjacentHTML('afterend', '<div id="p12-tally" style="margin-top:10px"></div>');
    function sumSide(list, coeffs, off) {
      var tot = {};
      list.forEach(function (sp, i) { var cnt = C().elementCounts ? C().elementCounts(sp) : {}; var co = coeffs[off + i] || 1; for (var e in cnt) tot[e] = (tot[e] || 0) + cnt[e] * co; });
      return tot;
    }
    function dots(el, n) { var s = ''; for (var i = 0; i < n; i++) s += '<span class="c7-atom" style="display:inline-block;width:13px;height:13px;border-radius:50%;margin:1px;background:' + (ELC[el] || '#94a3b8') + ';transform:scale(.2);opacity:0;transition:transform .3s ease,opacity .3s ease"></span>'; return s; }
    function col(title, tot) { return '<div style="flex:1;min-width:140px"><div style="font-weight:700;font-size:.82rem;margin-bottom:4px">' + title + '</div>' + Object.keys(tot).map(function (e) { return '<div style="display:flex;align-items:center;gap:6px;margin:2px 0"><b style="width:26px">' + e + '</b>' + dots(e, tot[e]) + '<span style="color:var(--muted);font-size:.8rem">× ' + tot[e] + '</span></div>'; }).join('') + '</div>'; }
    function tally(skeleton, coeffs) {
      var t = $('p12-tally'); if (!t) return;
      var sides = skeleton.split(/->|=/);
      var L = sides[0].split('+').map(function (s) { return s.trim(); });
      var Rr = (sides[1] || '').split('+').map(function (s) { return s.trim(); });
      var left = sumSide(L, coeffs, 0), right = sumSide(Rr, coeffs, L.length);
      var ok = Object.keys(left).every(function (e) { return left[e] === right[e]; }) && Object.keys(right).every(function (e) { return left[e] === right[e]; });
      t.innerHTML = '<div style="display:flex;gap:14px;flex-wrap:wrap">' + col('Реагенты — атомы', left) + col('Продукты — атомы', right) + '</div>'
        + '<div class="out ' + (ok ? 'ok' : 'bad') + '" style="margin-top:6px">' + (ok ? '&#10003; Число атомов каждого элемента слева и справа <b>совпадает</b> — уравнение сбалансировано.' : 'Атомы не уравнены.') + '</div>';
      if (W.Chem7Anim && !W.Chem7Anim.HEADLESS) {
        var a = t.querySelectorAll('.c7-atom');
        a.forEach(function (d, i) { d.style.transitionDelay = (i * 28) + 'ms'; });
        W.requestAnimationFrame(function () { W.requestAnimationFrame(function () { a.forEach(function (d) { d.style.transform = 'scale(1)'; d.style.opacity = '1'; }); }); });
      }
    }
    function build() { var parts = pick.value.split('|'); var coeffs = parts[1].split(',').map(Number); C().equationBalancer(mount, { skeleton: parts[0], solution: coeffs }); tally(parts[0], coeffs); }
    pick.addEventListener('change', build); build();
  }

  W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, {
    p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3,
    p4: mount_p4, p5: mount_p5, p6: mount_p6,
    p7: mount_p7, p8: mount_p8, p9: mount_p9,
    p10: mount_p10, lo1: mount_lo1, p11: mount_p11
  });
  W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, { p12: mount_p12 });
})(window);
