feat(chemistry-8): Phase 5 — Глава 4 «Химическая связь» (§36–41)

Глава на движке (6 § + Лаб.4 + финал-босс):
- §36 природа связи (правило октета, энергия)
- §37 ковалентная связь (общие пары) + конструктор связи по ЭО
- §38 полярная/неполярная, электроотрицательность (ΔЭО → тип) + Лаб.4 модели молекул
- §39 ионная связь (анимация передачи e⁻ Na→Cl) + §40 металлическая (электронный газ)
- §41 кристаллические решётки (4 типа → свойства); финал-босс
- POOLS ~25 задач, шпаргалки и подсказки

chem8_svg.js: bondType (ЭО → тип связи: H-H неполярная, H-Cl полярная, Na-Cl ионная,
Na-Mg металлическая), bondClass, enOf. chem8_ch4_widgets.js: монтаж по §.

Тесты: 33/33 (юнит + jsdom-виджеты + полностраничный SPA 5 глав). Ассеты 200.
--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:48:49 +03:00
parent 35a3b2406f
commit 8ce4cec798
6 changed files with 296 additions and 100 deletions
+14
View File
@@ -0,0 +1,14 @@
/* chem8_ch4_widgets.js — виджеты Главы 4 «Химическая связь».
* Использует window.Chem8: bondType.
*/
(function (W) {
'use strict';
function C() { return W.Chem8 || {}; }
function $(id) { return document.getElementById(id); }
function mount_p37() { var el = $('c-bond1'); if (el && !el._b && C().bondType) { el._b = 1; C().bondType(el, { a: 'H', b: 'H' }); } }
function mount_p38() { var el = $('c-bond2'); if (el && !el._b && C().bondType) { el._b = 1; C().bondType(el, { a: 'H', b: 'Cl' }); } }
W.CHEM8_WIDGETS = {};
W.FLAG_MOUNTS = { p37: mount_p37, p38: mount_p38 };
})(window);
+61
View File
@@ -690,6 +690,63 @@
return { el: host, draw: draw };
}
/* ──────────────────────────────────────────────────────────────────────────
Химическая связь (Phase 5).
EN — электроотрицательность (Полинг, школьные значения). bondClass(da,db)
по разнице ЭО → тип связи. bondType(mount) — интерактивный виджет.
────────────────────────────────────────────────────────────────────────── */
var EN = {
H:2.1, Li:1.0, Be:1.5, B:2.0, C:2.5, N:3.0, O:3.5, F:4.0,
Na:0.9, Mg:1.2, Al:1.5, Si:1.8, P:2.1, S:2.5, Cl:3.0,
K:0.8, Ca:1.0, Br:2.8, I:2.5, Zn:1.6, Fe:1.8, Cu:1.9, Ag:1.9
};
function enOf(sym) { return EN[sym] != null ? EN[sym] : 2.0; }
function bondClass(a, b) {
var d = Math.abs(enOf(a) - enOf(b));
if (a !== b && (a in EN) && (b in EN) && enOf(a) <= 1.6 && enOf(b) <= 1.6) {
// два металла → металлическая
if (METALS_EN[a] && METALS_EN[b]) return { type: 'металлическая', cls: 'warn', d: d };
}
if (d >= 1.7) return { type: 'ионная', cls: 'bad', d: d };
if (d < 0.4) return { type: 'ковалентная неполярная', cls: 'good', d: d };
return { type: 'ковалентная полярная', cls: 'mid', d: d };
}
var METALS_EN = { Li:1, Be:1, Na:1, Mg:1, Al:1, K:1, Ca:1, Zn:1, Fe:1, Cu:1, Ag:1 };
function bondType(mount, opts) {
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
if (!host) return null;
opts = opts || {};
var syms = Object.keys(EN);
function optList(sel) { return syms.map(function (s) { return '<option value="' + s + '"' + (s === sel ? ' selected' : '') + '>' + s + ' (ЭО ' + enOf(s) + ')</option>'; }).join(''); }
host.innerHTML = '<div class="fld"><label>Атом A</label><select class="bt-a">' + optList(opts.a || 'H') + '</select>'
+ '<label>Атом B</label><select class="bt-b">' + optList(opts.b || 'Cl') + '</select></div>'
+ '<div class="bt-stage"></div><div class="out bt-out"></div>';
var sa = host.querySelector('.bt-a'), sb = host.querySelector('.bt-b'), stage = host.querySelector('.bt-stage'), out = host.querySelector('.bt-out');
function upd() {
var a = sa.value, b = sb.value, r = bondClass(a, b), d = Math.round(r.d * 10) / 10;
// δ-заряды: более ЭО атом — δ−
var aMore = enOf(a) > enOf(b), polar = r.type.indexOf('полярная') >= 0;
var da = (r.type === 'ионная') ? (aMore ? '' : '+') : (polar ? (aMore ? 'δ−' : 'δ+') : '');
var db = (r.type === 'ионная') ? (aMore ? '+' : '') : (polar ? (aMore ? 'δ+' : 'δ−') : '');
var color = r.cls === 'good' ? 'var(--ok)' : r.cls === 'bad' ? 'var(--fail)' : 'var(--pri)';
stage.innerHTML = '<svg viewBox="0 0 240 70" class="bt-svg">'
+ '<line x1="95" y1="35" x2="145" y2="35" stroke="' + color + '" stroke-width="3"/>'
+ '<circle cx="80" cy="35" r="26" fill="' + color + '" opacity=".15" stroke="' + color + '" stroke-width="2"/>'
+ '<circle cx="160" cy="35" r="26" fill="' + color + '" opacity=".15" stroke="' + color + '" stroke-width="2"/>'
+ '<text x="80" y="40" text-anchor="middle" font-size="16" font-weight="800" fill="currentColor">' + a + '</text>'
+ '<text x="160" y="40" text-anchor="middle" font-size="16" font-weight="800" fill="currentColor">' + b + '</text>'
+ (da ? '<text x="80" y="12" text-anchor="middle" font-size="12" font-weight="800" fill="' + color + '">' + da + '</text>' : '')
+ (db ? '<text x="160" y="12" text-anchor="middle" font-size="12" font-weight="800" fill="' + color + '">' + db + '</text>' : '')
+ '</svg>';
out.className = 'out bt-out ' + (r.cls === 'good' ? 'ok' : r.cls === 'bad' ? 'bad' : '');
out.innerHTML = '<span class="bd">ΔЭО = |' + enOf(a) + ' ' + enOf(b) + '| = <b>' + d + '</b> → связь <b>' + r.type + '</b>'
+ (r.type === 'ионная' ? '<br>Электрон полностью переходит к более электроотрицательному атому.' : polar ? '<br>Общая пара смещена к более электроотрицательному атому (' + (aMore ? a : b) + ').' : r.type.indexOf('металл') >= 0 ? '<br>Общие электроны принадлежат всем атомам («электронный газ»).' : '<br>Общая пара поделена поровну.') + '</span>';
}
sa.addEventListener('change', upd); sb.addEventListener('change', upd); upd();
return { el: host, update: upd };
}
/* ---- Каркасы-заглушки интерактивных виджетов (реализуются по фазам) ---- */
function notImplemented(name) {
return function () {
@@ -727,6 +784,10 @@
shellConfig: shellConfig, // распределение электронов по слоям
nuclide: nuclide, // §30 — A=Z+N, нуклид
zSym: zSym, // Z → символ элемента
// готово (Phase 5 — химическая связь)
bondType: bondType, // §37,38 — ЭО → тип связи
bondClass: bondClass, // классификация связи по ΔЭО
enOf: enOf, // электроотрицательность
// заглушки (см. план, разд. B) — наполняются в Phase 5–6
oxStateCalc: notImplemented('oxStateCalc'), // §42 — калькулятор степени окисления
redoxBalancer: notImplemented('redoxBalancer'), // §44 — e-баланс ОВР