@
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:
@@ -100,7 +100,8 @@
|
||||
|
||||
/* §22 — генетическая карта классов */
|
||||
function mount_p22() { var el = $('c-genetic'); if (el && !el._b && C().geneticMap) { el._b = 1; C().geneticMap(el, {}); } }
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"ox","t":"Оксид","x":20,"y":22},{"id":"acid","t":"Кислота","x":160,"y":22,"c":"#2563eb"},{"id":"base","t":"Основание","x":20,"y":95,"c":"#0d9488"},{"id":"salt","t":"Соль","x":330,"y":55,"c":"#d97706"}],"edges":[{"f":"ox","t":"acid","label":"кислотный оксид + вода → кислота"},{"f":"acid","t":"base","label":"нейтрализация → соль + вода"},{"f":"acid","t":"salt","label":"кислота + металл/оксид → соль"},{"f":"base","t":"salt","label":"основание + кислота → соль"}]}); } }
|
||||
|
||||
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, p22: mount_p22, p23: mount_p23 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p10: mount_p10, p14: mount_p14, p19: mount_p19, p20: mount_p20, p22: mount_p22, p23: mount_p23 };
|
||||
})(window);
|
||||
|
||||
@@ -69,7 +69,8 @@
|
||||
el.querySelector('.amph-reset').addEventListener('click', reset);
|
||||
reset();
|
||||
}
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"per","t":"Период","x":20,"y":22},{"id":"grp","t":"Группа","x":20,"y":95},{"id":"fam","t":"Семейство","x":160,"y":55},{"id":"prop","t":"Свойства","x":330,"y":55}],"edges":[{"f":"per","t":"prop","label":"номер периода = число слоёв"},{"f":"grp","t":"prop","label":"номер группы = внешние e⁻"},{"f":"fam","t":"grp","label":"одна группа — одно семейство"}]}); } }
|
||||
|
||||
W.CHEM8_WIDGETS = { p25: mount_p25 };
|
||||
W.FLAG_MOUNTS = { p24: mount_p24, p26: mount_p26, p28: mount_p28 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p24: mount_p24, p26: mount_p26, p28: mount_p28 };
|
||||
})(window);
|
||||
|
||||
@@ -91,7 +91,8 @@
|
||||
if (W.chem8RenderMath) try { W.chem8RenderMath(panel); } catch (e) {}
|
||||
} });
|
||||
}
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"nuc","t":"Ядро","x":20,"y":55},{"id":"prot","t":"Протоны","x":170,"y":22},{"id":"neut","t":"Нейтроны","x":170,"y":95},{"id":"elec","t":"Электроны","x":330,"y":55}],"edges":[{"f":"nuc","t":"prot","label":"Z = число протонов"},{"f":"nuc","t":"neut","label":"N нейтронов (A = Z + N)"},{"f":"prot","t":"elec","label":"Z = e⁻ (атом нейтрален)"}]}); } }
|
||||
|
||||
W.CHEM8_WIDGETS = { p29: mount_p29, p30: mount_p30, p31: mount_p31, p33: mount_p33 };
|
||||
W.FLAG_MOUNTS = { p34: mount_p34, p35: mount_p35 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p34: mount_p34, p35: mount_p35 };
|
||||
})(window);
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
var mol = $('c-mol'); if (mol && !mol._b && M()) { mol._b = 1; M().molModel(mol, 'H2O'); }
|
||||
}
|
||||
function mount_p41() { var el = $('c-lattice'); if (el && !el._b && M()) { el._b = 1; M().latticeViewer(el, 'ionic'); } }
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"cov","t":"Ковалент.","x":20,"y":22},{"id":"ion","t":"Ионная","x":20,"y":95},{"id":"met","t":"Металлич.","x":160,"y":55},{"id":"lat","t":"Решётка","x":330,"y":22},{"id":"prop","t":"Свойства","x":330,"y":95}],"edges":[{"f":"cov","t":"lat","label":"ковалентная → атомная/молек. решётка"},{"f":"ion","t":"lat","label":"ионная → ионная решётка"},{"f":"met","t":"lat","label":"металлическая → металл. решётка"},{"f":"lat","t":"prop","label":"тип решётки определяет свойства"}]}); } }
|
||||
|
||||
W.CHEM8_WIDGETS = {};
|
||||
W.FLAG_MOUNTS = { p37: mount_p37, p38: mount_p38, p41: mount_p41 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p37: mount_p37, p38: mount_p38, p41: mount_p41 };
|
||||
})(window);
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
bAll.addEventListener('click', function () { shown = R[cur].steps.length; render(); });
|
||||
render();
|
||||
}
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"so","t":"Степ. ок.","x":20,"y":55},{"id":"oxi","t":"Окисление","x":170,"y":22},{"id":"red","t":"Восстан.","x":170,"y":95},{"id":"bal","t":"Баланс","x":330,"y":55,"c":"#d97706"}],"edges":[{"f":"so","t":"oxi","label":"с.о. растёт (отдача e⁻)"},{"f":"so","t":"red","label":"с.о. падает (приём e⁻)"},{"f":"oxi","t":"bal","label":"отдано e⁻"},{"f":"red","t":"bal","label":"= принято e⁻"}]}); } }
|
||||
|
||||
W.CHEM8_WIDGETS = { p42: mount_p42 };
|
||||
W.FLAG_MOUNTS = { p44: mount_p44 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p44: mount_p44 };
|
||||
})(window);
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
|
||||
/* §47 — анимация растворения/диссоциации */
|
||||
function mount_p47() { var el = $('c-dissoc'); if (el && !el._b && C().dissociationAnim) { el._b = 1; C().dissociationAnim(el, { substance: 'NaCl' }); } }
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"mix","t":"Смесь","x":20,"y":55},{"id":"sol","t":"Раствор","x":170,"y":55,"c":"#0891b2"},{"id":"sb","t":"Растворим.","x":330,"y":22},{"id":"w","t":"w (доля)","x":330,"y":55},{"id":"c","t":"c (моль/л)","x":330,"y":95}],"edges":[{"f":"mix","t":"sol","label":"однородная смесь = раствор"},{"f":"sol","t":"sb","label":"растворимость: г / 100 г воды"},{"f":"sol","t":"w","label":"массовая доля w = m / m"},{"f":"sol","t":"c","label":"молярная концентрация c = n/V"}]}); } }
|
||||
|
||||
W.CHEM8_WIDGETS = { p46: mount_p46, p50: mount_p50, p51: mount_p51 };
|
||||
W.FLAG_MOUNTS = { p47: mount_p47, p48: mount_p48 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p47: mount_p47, p48: mount_p48 };
|
||||
})(window);
|
||||
|
||||
@@ -139,7 +139,8 @@
|
||||
bAll.addEventListener('click', function () { shown = ST[cur].steps.length; render(); });
|
||||
render();
|
||||
}
|
||||
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"n","t":"n, моль","x":170,"y":55,"c":"#d97706"},{"id":"m","t":"m, г","x":20,"y":22},{"id":"M","t":"M, г/моль","x":20,"y":95},{"id":"V","t":"V, л","x":330,"y":22},{"id":"N","t":"N частиц","x":330,"y":95}],"edges":[{"f":"m","t":"n","label":"n = m / M"},{"f":"M","t":"n","label":"M = m / n"},{"f":"n","t":"V","label":"V = n · 22,4 (газ, н.у.)"},{"f":"n","t":"N","label":"N = n · 6,02·10²³"}]}); } }
|
||||
|
||||
W.CHEM8_WIDGETS = { p1: mount_p1, p2: mount_p2, p3: mount_p3, p4: mount_p4, p5: mount_p5, pr1: mount_pr1 };
|
||||
W.FLAG_MOUNTS = { p6: mount_p6, p7: mount_p7, p8: mount_p8, p9: mount_p9 };
|
||||
W.FLAG_MOUNTS = { final1: mount_final1, p6: mount_p6, p7: mount_p7, p8: mount_p8, p9: mount_p9 };
|
||||
})(window);
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user