feat(chemistry-8): U6 — карты связей понятий в финалах глав

chem8_svg.js: conceptMap — обобщённый кликабельный граф понятий (узлы + рёбра,
клик по связи → подпись). Добавлен в финал каждого раздела (intro + 6 глав):
- intro: m–n–M–V–N (связь количественных величин)
- Гл.1: оксид→кислота/основание→соль; Гл.2: период/группа/семейство→свойства
- Гл.3: ядро→протоны/нейтроны/электроны; Гл.4: типы связи→решётка→свойства
- Гл.5: с.о.→окисление/восстановление→баланс; Гл.6: смесь→раствор→растворимость/w/c

Ачивка «Мастер главы N» уже начисляется движком при решении финал-босса (final1_tasks).

Тесты: 43/43 (+ jsdom: монтаж карты связей в финале). Конфиг-данные карт — в виджетах глав.
--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 16:39:47 +03:00
parent 7bf15d449a
commit 57e4a6ae95
16 changed files with 64 additions and 15 deletions
+37 -1
View File
@@ -850,6 +850,41 @@
return { el: host };
}
/* ──────────────────────────────────────────────────────────────────────────
conceptMap(mount, {nodes, edges}) — обобщённая карта связей понятий главы.
nodes: [{id, t, x, y, c?}]; edges: [{f, t, label}]. Клик по ребру → подпись.
Используется в финалах глав (U6).
────────────────────────────────────────────────────────────────────────── */
function conceptMap(mount, opts) {
var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount;
if (!host || !opts) return null;
var nodes = opts.nodes || [], edges = opts.edges || [];
var byId = {}; nodes.forEach(function (n) { byId[n.id] = n; });
var W0 = opts.w || 430, H0 = opts.h || 150;
function cx(n) { return n.x + 44; } function cy(n) { return n.y + 16; }
var edgesSvg = edges.map(function (e, i) {
var a = byId[e.f], b = byId[e.t]; if (!a || !b) return '';
return '<line class="gm-edge" data-i="' + i + '" x1="' + cx(a) + '" y1="' + cy(a) + '" x2="' + cx(b) + '" y2="' + cy(b) + '" stroke="var(--muted,#888)" stroke-width="2.5"/>';
}).join('');
var nodesSvg = nodes.map(function (n) {
var c = n.c || 'var(--pri,#d97706)';
return '<g><rect x="' + n.x + '" y="' + n.y + '" width="88" height="32" rx="8" fill="' + c + '" opacity=".16" stroke="' + c + '" stroke-width="1.5"/>'
+ '<text x="' + cx(n) + '" y="' + (cy(n) + 4) + '" text-anchor="middle" font-size="10.5" font-weight="800" fill="currentColor">' + n.t + '</text></g>';
}).join('');
host.innerHTML = '<div class="gm-wrap"><svg viewBox="0 0 ' + W0 + ' ' + H0 + '" class="gm-svg">' + edgesSvg + nodesSvg + '</svg></div>'
+ '<div class="out gm-out">Кликни по связи — увидишь, как понятия главы связаны.</div>';
var out = host.querySelector('.gm-out');
host.querySelectorAll('.gm-edge').forEach(function (ln) {
ln.style.cursor = 'pointer';
ln.addEventListener('click', function () {
host.querySelectorAll('.gm-edge').forEach(function (x) { x.setAttribute('stroke', 'var(--muted,#888)'); x.setAttribute('stroke-width', '2.5'); });
ln.setAttribute('stroke', 'var(--pri,#d97706)'); ln.setAttribute('stroke-width', '4');
out.className = 'out gm-out ok'; out.innerHTML = '<b>' + edges[+ln.getAttribute('data-i')].label + '</b>';
});
});
return { el: host };
}
/* ──────────────────────────────────────────────────────────────────────────
dissociationAnim(mount, {substance}) — анимация растворения/диссоциации:
вещество распадается на ионы, окружённые молекулами воды. §47.
@@ -932,8 +967,9 @@
// готово (Phase 6 — ОВР)
oxStateCalc: oxStateCalc, // §42 — калькулятор степени окисления
oxStates: oxStates, // степени окисления (чистая функция)
// готово (Phase 8/U3 — апгрейд)
// готово (Phase 8/U3,U6 — апгрейд)
geneticMap: geneticMap, // §22 — генетическая карта-граф классов
conceptMap: conceptMap, // финалы глав — карта связей понятий (U6)
dissociationAnim: dissociationAnim, // §47 — анимация растворения/диссоциации
// редокс-баланс §44 реализован пошагово в chem8_ch5_widgets (преднабор)
redoxBalancer: notImplemented('redoxBalancer'),