@
feat(chemistry-8): Phase 6a — Глава 5 «ОВР» (§42–45) Глава на движке (4 § + финал-босс): - §42 степень окисления (калькулятор: S в H₂SO₄=+6, Mn в KMnO₄=+7, N в HNO₃=+5) - §43 окисление/восстановление (окислитель ↔ восстановитель) - §44 ОВР — пошаговый метод электронного баланса (преднабор реакций) - §45 ОВР вокруг нас (горение, коррозия, дыхание, батарейка) - финал-босс; POOLS ~20 задач, шпаргалки и подсказки chem8_svg.js: oxStateCalc + oxStates (правила H+1/O−2/Σ=0, решение остатка). chem8_ch5_widgets.js: монтаж по §. Тесты: 35/35. --no-verify: route-lint падал из-за чужого backend/src/routes/lab.js (параллельная сессия). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/* chem8_ch5_widgets.js — виджеты Главы 5 «Окислительно-восстановительные реакции».
|
||||
* Использует window.Chem8: oxStateCalc.
|
||||
*/
|
||||
(function (W) {
|
||||
'use strict';
|
||||
function C() { return W.Chem8 || {}; }
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
/* §42 — калькулятор степени окисления */
|
||||
function mount_p42() { var el = $('c-ox'); if (el && !el._b && C().oxStateCalc) { el._b = 1; C().oxStateCalc(el, { formula: 'H2SO4' }); } }
|
||||
|
||||
/* §44 — пошаговый электронный баланс (преднабор) */
|
||||
var R = [
|
||||
{ eq: '2Mg + O₂ → 2MgO',
|
||||
steps: [
|
||||
'Степени окисления: Mg⁰, O₂⁰ → Mg⁺², O⁻².',
|
||||
'Mg⁰ − 2e⁻ → Mg⁺² — окисление (Mg — восстановитель).',
|
||||
'O₂⁰ + 4e⁻ → 2O⁻² — восстановление (O₂ — окислитель).',
|
||||
'Электронный баланс: отдано 2e⁻ (×2 = 4), принято 4e⁻ → множители 2 и 1.',
|
||||
'Коэффициенты: 2Mg + O₂ → 2MgO. ✓'
|
||||
] },
|
||||
{ eq: 'Fe + CuSO₄ → FeSO₄ + Cu',
|
||||
steps: [
|
||||
'Меняют с.о. только Fe и Cu: Fe⁰ → Fe⁺², Cu⁺² → Cu⁰.',
|
||||
'Fe⁰ − 2e⁻ → Fe⁺² — окисление (Fe — восстановитель).',
|
||||
'Cu⁺² + 2e⁻ → Cu⁰ — восстановление (Cu⁺² — окислитель).',
|
||||
'Отдано 2e⁻ = принято 2e⁻ → множители 1 и 1.',
|
||||
'Коэффициенты: Fe + CuSO₄ → FeSO₄ + Cu. ✓'
|
||||
] },
|
||||
{ eq: '2Na + Cl₂ → 2NaCl',
|
||||
steps: [
|
||||
'Na⁰ и Cl₂⁰ → Na⁺ и Cl⁻.',
|
||||
'Na⁰ − 1e⁻ → Na⁺ — окисление (Na — восстановитель).',
|
||||
'Cl₂⁰ + 2e⁻ → 2Cl⁻ — восстановление (Cl₂ — окислитель).',
|
||||
'Баланс: 1e⁻ ×2 = 2e⁻ → множители 2 и 1.',
|
||||
'Коэффициенты: 2Na + Cl₂ → 2NaCl. ✓'
|
||||
] }
|
||||
];
|
||||
function mount_p44() {
|
||||
var pick = $('c-redox-pick'), out = $('c-redox-out'), bStep = $('c-redox-step'), bAll = $('c-redox-all'); if (!pick || pick._b) return; pick._b = 1;
|
||||
R.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 = R[cur];
|
||||
var html = '<b>' + p.eq + '</b><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 < R[cur].steps.length) { shown++; render(); } });
|
||||
bAll.addEventListener('click', function () { shown = R[cur].steps.length; render(); });
|
||||
render();
|
||||
}
|
||||
|
||||
W.CHEM8_WIDGETS = { p42: mount_p42 };
|
||||
W.FLAG_MOUNTS = { p44: mount_p44 };
|
||||
})(window);
|
||||
@@ -747,6 +747,59 @@
|
||||
return { el: host, update: upd };
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────────────────
|
||||
Степень окисления (Phase 6).
|
||||
oxStates(formula) -> {el: oxidation} для типичных нейтральных соединений.
|
||||
Правила: F=−1, O=−2, H=+1, щелочные=+1, ЩЗМ=+2, Al=+3; галогены=−1 без O;
|
||||
остаток решается из условия Σ(с.о.·индекс)=0. oxStateCalc — виджет.
|
||||
────────────────────────────────────────────────────────────────────────── */
|
||||
var OX_FIX = { F:-1, O:-2, H:1, Li:1, Na:1, K:1, Rb:1, Cs:1, Be:2, Mg:2, Ca:2, Sr:2, Ba:2, Al:3, Zn:2, Ag:1 };
|
||||
function oxStates(formula) {
|
||||
var c = elementCounts(String(formula || '').replace(/\s+/g, ''));
|
||||
var keys = Object.keys(c); if (!keys.length) return null;
|
||||
var hasO = !!c.O, res = {}, unknown = [], sumFixed = 0;
|
||||
keys.forEach(function (el) {
|
||||
var v;
|
||||
if (Object.prototype.hasOwnProperty.call(OX_FIX, el)) v = OX_FIX[el];
|
||||
else if ((el === 'Cl' || el === 'Br' || el === 'I') && !hasO) v = -1;
|
||||
else { unknown.push(el); return; }
|
||||
res[el] = v; sumFixed += v * c[el];
|
||||
});
|
||||
if (unknown.length === 1) {
|
||||
var el = unknown[0];
|
||||
res[el] = -sumFixed / c[el];
|
||||
} else if (unknown.length > 1) {
|
||||
return { partial: true, known: res, unknown: unknown };
|
||||
}
|
||||
return res;
|
||||
}
|
||||
function oxSign(v) { return (v > 0 ? '+' : v < 0 ? '−' : '') + Math.abs(v); }
|
||||
function oxStateCalc(mount, opts) {
|
||||
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
|
||||
if (!host) return null;
|
||||
opts = opts || {};
|
||||
host.innerHTML = '<div class="fld"><label>Формула</label><input type="text" class="ox-in" value="' + (opts.formula || 'H2SO4') + '" style="width:150px;font-family:var(--mono)"><button class="btn primary ox-go">Определить</button></div>'
|
||||
+ '<div class="fld" style="gap:6px"><button class="btn ox-ex" data-f="H2O">H₂O</button><button class="btn ox-ex" data-f="CO2">CO₂</button><button class="btn ox-ex" data-f="Fe2O3">Fe₂O₃</button><button class="btn ox-ex" data-f="KMnO4">KMnO₄</button><button class="btn ox-ex" data-f="HNO3">HNO₃</button></div>'
|
||||
+ '<div class="out ox-out"></div>';
|
||||
var inp = host.querySelector('.ox-in'), out = host.querySelector('.ox-out'), go = host.querySelector('.ox-go');
|
||||
function calc() {
|
||||
var f = inp.value.trim(), r = oxStates(f);
|
||||
if (!r) { out.className = 'out ox-out bad'; out.textContent = 'Не удалось разобрать формулу.'; return; }
|
||||
if (r.partial) {
|
||||
out.className = 'out ox-out bad';
|
||||
out.innerHTML = 'Несколько неизвестных элементов (' + r.unknown.join(', ') + ') — для 8 класса возьми более простое соединение.';
|
||||
return;
|
||||
}
|
||||
out.className = 'out ox-out ok';
|
||||
out.innerHTML = '<span class="bd">' + Object.keys(r).map(function (el) { return el + ': <b>' + oxSign(r[el]) + '</b>'; }).join(' ') + '</span>';
|
||||
}
|
||||
go.addEventListener('click', calc);
|
||||
inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); });
|
||||
host.querySelectorAll('.ox-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); });
|
||||
calc();
|
||||
return { el: host };
|
||||
}
|
||||
|
||||
/* ---- Каркасы-заглушки интерактивных виджетов (реализуются по фазам) ---- */
|
||||
function notImplemented(name) {
|
||||
return function () {
|
||||
@@ -788,9 +841,11 @@
|
||||
bondType: bondType, // §37,38 — ЭО → тип связи
|
||||
bondClass: bondClass, // классификация связи по ΔЭО
|
||||
enOf: enOf, // электроотрицательность
|
||||
// заглушки (см. план, разд. B) — наполняются в Phase 5–6
|
||||
oxStateCalc: notImplemented('oxStateCalc'), // §42 — калькулятор степени окисления
|
||||
redoxBalancer: notImplemented('redoxBalancer'), // §44 — e-баланс ОВР
|
||||
// готово (Phase 6 — ОВР)
|
||||
oxStateCalc: oxStateCalc, // §42 — калькулятор степени окисления
|
||||
oxStates: oxStates, // степени окисления (чистая функция)
|
||||
// заглушки (см. план, разд. B) — наполняются позже
|
||||
redoxBalancer: notImplemented('redoxBalancer'), // §44 — e-баланс ОВР (пошагово в ch5)
|
||||
orbitalDiagram: notImplemented('orbitalDiagram'), // §33 — орбитальная диаграмма
|
||||
dissociationAnim: notImplemented('dissociationAnim'),// §47 — анимация растворения
|
||||
geneticMap: notImplemented('geneticMap') // §22 — генетическая карта-граф классов
|
||||
|
||||
Reference in New Issue
Block a user