feat(chemistry-8): Phase 2 — Глава 1 «Важнейшие классы неорг. соединений» (§10–23)

Полная глава на движке (14 § + 2 лаб. опыта + 2 практические работы + финал-босс):
- §10–12 оксиды (классификатор, свойства, получение)
- §13–15 кислоты (классификатор, ряд активности, индикаторы, получение)
- §16–18 основания (классификатор, фенолфталеин, Лаб.1 Cu(OH)₂↓, ПР2 нейтрализация)
- §19–21 соли (таблица растворимости, РИО, соль+металл, Лаб.2, способы)
- §22 генетическая связь классов + ПР3; §23 расчётный решатель; финал-босс (6 задач)
- POOLS: ~45 задач (MCQ + числовые), шпаргалки и подсказки по каждому §

chem8_svg.js: реализованы 5 хим-виджетов (были заглушки) — testTube (осадок/газ),
indicatorScale (лакмус/фенолфталеин/метилоранж + pH), classifier (клик-DnD),
solubilityTable (катион×анион), activitySeries (ряд активности металлов).
chem8-textbook.css: стили виджетов. chem8_ch1_widgets.js: монтаж по §.

Тесты: 24/24 (юнит + jsdom-виджеты + полностраничный SPA intro и ch1 — para-selector,
активный §, монтаж флагманов, тренажёр, без ошибок). Ассеты 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
This commit is contained in:
Maxim Dolgolyov
2026-05-30 15:20:13 +03:00
parent 813d5ef5e6
commit d8508baf8d
6 changed files with 740 additions and 163 deletions
+103
View File
@@ -0,0 +1,103 @@
/* chem8_ch1_widgets.js — виджеты Главы 1 «Важнейшие классы неорганических соединений».
* Монтируются движком: window.CHEM8_WIDGETS[id] / window.FLAG_MOUNTS[id].
* Используют window.Chem8: classifier, indicatorScale, solubilityTable, activitySeries.
*/
(function (W) {
'use strict';
function C() { return W.Chem8 || {}; }
function $(id) { return document.getElementById(id); }
function xp(n, s) { try { if (W.addXp) W.addXp(n, s); } catch (e) {} }
/* §10 — классификатор оксидов */
function mount_p10() {
var el = $('c-ox-cls'); if (!el || el._b || !C().classifier) return; el._b = 1;
C().classifier(el, {
items: [
{ id: 'Na2O', label: 'Na₂O', cat: 'осн' }, { id: 'CaO', label: 'CaO', cat: 'осн' },
{ id: 'CO2', label: 'CO₂', cat: 'кисл' }, { id: 'SO3', label: 'SO₃', cat: 'кисл' }, { id: 'P2O5', label: 'P₂O₅', cat: 'кисл' },
{ id: 'ZnO', label: 'ZnO', cat: 'амф' }, { id: 'Al2O3', label: 'Al₂O₃', cat: 'амф' },
{ id: 'CO', label: 'CO', cat: 'несол' }, { id: 'N2O', label: 'N₂O', cat: 'несол' }
],
buckets: [{ cat: 'осн', label: 'Основные' }, { cat: 'кисл', label: 'Кислотные' }, { cat: 'амф', label: 'Амфотерные' }, { cat: 'несол', label: 'Несолеобразующие' }],
onCheck: function (ok) { if (ok) xp(8, 'p10-cls'); }
});
}
/* §13 — классификатор кислот + индикатор */
function mount_p13() {
var cls = $('c-acid-cls');
if (cls && !cls._b && C().classifier) { cls._b = 1; C().classifier(cls, {
items: [
{ id: 'HCl', label: 'HCl', cat: 'без' }, { id: 'H2S', label: 'H₂S', cat: 'без' }, { id: 'HBr', label: 'HBr', cat: 'без' },
{ id: 'H2SO4', label: 'H₂SO₄', cat: 'кисл' }, { id: 'HNO3', label: 'HNO₃', cat: 'кисл' }, { id: 'H3PO4', label: 'H₃PO₄', cat: 'кисл' }
],
buckets: [{ cat: 'без', label: 'Бескислородные' }, { cat: 'кисл', label: 'Кислородсодержащие' }],
onCheck: function (ok) { if (ok) xp(8, 'p13-cls'); }
}); }
var ind = $('c-acid-ind'); if (ind && !ind._b && C().indicatorScale) { ind._b = 1; C().indicatorScale(ind, { indicator: 'лакмус', ph: 2 }); }
}
/* §14 — ряд активности + индикатор */
function mount_p14() {
var act = $('c-acid-act'); if (act && !act._b && C().activitySeries) { act._b = 1; C().activitySeries(act, {}); }
var ind = $('c-acid-ind2'); if (ind && !ind._b && C().indicatorScale) { ind._b = 1; C().indicatorScale(ind, { indicator: 'метилоранж', ph: 2 }); }
}
/* §16 — классификатор оснований + индикатор (фенолфталеин) */
function mount_p16() {
var cls = $('c-base-cls');
if (cls && !cls._b && C().classifier) { cls._b = 1; C().classifier(cls, {
items: [
{ id: 'NaOH', label: 'NaOH', cat: 'щел' }, { id: 'KOH', label: 'KOH', cat: 'щел' }, { id: 'BaOH', label: 'Ba(OH)₂', cat: 'щел' },
{ id: 'CuOH', label: 'Cu(OH)₂', cat: 'нер' }, { id: 'FeOH', label: 'Fe(OH)₃', cat: 'нер' }, { id: 'MgOH', label: 'Mg(OH)₂', cat: 'нер' }
],
buckets: [{ cat: 'щел', label: 'Щёлочи (растворимые)' }, { cat: 'нер', label: 'Нерастворимые' }],
onCheck: function (ok) { if (ok) xp(8, 'p16-cls'); }
}); }
var ind = $('c-base-ind'); if (ind && !ind._b && C().indicatorScale) { ind._b = 1; C().indicatorScale(ind, { indicator: 'фенолфталеин', ph: 12 }); }
}
/* §17 — индикатор нейтрализации */
function mount_p17() { var ind = $('c-neutral-ind'); if (ind && !ind._b && C().indicatorScale) { ind._b = 1; C().indicatorScale(ind, { indicator: 'фенолфталеин', ph: 12 }); } }
/* §18 — индикатор (ПР2 нейтрализация) */
function mount_p18() { var ind = $('c-pr2-ind'); if (ind && !ind._b && C().indicatorScale) { ind._b = 1; C().indicatorScale(ind, { indicator: 'лакмус', ph: 7 }); } }
/* §19 — таблица растворимости */
function mount_p19() { var s = $('c-salt-sol'); if (s && !s._b && C().solubilityTable) { s._b = 1; C().solubilityTable(s, {}); } }
/* §20 — растворимость + ряд активности (соль + металл) */
function mount_p20() {
var s = $('c-salt-sol2'); if (s && !s._b && C().solubilityTable) { s._b = 1; C().solubilityTable(s, {}); }
var a = $('c-salt-act'); if (a && !a._b && C().activitySeries) { a._b = 1; C().activitySeries(a, {}); }
}
/* §23 — пошаговый решатель расчётных задач по классам */
var ST = [
{ eq: 'CaO + 2HCl → CaCl₂ + H₂O', given: 'Дано: m(CaO) = 28 г. Найти m(CaCl₂). M(CaO)=56, M(CaCl₂)=111.',
steps: ['n(CaO) = m/M = 28/56 = 0,5 моль.', 'n(CaO):n(CaCl₂) = 1:1 → n(CaCl₂)=0,5 моль.', 'm(CaCl₂) = n·M = 0,5·111 = 55,5 г. Ответ: 55,5 г.'] },
{ eq: 'Zn + H₂SO₄ → ZnSO₄ + H₂↑', given: 'Дано: n(Zn) = 2 моль. Найти V(H₂) при н.у.',
steps: ['n(Zn):n(H₂) = 1:1 → n(H₂)=2 моль.', 'V(H₂) = n·Vm = 2·22,4 = 44,8 л. Ответ: 44,8 л.'] },
{ eq: '2NaOH + H₂SO₄ → Na₂SO₄ + 2H₂O', given: 'Дано: n(H₂SO₄) = 0,5 моль. Найти n(NaOH).',
steps: ['n(NaOH):n(H₂SO₄) = 2:1 → n(NaOH)=2·0,5=1 моль.', 'Ответ: 1 моль NaOH.'] }
];
function mount_p23() {
var pick = $('c-calc-pick'), out = $('c-calc-out'), bStep = $('c-calc-step'), bAll = $('c-calc-all'); if (!pick || pick._b) return; pick._b = 1;
ST.forEach(function (p, i) { var o = document.createElement('option'); o.value = i; o.textContent = p.eq; pick.appendChild(o); });
var cur = 0, shown = 0;
function render() {
var p = ST[cur];
var html = '<b>' + p.eq + '</b><br><span style="color:var(--muted)">' + p.given + '</span><div style="margin-top:8px">';
for (var i = 0; i < shown; i++) html += '<div class="def-box" style="margin:6px 0">' + p.steps[i] + '</div>';
if (shown === 0) html += '<span style="color:var(--muted)">Нажмите «Следующий шаг».</span>';
html += '</div>'; out.className = shown >= p.steps.length ? 'out ok' : 'out'; out.innerHTML = html;
}
pick.addEventListener('change', function () { cur = +pick.value; shown = 0; render(); });
bStep.addEventListener('click', function () { if (shown < ST[cur].steps.length) { shown++; render(); } });
bAll.addEventListener('click', function () { shown = ST[cur].steps.length; render(); });
render();
}
W.CHEM8_WIDGETS = { p13: mount_p13, p16: mount_p16, p17: mount_p17, p18: mount_p18 };
W.FLAG_MOUNTS = { p10: mount_p10, p14: mount_p14, p19: mount_p19, p20: mount_p20, p23: mount_p23 };
})(window);
+213 -7
View File
@@ -350,6 +350,211 @@
});
}
/* ──────────────────────────────────────────────────────────────────────────
testTube(opts) -> SVG-строка пробирки. opts: {fill, color, precipitate, gas,
label}. fill/color — цвет раствора; precipitate — цвет осадка на дне;
gas:true — пузырьки; label — подпись под пробиркой.
────────────────────────────────────────────────────────────────────────── */
function testTube(opts) {
opts = opts || {};
var liq = opts.color || opts.fill || '#dbeafe';
var prec = opts.precipitate || null;
var gas = !!opts.gas;
var bubbles = '';
if (gas) for (var i = 0; i < 5; i++) {
var cx = 26 + (i % 3) * 7, cy = 60 - i * 8;
bubbles += '<circle cx="' + cx + '" cy="' + cy + '" r="' + (1.6 + (i % 2)) + '" fill="rgba(255,255,255,.75)"><animate attributeName="cy" from="78" to="20" dur="' + (1.4 + i * .2) + 's" repeatCount="indefinite"/></circle>';
}
var precSvg = prec ? '<path d="M20 78 q12 7 24 0 l-2 6 q-10 5 -20 0 z" fill="' + prec + '"/>' : '';
return '<svg class="tt-svg" viewBox="0 0 64 110" width="56" height="96">'
+ '<defs><clipPath id="ttclip"><path d="M20 14 v60 a12 12 0 0 0 24 0 v-60"/></clipPath></defs>'
+ '<rect x="20" y="38" width="24" height="46" fill="' + liq + '" clip-path="url(#ttclip)" opacity=".85"/>'
+ precSvg
+ '<g clip-path="url(#ttclip)">' + bubbles + '</g>'
+ '<path d="M20 12 v62 a12 12 0 0 0 24 0 v-62" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/>'
+ '<line x1="17" y1="12" x2="47" y2="12" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>'
+ (opts.label ? '<text x="32" y="104" text-anchor="middle" font-size="10" font-weight="700" fill="currentColor">' + escapeHtml(opts.label) + '</text>' : '')
+ '</svg>';
}
/* ──────────────────────────────────────────────────────────────────────────
indicatorScale(mount, opts) — индикатор + шкала pH. Слайдер pH 0–14,
выбор индикатора (лакмус/фенолфталеин/метилоранж), окраска полоски.
────────────────────────────────────────────────────────────────────────── */
var INDICATORS = {
'лакмус': function (ph) { return ph < 5 ? ['#dc2626', 'красный (кислота)'] : ph > 8 ? ['#2563eb', 'синий (щёлочь)'] : ['#7c3aed', 'фиолетовый (нейтр.)']; },
'фенолфталеин': function (ph) { return ph >= 8.2 ? ['#db2777', 'малиновый (щёлочь)'] : ['#f8fafc', 'бесцветный']; },
'метилоранж': function (ph) { return ph < 3.1 ? ['#dc2626', 'красный (кислота)'] : ph > 4.4 ? ['#f59e0b', 'жёлтый'] : ['#fb923c', 'оранжевый']; }
};
function indicatorScale(mount, opts) {
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
if (!host) return null;
opts = opts || {};
var inds = Object.keys(INDICATORS);
host.innerHTML =
'<div class="ind-row"><label>Индикатор</label><select class="ind-sel">' +
inds.map(function (n) { return '<option value="' + n + '"' + (n === opts.indicator ? ' selected' : '') + '>' + n + '</option>'; }).join('') +
'</select><label>pH</label><input type="range" class="ind-ph" min="0" max="14" step="0.5" value="' + (opts.ph != null ? opts.ph : 7) + '"><span class="ind-phv bd"></span></div>' +
'<div class="ind-strip"></div><div class="ind-label"></div>';
var sel = host.querySelector('.ind-sel'), ph = host.querySelector('.ind-ph'),
phv = host.querySelector('.ind-phv'), strip = host.querySelector('.ind-strip'), lab = host.querySelector('.ind-label');
function upd() {
var v = parseFloat(ph.value), pair = INDICATORS[sel.value](v);
phv.textContent = 'pH ' + String(v).replace('.', ',');
strip.style.background = pair[0];
strip.style.color = (pair[0] === '#f8fafc' || pair[0] === '#f59e0b') ? '#1c1917' : '#fff';
strip.textContent = pair[1];
lab.innerHTML = 'Среда: <b>' + (v < 7 ? 'кислая' : v > 7 ? 'щелочная' : 'нейтральная') + '</b> · ' + sel.value + ' → ' + pair[1];
}
sel.addEventListener('change', upd); ph.addEventListener('input', upd); upd();
return { el: host };
}
/* ──────────────────────────────────────────────────────────────────────────
classifier(mount, {items, buckets, onCheck}) — клик-классификатор (DnD без drag).
items: [{id,label,cat}]; buckets: [{cat,label}]. Клик по чипу → выбран; клик
по корзине → положить. «Проверить» подсвечивает верно/неверно. +XP по onCheck.
────────────────────────────────────────────────────────────────────────── */
function classifier(mount, opts) {
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
if (!host) return null;
opts = opts || {}; var items = opts.items || [], buckets = opts.buckets || [];
var placed = {}; // id -> cat
var sel = null;
host.innerHTML =
'<div class="cls-pool dnd-pool">' + items.map(function (it) {
return '<button class="dnd-chip cls-chip" data-id="' + it.id + '">' + it.label + '</button>';
}).join('') + '</div>' +
'<div class="dnd-zones">' + buckets.map(function (b) {
return '<div class="drop-box cls-zone" data-cat="' + b.cat + '"><h5>' + b.label + '</h5><div class="cls-items"></div></div>';
}).join('') + '</div>' +
'<div class="ceqb-actions" style="margin-top:10px"><button class="ceqb-btn primary cls-check">Проверить</button><button class="ceqb-btn cls-reset">Сброс</button></div>' +
'<div class="out cls-out" style="display:none"></div>';
var out = host.querySelector('.cls-out');
function findItem(id) { return items.filter(function (x) { return x.id === id; })[0]; }
function selectChip(chip) {
if (sel) sel.classList.remove('on'); sel = chip; chip.classList.add('on');
}
host.querySelectorAll('.cls-chip').forEach(function (chip) {
chip.addEventListener('click', function () { selectChip(chip); });
});
host.querySelectorAll('.cls-zone').forEach(function (zone) {
zone.addEventListener('click', function () {
if (!sel) return;
var id = sel.getAttribute('data-id');
placed[id] = zone.getAttribute('data-cat');
zone.querySelector('.cls-items').appendChild(sel);
sel.classList.remove('on'); sel.classList.add('placed'); sel = null;
});
});
host.querySelector('.cls-check').addEventListener('click', function () {
var ok = 0, total = items.length;
items.forEach(function (it) {
var chip = host.querySelector('.cls-chip[data-id="' + it.id + '"]');
var correct = placed[it.id] === it.cat;
chip.classList.remove('cls-ok', 'cls-bad');
chip.classList.add(correct ? 'cls-ok' : 'cls-bad');
if (correct) ok++;
});
out.style.display = 'block';
out.className = 'out cls-out ' + (ok === total ? 'ok' : 'bad');
out.textContent = 'Верно: ' + ok + ' из ' + total + (ok === total ? '. Отлично!' : '. Исправь выделенные.');
if (typeof opts.onCheck === 'function') opts.onCheck(ok === total, ok, total);
});
host.querySelector('.cls-reset').addEventListener('click', function () {
placed = {}; sel = null;
var pool = host.querySelector('.cls-pool');
host.querySelectorAll('.cls-chip').forEach(function (c) { c.classList.remove('placed', 'on', 'cls-ok', 'cls-bad'); pool.appendChild(c); });
out.style.display = 'none';
});
return { el: host, result: function () { return placed; } };
}
/* ──────────────────────────────────────────────────────────────────────────
solubilityTable(mount, opts) — таблица растворимости (катион×анион).
Клик по катиону и аниону → подсветка ячейки + вердикт (Р/М/Н/—).
────────────────────────────────────────────────────────────────────────── */
var SOL_ANIONS = ['OH', 'Cl', 'NO3', 'SO4', 'CO3', 'PO4', 'S'];
var SOL_CATIONS = ['Na', 'K', 'NH4', 'Ba', 'Ca', 'Mg', 'Al', 'Zn', 'Fe2', 'Fe3', 'Cu', 'Ag', 'Pb'];
// P раств., M малораств., H нераств., '-' не существует/разлагается
var SOL = {
OH: {Na:'P',K:'P',NH4:'P',Ba:'P',Ca:'M',Mg:'H',Al:'H',Zn:'H',Fe2:'H',Fe3:'H',Cu:'H',Ag:'-',Pb:'H'},
Cl: {Na:'P',K:'P',NH4:'P',Ba:'P',Ca:'P',Mg:'P',Al:'P',Zn:'P',Fe2:'P',Fe3:'P',Cu:'P',Ag:'H',Pb:'M'},
NO3: {Na:'P',K:'P',NH4:'P',Ba:'P',Ca:'P',Mg:'P',Al:'P',Zn:'P',Fe2:'P',Fe3:'P',Cu:'P',Ag:'P',Pb:'P'},
SO4: {Na:'P',K:'P',NH4:'P',Ba:'H',Ca:'M',Mg:'P',Al:'P',Zn:'P',Fe2:'P',Fe3:'P',Cu:'P',Ag:'M',Pb:'H'},
CO3: {Na:'P',K:'P',NH4:'P',Ba:'H',Ca:'H',Mg:'H',Al:'-',Zn:'H',Fe2:'H',Fe3:'-',Cu:'H',Ag:'H',Pb:'H'},
PO4: {Na:'P',K:'P',NH4:'P',Ba:'H',Ca:'H',Mg:'H',Al:'H',Zn:'H',Fe2:'H',Fe3:'H',Cu:'H',Ag:'H',Pb:'H'},
S: {Na:'P',K:'P',NH4:'P',Ba:'P',Ca:'P',Mg:'P',Al:'-',Zn:'H',Fe2:'H',Fe3:'-',Cu:'H',Ag:'H',Pb:'H'}
};
var SOL_LABEL = { P: ['Р', 'растворимо'], M: ['М', 'малорастворимо'], H: ['Н', 'нерастворимо'], '-': ['—', 'не существует / разлагается'] };
var CAT_HTML = { Na:'Na⁺', K:'K⁺', NH4:'NH₄⁺', Ba:'Ba²⁺', Ca:'Ca²⁺', Mg:'Mg²⁺', Al:'Al³⁺', Zn:'Zn²⁺', Fe2:'Fe²⁺', Fe3:'Fe³⁺', Cu:'Cu²⁺', Ag:'Ag⁺', Pb:'Pb²⁺' };
var AN_HTML = { OH:'OH⁻', Cl:'Cl⁻', NO3:'NO₃⁻', SO4:'SO₄²⁻', CO3:'CO₃²⁻', PO4:'PO₄³⁻', S:'S²⁻' };
function solubilityTable(mount, opts) {
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
if (!host) return null;
opts = opts || {};
var th = '<tr><th>ион</th>' + SOL_CATIONS.map(function (c) { return '<th data-cat="' + c + '">' + CAT_HTML[c] + '</th>'; }).join('') + '</tr>';
var rows = SOL_ANIONS.map(function (an) {
return '<tr><th data-an="' + an + '">' + AN_HTML[an] + '</th>' + SOL_CATIONS.map(function (c) {
var v = SOL[an][c]; var cls = v === 'P' ? 'sP' : v === 'M' ? 'sM' : v === 'H' ? 'sH' : 'sX';
return '<td class="' + cls + '" data-an="' + an + '" data-cat="' + c + '">' + SOL_LABEL[v][0] + '</td>';
}).join('') + '</tr>';
}).join('');
host.innerHTML = '<div class="sol-wrap"><table class="sol-tab"><thead>' + th + '</thead><tbody>' + rows + '</tbody></table></div>'
+ '<div class="out sol-out">Кликни по катиону и аниону — узнаешь растворимость соли/основания.</div>';
var out = host.querySelector('.sol-out'), selCat = null, selAn = null;
function upd() {
host.querySelectorAll('.sol-tab td').forEach(function (td) {
var on = (!selCat || td.getAttribute('data-cat') === selCat) && (!selAn || td.getAttribute('data-an') === selAn);
td.classList.toggle('sol-dim', (selCat || selAn) && !on);
td.classList.toggle('sol-hot', selCat && selAn && td.getAttribute('data-cat') === selCat && td.getAttribute('data-an') === selAn);
});
if (selCat && selAn) {
var v = SOL[selAn][selCat];
out.className = 'out sol-out ' + (v === 'H' ? 'ok' : '');
out.innerHTML = CAT_HTML[selCat] + ' + ' + AN_HTML[selAn] + ' → <b>' + SOL_LABEL[v][1] + '</b>' +
(v === 'H' ? ' (выпадает осадок ↓ — реакция идёт)' : v === 'P' ? ' (осадок не образуется)' : '');
}
}
host.querySelectorAll('[data-cat]').forEach(function (el) {
if (el.tagName === 'TH') el.addEventListener('click', function () { selCat = el.getAttribute('data-cat'); upd(); });
});
host.querySelectorAll('th[data-an]').forEach(function (el) { el.addEventListener('click', function () { selAn = el.getAttribute('data-an'); upd(); }); });
host.querySelectorAll('.sol-tab td').forEach(function (td) {
td.addEventListener('click', function () { selCat = td.getAttribute('data-cat'); selAn = td.getAttribute('data-an'); upd(); });
});
return { el: host };
}
/* ──────────────────────────────────────────────────────────────────────────
activitySeries(mount, opts) — ряд активности металлов. Клик по металлу →
подсветка; показывает, какие металлы он вытесняет и реакцию с кислотой.
────────────────────────────────────────────────────────────────────────── */
var ACT = ['K', 'Ca', 'Na', 'Mg', 'Al', 'Zn', 'Fe', 'Ni', 'Sn', 'Pb', 'H', 'Cu', 'Hg', 'Ag', 'Pt', 'Au'];
function activitySeries(mount, opts) {
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
if (!host) return null;
host.innerHTML = '<div class="act-row">' + ACT.map(function (m) {
return '<button class="act-cell' + (m === 'H' ? ' act-h' : '') + '" data-m="' + m + '">' + (m === 'H' ? '(H₂)' : m) + '</button>';
}).join('') + '</div><div class="act-axis"><span>← восстановит. свойства растут</span><span>активность падает →</span></div>'
+ '<div class="out act-out">Кликни по металлу — узнаешь его активность и реакцию с кислотами.</div>';
var out = host.querySelector('.act-out');
host.querySelectorAll('.act-cell').forEach(function (c) {
c.addEventListener('click', function () {
var m = c.getAttribute('data-m'); if (m === 'H') return;
var idx = ACT.indexOf(m), hIdx = ACT.indexOf('H');
host.querySelectorAll('.act-cell').forEach(function (x) { x.classList.remove('act-on', 'act-disp'); });
c.classList.add('act-on');
ACT.forEach(function (mm, i) { if (i > idx && mm !== 'H') host.querySelector('.act-cell[data-m="' + mm + '"]').classList.add('act-disp'); });
var withAcid = idx < hIdx ? 'вытесняет водород $\\text{H}_2$ из растворов кислот' : 'НЕ вытесняет водород из кислот (стоит после H)';
out.className = 'out act-out';
out.innerHTML = '<b>' + m + '</b>: ' + withAcid + '. Вытесняет из растворов солей все металлы, стоящие <b>правее</b> (подсвечены).';
if (global.window && global.window.chem8RenderMath) try { global.window.chem8RenderMath(out); } catch (e) {}
});
});
return { el: host };
}
/* ---- Каркасы-заглушки интерактивных виджетов (реализуются по фазам) ---- */
function notImplemented(name) {
return function () {
@@ -374,17 +579,18 @@
fmt: fmt,
moleTriangle: moleTriangle, // §6 — треугольник n–m–M
equationBalancer: equationBalancer, // §8 — балансировщик уравнений
// заглушки (см. план, разд. B) — наполняются в Phase 2–6
testTube: notImplemented('testTube'), // §18,25 — пробирка: осадок/газ/окраска
// готово (Phase 2 — классы неорганических соединений)
testTube: testTube, // §18,25 — пробирка: осадок/газ/окраска
indicatorScale: indicatorScale, // §13,14,16,17 — индикатор + шкала pH
classifier: classifier, // §10,13,16,19 — клик-классификатор
solubilityTable: solubilityTable, // §19,20 — таблица растворимости
activitySeries: activitySeries, // §14,20 — ряд активности металлов
// заглушки (см. план, разд. B) — наполняются в Phase 3–6
oxStateCalc: notImplemented('oxStateCalc'), // §42 — калькулятор степени окисления
redoxBalancer: notImplemented('redoxBalancer'), // §44 — e-баланс ОВР
orbitalDiagram: notImplemented('orbitalDiagram'), // §33 — орбитальная диаграмма
solubilityTable: notImplemented('solubilityTable'), // §19,20,48 — таблица растворимости
activitySeries: notImplemented('activitySeries'), // §14,20 — ряд активности металлов
miniPeriodic: notImplemented('miniPeriodic'), // §1,26,34 — мини-ПСХЭ с подсветкой
indicatorScale: notImplemented('indicatorScale'), // §13,14,16,17 — индикатор + шкала pH
miniPeriodic: notImplemented('miniPeriodic'), // §26,34 — мини-ПСХЭ с подсветкой
dissociationAnim: notImplemented('dissociationAnim'),// §47 — анимация растворения
classifier: notImplemented('classifier'), // §10,13,16,19,46 — DnD-классификатор
geneticMap: notImplemented('geneticMap') // §22 — генетическая карта-граф классов
};