feat(chemistry7): Phase 1 Волна 3 — Глава 1, §§7–9

§7 Химическая формула (разбор формулы на состав, индекс/коэффициент),
§8 Относительная молекулярная масса (калькулятор M_r через Chem8.molarMass),
§9 Валентность (конструктор формулы по валентности через НОК индексов).
Теория, тренажёры задач. Тест: 9/9 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 18:29:40 +03:00
parent 4a424505a8
commit bc50a0d9f1
3 changed files with 161 additions and 4 deletions
+13
View File
@@ -95,6 +95,19 @@ test('ch1 Волна 2: интерактивы §4–§6 монтируются
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch1 Волна 3: интерактивы §7–§9 монтируются и считают', async () => {
const { doc, errors } = await loadDom('chemistry_7_ch1.html');
doc.defaultView.goTo('p7'); await wait(100);
assert.ok(doc.querySelector('#p7-out'), 'парсер формулы §7');
assert.match(doc.querySelector('#p7-out').textContent, /4/, 'H2SO4 → 4 атома O в разборе');
doc.defaultView.goTo('p8'); await wait(100);
assert.match(doc.querySelector('#p8-out').textContent, /100/, 'M_r(CaCO3)=100');
doc.defaultView.goTo('p9'); await wait(100);
assert.ok(doc.querySelector('#p9-bld #p9-a'), 'конструктор валентности §9');
assert.match(doc.querySelector('#p9-bout').textContent, /Al/, 'формула по валентности построена');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch1: переход к §9 и финалу строит заглушку без ошибок', async () => {
const { doc, errors } = await loadDom('chemistry_7_ch1.html');
doc.defaultView.goTo('p9'); await wait(80);
+65 -1
View File
@@ -235,9 +235,73 @@
}
}
/* ── Волна 3 ── */
/* §7 — разбор химической формулы на состав */
function mount_p7() {
var inp = $('p7-in'), out = $('p7-out'), go = $('p7-go'); if (!inp || inp._built) return; inp._built = 1;
function calc() {
var f = inp.value.trim(), cnt = C().elementCounts ? C().elementCounts(f) : null;
if (!cnt || !Object.keys(cnt).length) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу. Проверь символы элементов (например, H2SO4).'; return; }
var els = Object.keys(cnt), tot = els.reduce(function (s, e) { return s + cnt[e]; }, 0);
out.className = 'out ok';
out.innerHTML = '<span class="bd"><b>' + (C().formula ? C().formula(f) : f) + '</b><br>'
+ 'Элементов: <b>' + els.length + '</b> (' + (els.length === 1 ? 'простое' : 'сложное') + ' вещество)<br>'
+ els.map(function (e) { return e + ': ' + cnt[e] + ' ' + (cnt[e] === 1 ? 'атом' : 'атома(ов)'); }).join('<br>')
+ '<br>Всего атомов в формуле: <b>' + tot + '</b></span>';
}
go.addEventListener('click', calc);
inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); });
document.querySelectorAll('.p7-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); });
calc();
}
/* §8 — калькулятор относительной молекулярной массы M_r */
function mount_p8() {
var inp = $('p8-in'), out = $('p8-out'), go = $('p8-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 = '<span class="bd"><b>M_r(' + f + ') = ' + C().fmt(mr) + '</b><br>'
+ 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('.p8-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); });
calc();
}
/* §9 — конструктор формулы по валентности (НОК индексов) */
function gcd(a, b) { return b ? gcd(b, a % b) : a; }
var VA = [ ['Na', 1], ['K', 1], ['H', 1], ['Mg', 2], ['Ca', 2], ['Zn', 2], ['Cu', 2], ['Al', 3], ['C', 4] ];
var VB = [ ['O', 2], ['Cl', 1], ['S', 2] ];
function mount_p9() {
var m = $('p9-bld'); if (!m || m._built) return; m._built = 1;
function optA(){ return VA.map(function(e,i){ return '<option value="'+i+'"'+(e[0]==='Al'?' selected':'')+'>'+e[0]+' (валентность '+'I'.repeat(e[1]).replace('IIII','IV')+')</option>'; }).join(''); }
function optB(){ return VB.map(function(e,i){ return '<option value="'+i+'">'+e[0]+' (валентность '+'I'.repeat(e[1])+')</option>'; }).join(''); }
m.innerHTML = '<div class="fld"><label>Элемент A</label><select id="p9-a">'+optA()+'</select>'
+'<label>Элемент B</label><select id="p9-b">'+optB()+'</select></div><div class="out" id="p9-bout"></div>';
function upd() {
var a = VA[+$('p9-a').value], b = VB[+$('p9-b').value];
var lcm = a[1] * b[1] / gcd(a[1], b[1]);
var ia = lcm / a[1], ib = lcm / b[1];
var raw = a[0] + (ia > 1 ? ia : '') + b[0] + (ib > 1 ? ib : '');
var out = $('p9-bout'); out.className = 'out ok';
out.innerHTML = '<span class="bd">Валентности: ' + a[0] + ' = ' + 'I'.repeat(a[1]).replace('IIII','IV') + ', ' + b[0] + ' = ' + 'I'.repeat(b[1]) + '<br>'
+ 'Наименьшее общее кратное валентностей = <b>' + lcm + '</b><br>'
+ 'Индексы: ' + a[0] + ' → ' + ia + ', ' + b[0] + ' → ' + ib + '<br>'
+ 'Формула: <b style="font-size:1.15rem">' + (C().formula ? C().formula(raw) : raw) + '</b><br>'
+ 'Проверка: ' + ia + '·' + a[1] + ' = ' + ib + '·' + b[1] + ' = ' + lcm + ' единиц валентности — совпало.</span>';
}
$('p9-a').addEventListener('change', upd); $('p9-b').addEventListener('change', upd); upd();
}
W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, {
p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3,
p4: mount_p4, p5: mount_p5, p6: mount_p6
p4: mount_p4, p5: mount_p5, p6: mount_p6,
p7: mount_p7, p8: mount_p8, p9: mount_p9
});
W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {});
})(window);
+83 -3
View File
@@ -105,7 +105,8 @@ window.PARAS = [
window.ACH_LABELS = { start:'Начало главы 1!', p1_done:'§1 изучен!', p2_done:'§2 изучен!',
pr1_done:'Практическая работа 1 выполнена!', p3_done:'§3 изучен!',
p4_done:'§4 изучен!', p5_done:'§5 изучен!', p6_done:'§6 изучен!', final1_tasks:'Глава 1 пройдена!' };
p4_done:'§4 изучен!', p5_done:'§5 изучен!', p6_done:'§6 изучен!',
p7_done:'§7 изучен!', p8_done:'§8 изучен!', p9_done:'§9 изучен!', final1_tasks:'Глава 1 пройдена!' };
window.SIDEBARS = {
p1:{ title:'Шпаргалка §1', rows:[['Вещество','то, из чего состоит тело'],['Тело','предмет из вещества'],['Свойства','цвет, запах, плотность, $t_{пл}$…']] },
p2:{ title:'Шпаргалка §2', rows:[['Чистое','постоянный состав'],['Смесь','2+ вещества'],['Разделение','по различию свойств']] },
@@ -113,7 +114,10 @@ window.SIDEBARS = {
p3:{ title:'Шпаргалка §3', rows:[['Атом','мельчайшая частица'],['Элемент','атомы с одинаковым $Z$'],['Символ','H, O, Fe, Cu…']] },
p4:{ title:'Шпаргалка §4', rows:[['$A_r$','во сколько раз тяжелее'],['Эталон','$1/12$ массы $^{12}$C'],['Пример','$A_r(\\text{O})=16$']] },
p5:{ title:'Шпаргалка §5', rows:[['Молекула','частица из атомов'],['Простое','1 элемент: $O_2$, $H_2$'],['Атомность','$O_2$, $O_3$']] },
p6:{ title:'Шпаргалка §6', rows:[['Сложное','разные элементы'],['Примеры','$H_2O$, $CO_2$, $NH_3$'],['Состав','можно разложить']] }
p6:{ title:'Шпаргалка §6', rows:[['Сложное','разные элементы'],['Примеры','$H_2O$, $CO_2$, $NH_3$'],['Состав','можно разложить']] },
p7:{ title:'Шпаргалка §7', rows:[['Индекс','число атомов'],['Коэффициент','число молекул'],['Состав','качеств. + количеств.']] },
p8:{ title:'Шпаргалка §8', rows:[['$M_r$','$=\\sum A_r$'],['$M_r(H_2O)$','18'],['$M_r(H_2SO_4)$','98']] },
p9:{ title:'Шпаргалка §9', rows:[['Валентность','число связей'],['H — I, O — II',''],['Формула','по НОК валентностей']] }
};
window.TIPS = [
{ sec:'p1', html:'Тело — это <b>предмет</b> (гвоздь, стакан), а вещество — <b>то, из чего</b> он сделан (железо, стекло). Из одного вещества можно сделать много тел.' },
@@ -122,7 +126,10 @@ window.TIPS = [
{ sec:'p3', html:'Химический элемент определяется <b>зарядом ядра</b> (числом протонов) — это и есть порядковый номер $Z$.' },
{ sec:'p4', html:'$A_r$ показывает, во сколько раз масса атома больше $1/12$ массы атома углерода-12. $A_r(\\text{H})=1$, $A_r(\\text{O})=16$, $A_r(\\text{Fe})=56$.' },
{ sec:'p5', html:'<b>Простое</b> вещество — атомы одного элемента ($O_2$, $Fe$). Кислород $O_2$ и озон $O_3$ — разные простые вещества одного и того же элемента.' },
{ sec:'p6', html:'<b>Сложное</b> вещество образовано атомами <b>разных</b> элементов ($H_2O$ — водород и кислород) и может быть разложено на простые.' }
{ sec:'p6', html:'<b>Сложное</b> вещество образовано атомами <b>разных</b> элементов ($H_2O$ — водород и кислород) и может быть разложено на простые.' },
{ sec:'p7', html:'Индекс относится к атому/группе слева от него. В $H_2SO_4$: 2 атома H, 1 атом S, 4 атома O. Коэффициент (число перед формулой) — это число молекул.' },
{ sec:'p8', html:'$M_r$ — сумма $A_r$ всех атомов формулы. $M_r(\\text{CO}_2)=12+2\\cdot16=44$.' },
{ sec:'p9', html:'Кислород в соединениях имеет валентность II, водород — I. Зная их, можно определить валентность другого элемента и составить формулу по НОК.' }
];
/* ── задачи (тренажёр) ── */
@@ -162,6 +169,24 @@ window.POOLS = {
{q:'Сложное вещество образовано…',opts:['Атомами одного элемента','Атомами разных элементов','Только смесью','Одним атомом'],a:1,ex:'Сложное — разные элементы.'},
{q:'Какое из веществ — простое?',opts:['$\\text{H}_2\\text{O}$','$\\text{CO}_2$','$\\text{N}_2$','$\\text{NH}_3$'],a:2,ex:'$N_2$ — один элемент (азот).'},
{q:'Сколько разных химических элементов в молекуле метана $\\text{CH}_4$?',hint:'углерод и водород',unit:'',a:2,ex:'C и H — два элемента.'}
],
p7:[
{q:'Что показывает индекс в химической формуле?',opts:['Число молекул','Число атомов элемента','Массу вещества','Заряд'],a:1,ex:'Индекс — число атомов элемента (или группы) в формуле.'},
{q:'Сколько атомов кислорода в формуле $\\text{H}_2\\text{SO}_4$?',hint:'индекс при O',unit:'',a:4,ex:'4 атома кислорода.'},
{q:'Сколько всего атомов в молекуле воды $\\text{H}_2\\text{O}$?',hint:'2 H + 1 O',unit:'',a:3,ex:'2 + 1 = 3 атома.'},
{q:'Число, стоящее перед формулой (коэффициент), показывает…',opts:['Число атомов','Число молекул','Валентность','Массу'],a:1,ex:'Коэффициент — число молекул вещества.'}
],
p8:[
{q:'Чему равна $M_r(\\text{H}_2\\text{O})$?',hint:'$2\\cdot1+16$',unit:'',a:18,ex:'$M_r=18$.'},
{q:'Чему равна $M_r(\\text{CO}_2)$?',hint:'$12+2\\cdot16$',unit:'',a:44,ex:'$12+32=44$.'},
{q:'Чему равна $M_r(\\text{H}_2\\text{SO}_4)$?',hint:'$2+32+4\\cdot16$',unit:'',a:98,ex:'$2+32+64=98$.'},
{q:'Чему равна $M_r(\\text{CaCO}_3)$?',hint:'$40+12+3\\cdot16$',unit:'',a:100,ex:'$40+12+48=100$.'}
],
p9:[
{q:'За единицу валентности принята валентность атома…',opts:['кислорода','водорода','углерода','железа'],a:1,ex:'Единица валентности — валентность водорода (I).'},
{q:'Какова валентность кислорода в соединениях?',hint:'постоянная',unit:'',a:2,ex:'Кислород почти всегда двухвалентен (II).'},
{q:'Какова валентность хлора в молекуле $\\text{HCl}$?',hint:'равна числу атомов H',unit:'',a:1,ex:'Хлор соединён с 1 атомом H → валентность I.'},
{q:'Какова формула оксида алюминия (Al — III, O — II)?',opts:['AlO','Al₂O₃','AlO₂','Al₃O₂'],a:1,ex:'НОК(3,2)=6 → индексы 2 и 3 → Al₂O₃.'}
]
};
@@ -290,6 +315,58 @@ function build_p6(){
wireReadBtn('p6');
}
function build_p7(){
document.getElementById('p7-body').innerHTML =
'<div class="para-hero"><div class="ph-label">§ 7 · Химия 7</div><h2>Химическая формула</h2>'
+'<div class="ph-formula">$\\text{H}_2\\text{O}$</div>'
+'<div class="ph-desc">Как с помощью формулы записывают, из каких атомов и в каком числе состоит вещество.</div>'
+'<div class="ph-tags"><span class="ph-tag">индекс</span><span class="ph-tag">коэффициент</span><span class="ph-tag">состав</span></div></div>'
+makeCard('theory','Что показывает формула','§7','<p><b>Химическая формула</b> показывает состав вещества: <b>качественный</b> (из каких элементов) и <b>количественный</b> (сколько атомов каждого элемента).</p>'
+'<div class="def-box"><b>Индекс</b> — маленькое число справа внизу: показывает число атомов элемента ($\\text{H}_2\\text{O}$ — 2 атома H, 1 атом O). <b>Коэффициент</b> — число перед формулой: показывает число молекул ($2\\text{H}_2\\text{O}$ — две молекулы воды).</div>')
+makeCard('example','Чтение формул',null,'<p>$\\text{H}_2\\text{O}$ читают «аш-два-о», $\\text{H}_2\\text{SO}_4$ — «аш-два-эс-о-четыре», $\\text{CH}_4$ — «цэ-аш-четыре».</p>')
+wgt('Разбор формулы на состав','<div class="fld"><label>Формула</label><input type="text" id="p7-in" value="H2SO4" style="width:150px;font-family:var(--mono)"><button class="btn primary" id="p7-go">Разобрать</button></div>'
+'<div class="fld" style="gap:6px"><button class="btn p7-ex" data-f="H2O">H₂O</button><button class="btn p7-ex" data-f="CO2">CO₂</button><button class="btn p7-ex" data-f="Ca(OH)2">Ca(OH)₂</button><button class="btn p7-ex" data-f="H2SO4">H₂SO₄</button></div>'
+'<div class="out" id="p7-out">Введи формулу и нажми «Разобрать».</div>')
+rememberBox(['Индекс — число атомов; стоит справа внизу.','Коэффициент — число молекул; стоит перед формулой.','Скобки: индекс умножает всё внутри — Ca(OH)₂ = 1 Ca, 2 O, 2 H.'])
+qList(['Чем индекс отличается от коэффициента?','Сколько атомов каждого элемента в $\\text{H}_3\\text{PO}_4$?','Что означает запись $3\\text{H}_2\\text{O}$?'])
+secNav('p6','p8')+readButton('p7');
wireReadBtn('p7');
}
function build_p8(){
document.getElementById('p8-body').innerHTML =
'<div class="para-hero"><div class="ph-label">§ 8 · Химия 7</div><h2>Относительная молекулярная масса</h2>'
+'<div class="ph-formula">$M_r=\\sum A_r$</div>'
+'<div class="ph-desc">Как по формуле рассчитать, во сколько раз молекула тяжелее эталона.</div>'
+'<div class="ph-tags"><span class="ph-tag">$M_r$</span><span class="ph-tag">$\\sum A_r$</span></div></div>'
+makeCard('rule','Относительная молекулярная масса','§8','<div class="def-box"><b>Относительная молекулярная масса</b> $M_r$ равна сумме относительных атомных масс всех атомов в формуле. Это безразмерная величина: она показывает, во сколько раз молекула тяжелее $\\tfrac{1}{12}$ массы атома углерода-12.</div>')
+makeCard('example','Расчёт $M_r$',null,'<p>$M_r(\\text{H}_2\\text{O})=2\\cdot A_r(\\text{H})+A_r(\\text{O})=2\\cdot1+16=18$.</p><div class="exa-step">$M_r(\\text{H}_2\\text{SO}_4)=2\\cdot1+32+4\\cdot16=98$.</div>')
+wgt('Калькулятор $M_r$ по формуле','<div class="fld"><label>Формула</label><input type="text" id="p8-in" value="CaCO3" style="width:150px;font-family:var(--mono)"><button class="btn primary" id="p8-go">Вычислить</button></div>'
+'<div class="fld" style="gap:6px"><button class="btn p8-ex" data-f="H2O">H₂O</button><button class="btn p8-ex" data-f="CO2">CO₂</button><button class="btn p8-ex" data-f="H2SO4">H₂SO₄</button><button class="btn p8-ex" data-f="CaCO3">CaCO₃</button></div>'
+'<div class="out" id="p8-out">Введи формулу и нажми «Вычислить».</div>')
+rememberBox(['$M_r$ — сумма $A_r$ всех атомов формулы.','$M_r$ безразмерна.','Индекс умножает $A_r$ соответствующего элемента.'])
+qList(['Вычисли $M_r(\\text{Na}_2\\text{CO}_3)$.','Во сколько раз молекула воды тяжелее $\\tfrac{1}{12}$ атома углерода-12?','Вычисли $M_r(\\text{Fe}_2\\text{O}_3)$.'])
+secNav('p7','p9')+readButton('p8');
wireReadBtn('p8');
}
function build_p9(){
document.getElementById('p9-body').innerHTML =
'<div class="para-hero"><div class="ph-label">§ 9 · Химия 7</div><h2>Валентность</h2>'
+'<div class="ph-formula">H — I, O — II</div>'
+'<div class="ph-desc">Сколько связей образует атом и как по валентности составить формулу вещества.</div>'
+'<div class="ph-tags"><span class="ph-tag">валентность</span><span class="ph-tag">НОК</span></div></div>'
+makeCard('theory','Что такое валентность','§9','<p><b>Валентность</b> — способность атома соединяться с определённым числом атомов других элементов. За единицу валентности принята валентность атома <b>водорода (I)</b>. В $\\text{HCl}$ хлор одновалентен, в $\\text{H}_2\\text{O}$ кислород двухвалентен, в $\\text{NH}_3$ азот трёхвалентен, в $\\text{CH}_4$ углерод четырёхвалентен.</p>')
+makeCard('rule','Составление формул по валентности','§9','<p>Кислород в соединениях обычно имеет валентность <b>II</b>, водород — <b>I</b>. Зная валентности, формулу составляют так, чтобы суммарное число единиц валентности обоих элементов совпало (по наименьшему общему кратному).</p>'
+'<div class="def-box">Постоянные валентности: H — I; O — II; Na, K — I; Mg, Ca, Zn — II; Al — III.</div>')
+makeCard('example','Оксид алюминия',null,'<p>Al — III, O — II. НОК(3, 2) = 6. Индексы: Al → 6/3 = 2, O → 6/2 = 3.</p><div class="exa-step">Формула: $\\text{Al}_2\\text{O}_3$.</div>')
+wgt('Конструктор формулы по валентности','<div id="p9-bld"></div>')
+rememberBox(['Валентность — число связей атома; единица — валентность водорода.','Кислород — II, водород — I (постоянные).','Формулу составляют по НОК валентностей.'])
+qList(['Определи валентность серы в $\\text{SO}_2$ (O — II).','Составь формулу соединения кальция (II) с кислородом.','Чему равна валентность азота в $\\text{NH}_3$?'])
+secNav('p8','p10')+readButton('p9');
wireReadBtn('p9');
}
/* заглушки для ещё не наполненных § (фазы — следующие волны) */
(function(){
var P = window.PARAS, B = {};
@@ -319,6 +396,9 @@ window.BUILDERS.p3 = build_p3;
window.BUILDERS.p4 = build_p4;
window.BUILDERS.p5 = build_p5;
window.BUILDERS.p6 = build_p6;
window.BUILDERS.p7 = build_p7;
window.BUILDERS.p8 = build_p8;
window.BUILDERS.p9 = build_p9;
</script>
</body>