From 639f985e6f6a375bece7f62743109c6d20a9620d Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 19:54:50 +0300 Subject: [PATCH] =?UTF-8?q?feat(chemistry7):=20=D0=B2=D0=B8=D0=B7=D1=83?= =?UTF-8?q?=D0=B0=D0=BB=20V4=20(=D0=93=D0=BB=D0=B0=D0=B2=D0=B0=204)=20?= =?UTF-8?q?=E2=80=94=20=D1=8D=D0=BB=D0=B5=D0=BA=D1=82=D1=80=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=202:1,=20=D0=B8=D0=BD=D0=B4=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D1=8B,=20=D1=82=D0=B8=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Подключён chem7_anim.js в Главу 4. - §23 (звёздный): электролиз воды — два потока пузырьков H₂ (18) и O₂ (9), наглядно 2:1; - §24/ЛО5 индикаторы щёлочи: блок плавно меняет цвет (фенолфталеин → малиновый); - §25/ПР4 нейтрализация (звёздный): раствор плавно обесцвечивается малиновый → бесцветный (colorBlock). Все 4 главы анимированы. Тесты chem7: 16/16; полный прогон 162/165 (3 — baseline Auth). Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/chemistry7-page.test.js | 3 +++ frontend/js/chem7_ch4_widgets.js | 36 +++++++++++++++++-------- frontend/textbooks/chemistry_7_ch4.html | 1 + 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/backend/tests/chemistry7-page.test.js b/backend/tests/chemistry7-page.test.js index 2789b82..e16db67 100644 --- a/backend/tests/chemistry7-page.test.js +++ b/backend/tests/chemistry7-page.test.js @@ -225,13 +225,16 @@ test('ch3 Волна 2: §21 + ЛО4 + §22 + ПР3 + финал главы мо test('ch4: вся глава 4 (§23–§26 + ЛО5 + ПР4 + финал) монтируется', async () => { const { doc, errors } = await loadDom('chemistry_7_ch4.html'); assert.ok(doc.querySelector('#p23-water #p23-pick'), 'разложение/реакции воды §23'); + assert.ok(doc.querySelector('#p23-bub-h div'), 'пузырьки электролиза 2:1 §23'); doc.defaultView.goTo('p24'); await wait(100); assert.ok(doc.querySelector('#p24-bld #p24-m'), 'конструктор оснований §24'); assert.ok(doc.querySelector('#p24-ind #p24-ind-sel'), 'индикаторы щёлочи §24'); + assert.ok(doc.querySelector('#p24-ind-drop div'), 'анимация индикатора §24'); doc.defaultView.goTo('lo5'); await wait(100); assert.ok(doc.querySelector('#lo5-ind #lo5-ind-sel'), 'индикаторы ЛО5'); doc.defaultView.goTo('p25'); await wait(100); assert.ok(doc.querySelector('#p25-neu #p25-neu-go'), 'нейтрализация §25'); + assert.ok(doc.querySelector('#p25-neu-cup div'), 'анимация раствора §25'); doc.defaultView.goTo('pr4'); await wait(100); assert.ok(doc.querySelector('#pr4-neu #pr4-neu-go'), 'нейтрализация ПР4'); doc.defaultView.goTo('p26'); await wait(100); diff --git a/frontend/js/chem7_ch4_widgets.js b/frontend/js/chem7_ch4_widgets.js index 42dcb65..b85b14c 100644 --- a/frontend/js/chem7_ch4_widgets.js +++ b/frontend/js/chem7_ch4_widgets.js @@ -36,15 +36,23 @@ } function mount_p23() { var m = $('p23-water'); if (!m || m._built) return; m._built = 1; - var idx = 0; + var idx = 0, anims = []; + function stopAnim(){ anims.forEach(function(a){ try { a.stop(); } catch(e){} }); anims = []; } function render(){ + stopAnim(); var r = WRX[idx]; - m.innerHTML = (idx===0 ? decompSvg() : '') + m.innerHTML = (idx===0 ? decompSvg() + + '
H₂ — 2 объёма
' + + '
O₂ — 1 объём
' : '') + '
' + '
'+ceq(r.eq,{cond:r.cond})+'
' + '
'+esc(r.note)+'
'; - $('p23-pick').addEventListener('change', function(e){ idx=+e.target.value; m._built=0; render(); }); + if (idx===0 && W.Chem7Anim) { + anims.push(W.Chem7Anim.bubbleField($('p23-bub-h'), { color:'rgba(96,165,250,.9)', count:18, h:84, bg:'linear-gradient(180deg,#dbeafe,transparent)' })); + anims.push(W.Chem7Anim.bubbleField($('p23-bub-o'), { color:'rgba(248,113,113,.9)', count:9, h:84, bg:'linear-gradient(180deg,#fee2e2,transparent)' })); + } + $('p23-pick').addEventListener('change', function(e){ idx=+e.target.value; render(); }); } render(); } @@ -57,14 +65,17 @@ }; function alkIndicator(mountId) { var m = $(mountId); if (!m || m._built) return; m._built = 1; - var ind = 'Фенолфталеин'; + var ind = 'Фенолфталеин', anim = null; function render(){ + if (anim) { anim.stop(); anim = null; } var c = ALK_IND[ind]; m.innerHTML = '
' + + '
' + '
В нейтральной среде: ' + strip(c.neutral[0]) + ' '+c.neutral[1]+'
' + 'В щёлочи: ' + strip(c.alk[0]) + ' '+c.alk[1]+'
'; - $(mountId+'-sel').addEventListener('change', function(e){ ind=e.target.value; m._built=0; render(); }); + if (W.Chem7Anim) anim = W.Chem7Anim.colorBlock($(mountId+'-drop'), c.neutral[0], c.alk[0], ind + ' в щёлочи → ' + c.alk[1], 900); + $(mountId+'-sel').addEventListener('change', function(e){ ind=e.target.value; render(); }); } render(); } @@ -94,16 +105,19 @@ /* §25 / ПР4 — нейтрализация (фенолфталеин малиновый → бесцветный) */ function mount_neutral(mountId) { var m = $(mountId); if (!m || m._built) return; m._built = 1; - var done = false; - function beaker(color){ return ''; } + var done = false, anim = null; function render(){ - m.innerHTML = '
' + beaker(done?'#f8fafc':'#db2777') - + '
'+(done + if (anim) { anim.stop(); anim = null; } + m.innerHTML = '
' + + '
'+(done ? 'Раствор стал бесцветным — кислота нейтрализовала щёлочь. Реакция завершена.' - : 'В щёлочи с фенолфталеином раствор малиновый. Добавляй кислоту по каплям.')+'
' + : 'В щёлочи с фенолфталеином раствор малиновый. Добавляй кислоту по каплям.')+'
' + '
' + (done ? '
'+ceq('HCl + NaOH = NaCl + H2O')+'
Кислота + основание → соль + вода. Это реакция нейтрализации.
' : ''); - $(mountId+'-go').addEventListener('click', function(){ done=!done; m._built=0; render(); }); + if (W.Chem7Anim) anim = done + ? W.Chem7Anim.colorBlock($(mountId+'-cup'), '#db2777', '#f8fafc', 'малиновый → бесцветный', 1600) + : W.Chem7Anim.colorBlock($(mountId+'-cup'), '#db2777', '#db2777', 'щёлочь + фенолфталеин', 1); + $(mountId+'-go').addEventListener('click', function(){ done=!done; render(); }); } render(); } diff --git a/frontend/textbooks/chemistry_7_ch4.html b/frontend/textbooks/chemistry_7_ch4.html index 7b79c64..b75c506 100644 --- a/frontend/textbooks/chemistry_7_ch4.html +++ b/frontend/textbooks/chemistry_7_ch4.html @@ -23,6 +23,7 @@ html.dark{--bg:#0a1222;--border:#1e3a5f;--pri-soft:rgba(37,99,235,.18);--sec-acc +