Files
Learn_System/frontend/js/chem8_intro_widgets.js
T
Maxim Dolgolyov 57e4a6ae95 @
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>
@
2026-05-30 16:39:47 +03:00

147 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* chem8_intro_widgets.js — виджеты вводного раздела «Химия 8».
* Монтируются движком chem8_engine.js: window.CHEM8_WIDGETS[id] / window.FLAG_MOUNTS[id].
* Используют window.Chem8 (chem8_svg.js): molarMass, elementCounts, arOf, fmt,
* moleTriangle, equationBalancer.
*/
(function (W) {
'use strict';
function C() { return W.Chem8 || {}; }
function $(id) { return document.getElementById(id); }
function rr(v, d) { var p = Math.pow(10, d == null ? 3 : d); return (Math.round(v * p) / p).toString().replace('.', ','); }
/* §1 — карта элементов */
var EL = {
H: [1, 'Водород'], He: [2, 'Гелий'], Li: [3, 'Литий'], Be: [4, 'Бериллий'], B: [5, 'Бор'], C: [6, 'Углерод'],
N: [7, 'Азот'], O: [8, 'Кислород'], F: [9, 'Фтор'], Ne: [10, 'Неон'], Na: [11, 'Натрий'], Mg: [12, 'Магний'],
Al: [13, 'Алюминий'], Si: [14, 'Кремний'], P: [15, 'Фосфор'], S: [16, 'Сера'], Cl: [17, 'Хлор'], Ar: [18, 'Аргон'],
K: [19, 'Калий'], Ca: [20, 'Кальций'], Fe: [26, 'Железо'], Cu: [29, 'Медь'], Zn: [30, 'Цинк'], Ag: [47, 'Серебро'], Ba: [56, 'Барий']
};
function mount_p1() {
var grid = $('p1-el'), info = $('p1-elinfo'); if (!grid || grid._built) return; grid._built = 1;
Object.keys(EL).forEach(function (s) {
var ar = C().arOf ? C().arOf(s) : '';
var c = document.createElement('div'); c.className = 'el-cell';
c.innerHTML = '<span class="z">' + EL[s][0] + '</span><span class="s">' + s + '</span><span class="a">' + ar + '</span>';
c.addEventListener('click', function () {
grid.querySelectorAll('.el-cell').forEach(function (x) { x.classList.remove('on'); }); c.classList.add('on');
info.innerHTML = '<b>' + EL[s][1] + '</b> (' + s + ') · порядковый номер Z = ' + EL[s][0] + ' · A_r = ' + ar;
});
grid.appendChild(c);
});
}
/* §2 — калькулятор Mr */
function mount_p2() {
var inp = $('p2-mr-in'), out = $('p2-mr-out'), go = $('p2-mr-go'); if (!inp || inp._built) return; inp._built = 1;
function calc() {
var f = inp.value.trim(), cnt = C().elementCounts ? C().elementCounts(f) : null, mr = C().molarMass ? C().molarMass(f) : NaN;
if (!cnt || isNaN(mr)) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу. Проверьте символы элементов.'; return; }
out.className = 'out ok';
out.innerHTML = '<b>M_r(' + f + ') = ' + C().fmt(mr) + '</b><br><span class="bd">' +
Object.keys(cnt).map(function (e) { return e + ': A_r=' + (C().arOf ? C().arOf(e) : '?') + ' × ' + cnt[e]; }).join(' &nbsp;|&nbsp; ') +
'<br>Σ = ' + Object.keys(cnt).map(function (e) { return (C().arOf ? C().arOf(e) : '?') + '·' + cnt[e]; }).join(' + ') + ' = ' + C().fmt(mr) + '</span>';
}
go.addEventListener('click', calc);
inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); });
document.querySelectorAll('.p2-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); });
calc();
}
/* §3 — порция вещества */
function mount_p3() {
var sub = $('p3-sub'), rng = $('p3-n'), nv = $('p3-nv'), out = $('p3-out'); if (!sub || sub._built) return; sub._built = 1;
var M = { H2O: 18, O2: 32, CO2: 44, NaCl: 58.5 };
function upd() {
var n = parseFloat(rng.value), s = sub.value, m = n * M[s], N = n * 6.02;
nv.textContent = n.toFixed(1).replace('.', ',');
out.innerHTML = '<span class="bd">n = ' + n.toFixed(1).replace('.', ',') + ' моль<br>m = n·M = ' + n.toFixed(1).replace('.', ',') + ' · ' + String(M[s]).replace('.', ',') + ' = <b>' + rr(m, 1) + ' г</b><br>N = n·N_A = <b>' + rr(N, 2) + '·10²³ частиц</b></span>';
}
sub.addEventListener('change', upd); rng.addEventListener('input', upd); upd();
}
/* §4 — счётчик частиц */
function mount_p4() {
var rng = $('p4-n'), nv = $('p4-nv'), out = $('p4-out'); if (!rng || rng._built) return; rng._built = 1;
function upd() { var n = parseFloat(rng.value), N = n * 6.02; nv.textContent = n.toFixed(2).replace('.', ',');
out.innerHTML = '<span class="bd">N = n · N_A = ' + n.toFixed(2).replace('.', ',') + ' · 6,02·10²³ = <b>' + rr(N, 2) + '·10²³ частиц</b></span>'; }
rng.addEventListener('input', upd); upd();
}
/* §5 — M + объём газа */
function mount_p5() {
var inp = $('p5-in'), out = $('p5-out'), go = $('p5-go'); if (!inp || inp._built) return; inp._built = 1;
function calc() {
var f = inp.value.trim(), mr = C().molarMass ? C().molarMass(f) : NaN;
if (isNaN(mr)) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу.'; return; }
out.className = 'out ok';
out.innerHTML = '<span class="bd">M(' + f + ') = <b>' + C().fmt(mr) + ' г/моль</b><br>1 моль газа при н.у. → <b>22,4 л</b><br>Плотность газа ≈ M/22,4 = ' + rr(mr / 22.4) + ' г/л</span>';
}
go.addEventListener('click', calc); inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); }); calc();
}
/* §6 / ПР1 — треугольник n–m–M (флагман) */
function mount_triangle(mountId, subId) {
var mount = $(mountId), sub = $(subId); if (!mount || mount._built || !C().moleTriangle) return; mount._built = 1;
var api = C().moleTriangle(mount, {});
if (sub) sub.addEventListener('change', function () {
var f = sub.value; if (!f) return; var m = C().molarMass(f);
if (!isNaN(m) && api && api.set) api.set('M', m);
});
}
function mount_p6() { mount_triangle('p6-mount', 'p6-sub'); }
function mount_pr1() { mount_triangle('pr1-mount', 'pr1-sub'); }
/* §7 — универсальный калькулятор газа (флагман) */
function mount_p7() {
var sub = $('p7-sub'), key = $('p7-key'), val = $('p7-val'), go = $('p7-go'), out = $('p7-out'); if (!sub || sub._built) return; sub._built = 1;
var Vm = 22.4, NA = 6.02;
function calc() {
var f = sub.value, M = C().molarMass(f), k = key.value, x = parseFloat((val.value || '').replace(',', '.'));
if (isNaN(x)) { out.className = 'out bad'; out.textContent = 'Введите число.'; return; }
var n; if (k === 'n') n = x; else if (k === 'm') n = x / M; else if (k === 'V') n = x / Vm; else n = x / NA;
var m = n * M, V = n * Vm, N = n * NA;
out.className = 'out ok';
out.innerHTML = '<span class="bd">M(' + f + ')=' + M + ' г/моль<br>n = <b>' + rr(n) + ' моль</b><br>m = <b>' + rr(m) + ' г</b><br>V(н.у.) = <b>' + rr(V) + ' л</b><br>N = <b>' + rr(N) + '·10²³ частиц</b></span>';
}
go.addEventListener('click', calc); val.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); }); calc();
}
/* §8 — балансировщик (флагман) */
function mount_p8() {
var pick = $('p8-pick'), mount = $('p8-mount'); if (!pick || pick._built || !C().equationBalancer) return; pick._built = 1;
function build() { var parts = pick.value.split('|'); C().equationBalancer(mount, { skeleton: parts[0], solution: parts[1].split(',').map(Number) }); }
pick.addEventListener('change', build); build();
}
/* §9 — пошаговый решатель (флагман) */
var ST = [
{ eq: '2H₂ + O₂ → 2H₂O', given: 'Дано: m(H₂) = 4 г. Найти m(H₂O).',
steps: ['M(H₂)=2 г/моль, M(H₂O)=18 г/моль.', 'n(H₂) = m/M = 4/2 = 2 моль.', 'По уравнению n(H₂):n(H₂O) = 2:2 = 1:1 → n(H₂O)=2 моль.', 'm(H₂O) = n·M = 2·18 = 36 г. Ответ: 36 г.'] },
{ eq: 'CaCO₃ → CaO + CO₂↑', given: 'Дано: m(CaCO₃) = 100 г. Найти V(CO₂) при н.у.',
steps: ['M(CaCO₃)=100 г/моль.', 'n(CaCO₃) = 100/100 = 1 моль.', 'n(CaCO₃):n(CO₂) = 1:1 → n(CO₂)=1 моль.', 'V(CO₂) = n·Vm = 1·22,4 = 22,4 л. Ответ: 22,4 л.'] },
{ eq: 'Zn + 2HCl → ZnCl₂ + H₂↑', given: 'Дано: n(Zn) = 0,5 моль. Найти V(H₂) при н.у.',
steps: ['n(Zn):n(H₂) = 1:1 → n(H₂)=0,5 моль.', 'V(H₂) = n·Vm = 0,5·22,4 = 11,2 л. Ответ: 11,2 л.'] }
];
function mount_p9() {
var pick = $('p9-pick'), out = $('p9-out'), bStep = $('p9-step'), bAll = $('p9-all'); if (!pick || pick._built) return; pick._built = 1;
ST.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 = ST[cur];
var html = '<b>' + p.eq + '</b><br><span style="color:var(--muted)">' + p.given + '</span><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;
if (W.chem8RenderMath) try { W.chem8RenderMath(out); } catch (e) {}
}
pick.addEventListener('change', function () { cur = +pick.value; shown = 0; render(); });
bStep.addEventListener('click', function () { if (shown < ST[cur].steps.length) { shown++; render(); } });
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 = { final1: mount_final1, p6: mount_p6, p7: mount_p7, p8: mount_p8, p9: mount_p9 };
})(window);