diff --git a/backend/tests/chemistry8-page.test.js b/backend/tests/chemistry8-page.test.js
index 8a18dc9..f64fbf3 100644
--- a/backend/tests/chemistry8-page.test.js
+++ b/backend/tests/chemistry8-page.test.js
@@ -141,3 +141,17 @@ test('ch5: SPA без ошибок, 5 карточек, §42 активен, с.
doc.defaultView.goTo('p44'); await wait(120);
assert.ok(doc.querySelector('#c-redox-pick option'), 'электронный баланс §44');
});
+
+/* ── Глава 6 ── */
+test('ch6: SPA без ошибок, 8 карточек, §46 активен, w/c калькуляторы', async () => {
+ const { doc, errors } = await loadDom('chemistry_8_ch6.html', '/js/chem8_ch6_widgets.js');
+ assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
+ assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 8, '7 § + финал');
+ assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p46', '§46 активен');
+ await wait(120);
+ assert.ok(doc.querySelector('#c-mix .cls-chip'), 'классификатор смесей §46');
+ doc.defaultView.goTo('p50'); await wait(120);
+ assert.ok(doc.querySelector('#c-wcalc #w-go'), 'калькулятор w §50');
+ doc.defaultView.goTo('p51'); await wait(120);
+ assert.ok(doc.querySelector('#c-ccalc #c-go'), 'калькулятор c §51');
+});
diff --git a/backend/tests/chemistry8.test.js b/backend/tests/chemistry8.test.js
index b39f87a..4e8d7b5 100644
--- a/backend/tests/chemistry8.test.js
+++ b/backend/tests/chemistry8.test.js
@@ -113,13 +113,9 @@ test('каждая глава существует, ссылается на ха
const html = fs.readFileSync(path.join(TB, ch.file), 'utf8');
assert.ok(html.includes('/textbook/chemistry-8"'), ch.file + ' ссылка назад в хаб');
assert.ok(html.includes('/js/chem8_svg.js'), ch.file + ' подключает chem8_svg');
- if (['chemistry-8-intro', 'chemistry-8-ch1', 'chemistry-8-ch2', 'chemistry-8-ch3', 'chemistry-8-ch4', 'chemistry-8-ch5'].includes(ch.slug)) {
- // перестроены на движок (SPA): slug задаётся через CHEM8_CFG
- assert.ok(html.includes("slug:'" + ch.slug + "'"), ch.file + ' slug в CHEM8_CFG');
- assert.ok(html.includes('/js/chem8_engine.js'), ch.file + ' подключает движок');
- } else {
- assert.ok(html.includes("const _TB_SLUG = '" + ch.slug + "'"), ch.file + ' slug (каркас)');
- }
+ // все 8 страниц (intro + 6 глав) перестроены на движок chem8_engine.js (SPA)
+ assert.ok(html.includes("slug:'" + ch.slug + "'"), ch.file + ' slug в CHEM8_CFG');
+ assert.ok(html.includes('/js/chem8_engine.js'), ch.file + ' подключает движок');
}
});
@@ -197,6 +193,16 @@ test('Phase 6 — Глава 5 построена + oxStates корректен'
assert.equal(C.oxStates('HNO3').N, 5, 'N в HNO₃ = +5');
});
+test('Phase 6 — Глава 6 построена (§46–52 + ПР4 + финал)', () => {
+ const html = fs.readFileSync(path.join(TB, 'chemistry_8_ch6.html'), 'utf8');
+ for (let i = 46; i <= 52; i++) assert.ok(html.includes('id="sec-p' + i + '"'), '§' + i + ' секция');
+ assert.ok(html.includes('id="c-mix"'), 'классификатор смесей §46');
+ assert.ok(html.includes('id="c-wcalc"'), 'калькулятор w §50');
+ assert.ok(html.includes('id="c-ccalc"'), 'калькулятор c §51');
+ assert.ok(html.includes('Практическая работа 4'), 'ПР4');
+ assert.ok(html.includes('/js/chem8_ch6_widgets.js'), 'виджеты главы 6');
+});
+
test('chem8_engine.js и виджеты — валидный синтаксис', () => {
const eng = fs.readFileSync(path.join(ROOT, 'frontend', 'js', 'chem8_engine.js'), 'utf8');
const wid = fs.readFileSync(path.join(ROOT, 'frontend', 'js', 'chem8_intro_widgets.js'), 'utf8');
diff --git a/frontend/js/chem8_ch6_widgets.js b/frontend/js/chem8_ch6_widgets.js
new file mode 100644
index 0000000..bbffc45
--- /dev/null
+++ b/frontend/js/chem8_ch6_widgets.js
@@ -0,0 +1,81 @@
+/* chem8_ch6_widgets.js — виджеты Главы 6 «Растворы».
+ * Использует window.Chem8: classifier, solubilityTable, molarMass.
+ */
+(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 ? 2 : d); return (Math.round(v * p) / p).toString().replace('.', ','); }
+
+ /* §46 — классификатор смесей */
+ function mount_p46() {
+ var el = $('c-mix'); if (!el || el._b || !C().classifier) return; el._b = 1;
+ C().classifier(el, {
+ items: [
+ { id: 'air', label: 'воздух', cat: 'odn' }, { id: 'saltsol', label: 'раствор соли', cat: 'odn' }, { id: 'steel', label: 'сталь', cat: 'odn' },
+ { id: 'sandwater', label: 'песок + вода', cat: 'neod' }, { id: 'milk', label: 'молоко', cat: 'neod' }, { id: 'granite', label: 'гранит', cat: 'neod' }
+ ],
+ buckets: [{ cat: 'odn', label: 'Однородные (растворы)' }, { cat: 'neod', label: 'Неоднородные' }],
+ onCheck: function (ok) { if (ok && W.addXp) W.addXp(8, 'p46-mix'); }
+ });
+ }
+
+ /* §48 — кривая растворимости s = f(t) */
+ var CURVE = { KNO3: [13, 21, 32, 46, 64, 88, 110, 138, 169, 202, 246], NaCl: [35.7, 35.8, 36, 36.3, 36.6, 37, 37.3, 37.8, 38.4, 39, 39.8] };
+ function mount_p48() {
+ var el = $('c-solcurve'); if (!el || el._b) return; el._b = 1;
+ el.innerHTML = '
Вещество KNO₃ (нитрат калия) NaCl (соль) t, °C
';
+ var sub = $('sc-sub'), tr = $('sc-t'), tv = $('sc-tv'), plot = $('sc-plot'), out = $('sc-out');
+ function draw() {
+ var data = CURVE[sub.value], t = +tr.value, idx = t / 10, s = data[idx];
+ tv.textContent = t + ' °C';
+ var maxS = Math.max.apply(null, CURVE.KNO3);
+ var W0 = 280, H0 = 140, pad = 24;
+ var pts = data.map(function (v, i) { var x = pad + i / 10 * (W0 - pad * 2); var y = H0 - pad - v / maxS * (H0 - pad * 2); return x.toFixed(1) + ',' + y.toFixed(1); }).join(' ');
+ var cx = pad + idx / 10 * (W0 - pad * 2), cy = H0 - pad - s / maxS * (H0 - pad * 2);
+ plot.innerHTML = ''
+ + ' '
+ + ' '
+ + ' '
+ + ' '
+ + 't, °C '
+ + 's, г/100г '
+ + ' ';
+ out.className = 'out ok';
+ out.innerHTML = 'При ' + t + ' °C растворимость ' + sub.value + ' ≈ ' + rr(s, 1) + ' г на 100 г воды.' + (sub.value === 'KNO3' ? ' Растворимость сильно растёт с температурой.' : ' У NaCl почти не зависит от t.') + ' ';
+ }
+ sub.addEventListener('change', draw); tr.addEventListener('input', draw); draw();
+ }
+
+ /* §50 — массовая доля w */
+ function mount_p50() {
+ var el = $('c-wcalc'); if (!el || el._b) return; el._b = 1;
+ el.innerHTML = 'm(вещества), г m(воды), г Найти w
';
+ function calc() {
+ var ms = parseFloat($('w-ms').value), mw = parseFloat($('w-mw').value);
+ if (isNaN(ms) || isNaN(mw) || ms + mw <= 0) { $('w-out').className = 'out bad'; $('w-out').textContent = 'Введите массы.'; return; }
+ var w = ms / (ms + mw) * 100;
+ $('w-out').className = 'out ok';
+ $('w-out').innerHTML = 'm(раствора) = ' + ms + ' + ' + mw + ' = ' + (ms + mw) + ' г w = m(в-ва)/m(р-ра) = ' + ms + '/' + (ms + mw) + ' = ' + rr(w, 1) + ' % ';
+ }
+ $('w-go').addEventListener('click', calc); calc();
+ }
+
+ /* §51 — молярная концентрация c = n/V */
+ function mount_p51() {
+ var el = $('c-ccalc'); if (!el || el._b) return; el._b = 1;
+ el.innerHTML = 'Вещество m, г V, л Найти c
';
+ function calc() {
+ var f = $('c-f').value.trim(), M = C().molarMass ? C().molarMass(f) : NaN, m = parseFloat($('c-m').value), V = parseFloat($('c-v').value);
+ if (isNaN(M)) { $('c-out').className = 'out bad'; $('c-out').textContent = 'Не удалось разобрать формулу.'; return; }
+ if (isNaN(m) || isNaN(V) || V <= 0) { $('c-out').className = 'out bad'; $('c-out').textContent = 'Введите m и V.'; return; }
+ var n = m / M, c = n / V;
+ $('c-out').className = 'out ok';
+ $('c-out').innerHTML = 'M(' + f + ') = ' + M + ' г/моль n = m/M = ' + m + '/' + M + ' = ' + rr(n) + ' моль c = n/V = ' + rr(n) + '/' + rr(V) + ' = ' + rr(c) + ' моль/л ';
+ }
+ $('c-go').addEventListener('click', calc); calc();
+ }
+
+ W.CHEM8_WIDGETS = { p46: mount_p46, p50: mount_p50, p51: mount_p51 };
+ W.FLAG_MOUNTS = { p48: mount_p48 };
+})(window);
diff --git a/frontend/textbooks/chemistry_8_ch6.html b/frontend/textbooks/chemistry_8_ch6.html
index f533c23..2d1f0ef 100644
--- a/frontend/textbooks/chemistry_8_ch6.html
+++ b/frontend/textbooks/chemistry_8_ch6.html
@@ -7,131 +7,224 @@
Химия 8 · Глава 6 · «Растворы»
-
+
+
+
-
+
+
-
-
-
- К разделам
-
+
-
Глава 6 · § 46–52
-
Растворы
+
Химия 8 · Глава 6
+
Смеси и растворы, растворимость, массовая доля и молярная концентрация, вода в жизни человека
-
-
-
-
-
Раздел в разработке
-
Интерактивное наглядное наполнение этого раздела (теория, модели, симуляторы, тренажёры и боссы) добавляется поэтапно. Ниже — план параграфов раздела согласно учебнику.
-
-
+
+
+
+ Самые важные смеси на Земле
+ Морская вода, кровь, лимонад, воздух — это всё растворы и смеси. Химик умеет описывать их состав количественно: массовой долей и молярной концентрацией.
+
+
-
-
- Содержание раздела
+
+
+
+
+
+
+
+
+
+
-
- § 46 Смеси веществ
- § 47 Растворение веществ в воде
- § 48 Характеристики растворимости веществ
- § 49 Качественные характеристики состава растворов
- § 50 Количественные характеристики растворённых веществ. Массовая доля растворённого вещества
- § 51 Молярная концентрация растворённых веществ
- Практическая работа 4. Приготовление раствора с заданной массовой долей и молярной концентрацией
- § 52 Вода и растворы в жизни и деятельности человека
-
+
-
+
+