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()
+ + '
' : '')
+ ''
+ ''+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
+