Files
Learn_System/frontend/js/chem8_intro_widgets.js
T
Maxim Dolgolyov 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>
@
2026-05-30 15:04:04 +03:00

146 lines
9.8 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();
}
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);