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:
Maxim Dolgolyov
2026-05-30 15:57:58 +03:00
parent c1c5bafaff
commit f8c68f940d
5 changed files with 287 additions and 101 deletions
+58 -3
View File
@@ -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(' &nbsp; ') + '</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 — генетическая карта-граф классов