Files
Learn_System/frontend/js/chem7_ch1_widgets.js
T
Maxim Dolgolyov 13cbbacc1f feat(chemistry7): Phase 1 Волна 4 — Глава 1 завершена (§§10–12 + ЛО1 + финал)
§10 Физические и химические явления (детектор признаков реакции),
ЛО1 Признаки реакций (опыты с признаками), §11 Закон сохранения массы
(весы сохранения массы), §12 Составление уравнений (балансировщик через
Chem8.equationBalancer), финал главы (6 интегрированных боссов + шпаргалка).

Глава 1 «Первоначальные химические понятия» наполнена полностью (12§).
Тесты: 10/10 chem7 pass; полный прогон 156/159 (3 — известный baseline Auth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 18:33:55 +03:00

377 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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: 'Фильтрование', why: 'Песок не растворяется — задерживается фильтром, вода проходит.' },
{ mix: 'Соль и вода', method: 'Выпаривание', why: 'Вода испаряется, соль остаётся на дне.' },
{ mix: 'Железные опилки и сера', method: 'Магнит', why: 'Железо притягивается магнитом, сера — нет.' },
{ mix: 'Вода и растительное масло', method: 'Отстаивание (делительная воронка)', why: 'Масло легче воды и не смешивается — слои разделяют.' },
{ mix: 'Спирт и вода', method: 'Перегонка (дистилляция)', why: 'У спирта и воды разные температуры кипения.' }
];
var METHODS = ['Фильтрование', 'Выпаривание', 'Магнит', 'Отстаивание (делительная воронка)', 'Перегонка (дистилляция)'];
function mount_sep(mountId) {
var m = $(mountId); if (!m || m._built) return; m._built = 1;
var idx = 0;
function render() {
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>';
$(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; m._built = 0; 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> Подумай, чем различаются вещества в смеси (растворимость, магнитные свойства, температура кипения, плотность).';
});
});
}
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>';
}
/* §5 — галерея простых веществ */
function mount_p5() {
var m = $('p5-gal'); if (!m || m._built) return; m._built = 1;
m.innerHTML = '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px">'
+ molCard('Водород','H2',[['H',2]],'2 атома H — двухатомная молекула')
+ molCard('Кислород','O2',[['O',2]],'2 атома O')
+ molCard('Озон','O3',[['O',3]],'3 атома O — тоже простое вещество')
+ molCard('Азот','N2',[['N',2]],'2 атома N')
+ '</div><div style="font-size:.84rem;color:var(--muted);margin-top:8px">Во всех молекулах — атомы <b>одного</b> элемента → это <b>простые вещества</b>. Кислород $\\text{O}_2$ и озон $\\text{O}_3$ образованы одним элементом, но это разные простые вещества.</div>';
if (W.chem8RenderMath) try { W.chem8RenderMath(m); } catch(e){}
}
/* §6 — классификатор простое/сложное + галерея сложных веществ */
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 }
]
});
var g = $('p6-gal');
if (g && !g._built) { g._built = 1;
g.innerHTML = '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px">'
+ molCard('Вода','H2O',[['O',1],['H',2]],'2 элемента: H и O')
+ molCard('Углекислый газ','CO2',[['C',1],['O',2]],'2 элемента: C и O')
+ molCard('Метан','CH4',[['C',1],['H',4]],'2 элемента: C и H')
+ molCard('Аммиак','NH3',[['N',1],['H',3]],'2 элемента: N и H')
+ '</div><div style="font-size:.84rem;color:var(--muted);margin-top:8px">В каждой молекуле — атомы <b>разных</b> элементов → это <b>сложные вещества</b>.</div>';
if (W.chem8RenderMath) try { W.chem8RenderMath(g); } catch(e){}
}
}
/* ── Волна 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] ];
function mount_p9() {
var m = $('p9-bld'); if (!m || m._built) return; m._built = 1;
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 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 : '');
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>'
+ 'Наименьшее общее кратное валентностей = <b>' + lcm + '</b><br>'
+ 'Индексы: ' + a[0] + ' → ' + ia + ', ' + b[0] + ' → ' + ib + '<br>'
+ 'Формула: <b style="font-size:1.15rem">' + (C().formula ? C().formula(raw) : raw) + '</b><br>'
+ 'Проверка: ' + ia + '·' + a[1] + ' = ' + ib + '·' + b[1] + ' = ' + lcm + ' единиц валентности — совпало.</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: ['выделение газа (пузырьки)'] }
];
function mount_signs(mountId) {
var m = $(mountId); if (!m || m._built) return; m._built = 1;
var idx = 0;
function render() {
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 class="out" id="' + mountId + '-out">Выбери опыт и нажми «Провести опыт».</div>';
$(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; m._built = 0; render(); });
$(mountId + '-go').addEventListener('click', function () {
var d = DEMOS[idx], out = $(mountId + '-out'); 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>';
}
function render() {
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>'
+ '<button class="btn primary" id="p11-mix">' + (mixed ? 'Сбросить' : 'Смешать растворы') + '</button>';
$('p11-mix').addEventListener('click', function () { mixed = !mixed; m._built = 0; render(); });
}
render();
}
/* §12 — балансировщик уравнений (переиспользуем Chem8.equationBalancer) */
function mount_p12() {
var pick = $('p12-pick'), mount = $('p12-mount'); if (!pick || pick._built || !C().equationBalancer) return; pick._built = 1;
function build() { var parts = pick.value.split('|'); C().equationBalancer(mount, { skeleton: parts[0], solution: parts[1].split(',').map(Number) }); }
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);