feat(chemistry7): Phase 1 Волна 4 — Глава 1 завершена (§§10–12 + ЛО1 + финал)

§10 Физические и химические явления (детектор признаков реакции),
ЛО1 Признаки реакций (опыты с признаками), §11 Закон сохранения массы
(весы сохранения массы), §12 Составление уравнений (балансировщик через
Chem8.equationBalancer), финал главы (6 интегрированных боссов + шпаргалка).

Глава 1 «Первоначальные химические понятия» наполнена полностью (12§).
Тесты: 10/10 chem7 pass; полный прогон 156/159 (3 — известный baseline Auth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 18:33:55 +03:00
parent bc50a0d9f1
commit 13cbbacc1f
3 changed files with 209 additions and 6 deletions
+71 -2
View File
@@ -298,10 +298,79 @@
$('p9-a').addEventListener('change', upd); $('p9-b').addEventListener('change', upd); upd();
}
/* ── Волна 4 ── */
/* §10 / ЛО1 — детектор признаков химической реакции */
var DEMOS = [
{ name: 'Нагревание малахита', signs: ['изменение цвета: зелёный → чёрный', 'выделение газа (водяной пар и углекислый газ)'] },
{ name: 'Сливание растворов CuSO₄ и NaOH', signs: ['образование осадка (голубой)', 'изменение цвета раствора'] },
{ name: 'Горение серы', signs: ['выделение света и тепла (пламя)', 'появление резкого запаха'] },
{ name: 'Добавление соды в уксус', signs: ['выделение газа (пузырьки)'] }
];
function mount_signs(mountId) {
var m = $(mountId); if (!m || m._built) return; m._built = 1;
var idx = 0;
function render() {
m.innerHTML = '<div class="fld"><label>Опыт</label><select id="' + mountId + '-pick">'
+ DEMOS.map(function (d, i) { return '<option value="' + i + '"' + (i === idx ? ' selected' : '') + '>' + esc(d.name) + '</option>'; }).join('') + '</select>'
+ '<button class="btn primary" id="' + mountId + '-go">Провести опыт</button></div>'
+ '<div class="out" id="' + mountId + '-out">Выбери опыт и нажми «Провести опыт».</div>';
$(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; m._built = 0; render(); });
$(mountId + '-go').addEventListener('click', function () {
var d = DEMOS[idx], out = $(mountId + '-out'); out.className = 'out ok';
out.innerHTML = '<b>Наблюдаемые признаки реакции:</b><div style="margin-top:6px">'
+ d.signs.map(function (s) { return '<div style="padding:5px 10px;margin:3px 0;border-radius:8px;background:var(--pri-soft);font-weight:600">&#10003; ' + esc(s) + '</div>'; }).join('')
+ '</div><div style="font-size:.84rem;color:var(--muted);margin-top:6px">Эти признаки указывают, что произошла <b>химическая реакция</b> — образовались новые вещества.</div>';
});
}
render();
}
function mount_p10() { mount_signs('p10-signs'); }
function mount_lo1() { mount_signs('lo1-signs'); }
/* §11 — весы сохранения массы */
function mount_p11() {
var m = $('p11-bal'); if (!m || m._built) return; m._built = 1;
var mixed = false;
function scale(level) {
// level: 0 = равновесие
return '<svg viewBox="0 0 320 130" width="100%" style="max-width:340px">'
+ '<line x1="160" y1="14" x2="160" y2="40" stroke="var(--muted)" stroke-width="3"/>'
+ '<line x1="60" y1="40" x2="260" y2="40" stroke="var(--muted)" stroke-width="3"/>'
+ '<circle cx="160" cy="14" r="6" fill="var(--pri)"/>'
+ '<rect x="30" y="55" width="80" height="34" rx="6" fill="var(--pri-soft)" stroke="var(--border)"/>'
+ '<rect x="210" y="55" width="80" height="34" rx="6" fill="var(--pri-soft)" stroke="var(--border)"/>'
+ '<line x1="60" y1="40" x2="70" y2="55" stroke="var(--muted)" stroke-width="2"/><line x1="110" y1="40" x2="100" y2="55" stroke="var(--muted)" stroke-width="2"/>'
+ '<line x1="210" y1="40" x2="220" y2="55" stroke="var(--muted)" stroke-width="2"/><line x1="260" y1="40" x2="250" y2="55" stroke="var(--muted)" stroke-width="2"/>'
+ '<text x="70" y="77" font-size="13" font-weight="700" fill="var(--text)">100 г</text>'
+ '<text x="250" y="77" font-size="13" font-weight="700" fill="var(--text)">100 г</text>'
+ '<text x="70" y="108" font-size="11" fill="var(--muted)">' + (mixed ? 'продукты' : 'реагенты') + '</text>'
+ '<text x="225" y="108" font-size="11" fill="var(--muted)">' + (mixed ? 'продукты' : 'реагенты') + '</text>'
+ '</svg>';
}
function render() {
m.innerHTML = scale()
+ '<div style="margin:6px 0;font-size:.92rem">' + (mixed
? 'После реакции: <b>осадок Cu(OH)₂ + раствор Na₂SO₄</b>. Стрелка весов не сдвинулась — <b>масса сохранилась</b> (100 г = 100 г).'
: 'До реакции: <b>раствор CuSO₄ + раствор NaOH</b>, общая масса 100 г.') + '</div>'
+ '<button class="btn primary" id="p11-mix">' + (mixed ? 'Сбросить' : 'Смешать растворы') + '</button>';
$('p11-mix').addEventListener('click', function () { mixed = !mixed; m._built = 0; render(); });
}
render();
}
/* §12 — балансировщик уравнений (переиспользуем Chem8.equationBalancer) */
function mount_p12() {
var pick = $('p12-pick'), mount = $('p12-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();
}
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,
p7: mount_p7, p8: mount_p8, p9: mount_p9
p7: mount_p7, p8: mount_p8, p9: mount_p9,
p10: mount_p10, lo1: mount_lo1, p11: mount_p11
});
W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {});
W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, { p12: mount_p12 });
})(window);