809d0316c3
feat(chemistry-8): перестройка раздела intro под эталон учебников (SPA-движок) По замечанию: учебник не соответствовал структуре/наполнению других учебников. Перестроено по контракту глав физики (para-selector SPA + движок задач): - chem8_engine.js — общий движок: para-selector, ленивая сборка §, makeCard, тренажёр задач (числовой ввод + MCQ, nav-dots, score), sidebar-шпаргалка с XP, уровни/достижения, серверная синхронизация прогресса, тема. Конфиг — CHEM8_CFG. - chem8-textbook.css — фреймворк-CSS: layout+sidebar, hero, psel-карточки, para-hero (9 градиентов), карточки теории, def/remember/insight, тренажёр, mcq, флагман-карточки, виджеты, ach-popup (amber-палитра). - chem8_intro_widgets.js — виджеты § (карта элементов, Mr, порция, Авогадро, M+объём) и флагманы (треугольник n–m–M, калькулятор газа, балансировщик, пошаговый решатель) на chem8_svg.js. - chemistry_8_intro.html — перестроен: PARAS, build_p1..p9+pr1+final, POOLS (38 задач), SIDEBARS, TIPS. Богатая анатомия § как в физике. Тесты: 23/23 (юнит + jsdom-виджеты + полностраничный jsdom SPA — para-selector, активный §, монтаж виджетов, тренажёр, без ошибок скриптов). Ассеты отдаются 200. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
146 lines
9.8 KiB
JavaScript
146 lines
9.8 KiB
JavaScript
/* 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(' | ') +
|
||
'<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();
|
||
}
|
||
|
||
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 };
|
||
})(window);
|