Files
Learn_System/frontend/js/chem7_ch3_widgets.js
Maxim Dolgolyov 33f968bff9 feat(chemistry7): визуал V3 (Глава 3) — пузырьки, морфинг цвета, индикаторы
Подключён chem7_anim.js в Главу 3.
- §21 ряд активности (звёздный): клик металла левее H₂ → анимация пузырьков
  H₂ (bubbleField); правее (Cu, Ag) — «реакция не идёт»;
- §19 восстановление CuO: colorBlock плавно чёрный→красный (медь); горение —
  пламя водорода;
- §20/ЛО3 индикаторы: блок плавно меняет цвет на цвет индикатора в кислоте.

Тесты chem7: 16/16; полный прогон 162/165 (3 — baseline Auth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:51:27 +03:00

188 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* chem7_ch3_widgets.js — интерактивы главы 3 «Водород» (Химия 7).
* Монтируются движком chem8_engine.js: window.CHEM8_WIDGETS[id].
* Используют window.Chem8 (chem8_svg.js): chemEq, formula.
* Без эмоджи; KaTeX — через window.chem8RenderMath.
*/
(function (W) {
'use strict';
function C() { return W.Chem8 || {}; }
function $(id) { return document.getElementById(id); }
function esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function gcd(a, b) { return b ? gcd(b, a % b) : a; }
function ceq(src, opts){ return C().chemEq ? C().chemEq(src, opts || {}) : esc(src); }
function fml(s){ return C().formula ? C().formula(s) : s; }
var COL = { H:'#cbd5e1', O:'#ef4444' };
function molSvg(atoms){
var list=[]; atoms.forEach(function(p){ for(var i=0;i<p[1];i++) list.push(p[0]); });
var x=22, svg=''; list.forEach(function(el){ x+=16; svg+='<circle cx="'+x+'" cy="30" r="15" fill="'+(COL[el]||'#94a3b8')+'" stroke="rgba(0,0,0,.25)"/><text x="'+x+'" y="35" text-anchor="middle" font-size="12" font-weight="700" fill="#fff">'+el+'</text>'; x+=24; });
return '<svg viewBox="0 0 '+(x+10)+' 60" width="100%" style="max-width:'+(x+10)+'px;height:auto">'+svg+'</svg>';
}
/* §18 — модель H₂ + паспорт водорода */
function mount_p18() {
var m = $('p18-card'); if (!m || m._built) return; m._built = 1;
m.innerHTML = molSvg([['H',2]])
+ '<div class="out ok"><b>Водород</b><br>Элемент: символ H, $Z=1$, $A_r=1$ — самый лёгкий элемент.<br>'
+ 'Простое вещество: молекула $H_2$ — самый лёгкий газ, без цвета и запаха, легче воздуха, мало растворим в воде.<br>'
+ 'В природе: в составе воды, многих веществ; во Вселенной — самый распространённый элемент.</div>';
if (W.chem8RenderMath) try { W.chem8RenderMath(m); } catch(e){}
}
/* §19 — реакции водорода: горение и восстановление */
var RX = [
{ name:'Горение водорода в кислороде', eq:'2H2 + O2 = 2H2O', note:'Водород горит, образуя воду. Смесь водорода с воздухом — «гремучий газ», взрывается!' },
{ name:'Восстановление оксида меди(II)', eq:'H2 + CuO = Cu + H2O', note:'Водород отнимает кислород у оксида: чёрный CuO превращается в красную медь. Водород здесь — восстановитель.' }
];
function mount_p19() {
var m = $('p19-rx'); if (!m || m._built) return; m._built = 1;
var idx = 0, anim = null;
function stopAnim(){ if(anim){anim.stop();anim=null;} }
function render(){
stopAnim();
var r = RX[idx];
m.innerHTML = '<div class="fld"><label>Реакция</label><select id="p19-pick">'
+ RX.map(function(x,i){ return '<option value="'+i+'"'+(i===idx?' selected':'')+'>'+esc(x.name)+'</option>'; }).join('') + '</select></div>'
+ '<div id="p19-stage" style="margin:8px 0"></div>'
+ '<div class="out ok" style="margin-top:8px"><div style="font-size:1.05rem">' + ceq(r.eq) + '</div><div style="font-size:.86rem;color:var(--muted);margin-top:6px">' + esc(r.note) + '</div></div>';
var stage = $('p19-stage');
if (stage && W.Chem7Anim) {
if (idx === 1) anim = W.Chem7Anim.colorBlock(stage, '#1f2937', '#b45309', 'CuO (чёрный) → Cu (красная медь)', 1800);
else anim = W.Chem7Anim.flameBox(stage, { color: '#93c5fd' });
}
$('p19-pick').addEventListener('change', function(e){ idx=+e.target.value; render(); });
}
render();
}
/* индикаторы */
var ACIDS = [
{ f:'HCl', name:'соляная', res:'Cl', resName:'хлорид', resVal:1 },
{ f:'H2SO4', name:'серная', res:'SO4', resName:'сульфат', resVal:2 },
{ f:'HNO3', name:'азотная', res:'NO3', resName:'нитрат', resVal:1 },
{ f:'H2CO3', name:'угольная', res:'CO3', resName:'карбонат',resVal:2 }
];
var INDIC = {
'Лакмус': { neutral:['#7c3aed','фиолетовый'], acid:['#dc2626','красный'] },
'Метилоранж': { neutral:['#f59e0b','оранжевый'], acid:['#e11d48','розово-красный'] }
};
function indicatorWidget(mountId, withAcidPick) {
var m = $(mountId); if (!m || m._built) return; m._built = 1;
var ind = 'Лакмус', acid = 0, anim = null;
function strip(color){ return '<div style="width:120px;height:34px;border-radius:8px;border:1.5px solid var(--border);background:'+color+';display:inline-block;vertical-align:middle"></div>'; }
function render(){
if (anim) { anim.stop(); anim = null; }
var a = ACIDS[acid], col = INDIC[ind];
m.innerHTML = '<div class="fld"><label>Индикатор</label><select id="'+mountId+'-ind">'
+ Object.keys(INDIC).map(function(k){ return '<option'+(k===ind?' selected':'')+'>'+k+'</option>'; }).join('') + '</select>'
+ (withAcidPick ? '<label>Кислота</label><select id="'+mountId+'-acid">' + ACIDS.map(function(x,i){ return '<option value="'+i+'"'+(i===acid?' selected':'')+'>'+fml(x.f)+' ('+x.name+')</option>'; }).join('') + '</select>' : '') + '</div>'
+ '<div id="'+mountId+'-drop" style="margin-top:8px"></div>'
+ '<div class="out ok" style="margin-top:8px">В нейтральной среде: ' + strip(col.neutral[0]) + ' <b>'+col.neutral[1]+'</b><br>'
+ 'В кислоте' + (withAcidPick?(' ('+fml(a.f)+')'):'') + ': ' + strip(col.acid[0]) + ' <b>'+col.acid[1]+'</b></div>';
if (W.Chem7Anim) anim = W.Chem7Anim.colorBlock($(mountId+'-drop'), col.neutral[0], col.acid[0], ind + ' в кислоте → ' + col.acid[1], 900);
$(mountId+'-ind').addEventListener('change', function(e){ ind=e.target.value; render(); });
if (withAcidPick) $(mountId+'-acid').addEventListener('change', function(e){ acid=+e.target.value; render(); });
}
render();
}
function mount_p20() {
indicatorWidget('p20-ind', true);
var t = $('p20-acids'); if (t && !t._built) { t._built = 1;
t.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:.9rem"><tr style="background:var(--pri-soft)"><th style="padding:6px;text-align:left">Кислота</th><th style="padding:6px;text-align:left">Название</th><th style="padding:6px;text-align:left">Остаток</th></tr>'
+ ACIDS.map(function(a){ return '<tr><td style="padding:6px;border-top:1px solid var(--border)">'+fml(a.f)+'</td><td style="padding:6px;border-top:1px solid var(--border)">'+a.name+'</td><td style="padding:6px;border-top:1px solid var(--border)">'+fml(a.res)+' ('+a.resName+')</td></tr>'; }).join('') + '</table>';
}
}
function mount_lo3() { indicatorWidget('lo3-ind', false); }
/* §21 — ряд активности металлов */
var ROW = ['K','Ca','Na','Mg','Al','Zn','Fe','Ni','Sn','Pb','H','Cu','Hg','Ag','Pt','Au'];
function mount_p21() {
var m = $('p21-act'); if (!m || m._built) return; m._built = 1;
var hIdx = ROW.indexOf('H'), anim = null;
function stopAnim(){ if(anim){anim.stop();anim=null;} }
m.innerHTML = '<div style="display:flex;flex-wrap:wrap;gap:4px">'
+ ROW.map(function(el,i){ var isH=el==='H'; return '<button class="act-cell" data-i="'+i+'" style="padding:6px 9px;border-radius:7px;border:1.5px solid '+(isH?'#dc2626':'var(--border)')+';background:'+(isH?'#fee2e2':'var(--card)')+';color:var(--text);font-weight:700;cursor:'+(isH?'default':'pointer')+'">'+(isH?'H₂':el)+'</button>'; }).join('') + '</div>'
+ '<div style="font-size:.8rem;color:var(--muted);margin-top:4px">Слева активность убывает вправо. Граница — водород H₂. Кликни металл — «опусти» его в кислоту.</div>'
+ '<div id="p21-tube" style="margin-top:8px"></div>'
+ '<div class="out" id="p21-act-out" style="margin-top:8px">Кликни по металлу — узнаешь, вытесняет ли он водород из кислоты.</div>';
var out = $('p21-act-out');
m.querySelectorAll('.act-cell').forEach(function(b){
b.addEventListener('click', function(){
var i=+b.dataset.i, el=ROW[i], tube=$('p21-tube'); stopAnim();
if(el==='H'){ out.className='out'; out.innerHTML='<b>Водород H₂</b> — граница ряда активности.'; if(tube)tube.innerHTML=''; return; }
out.className='out ok';
if(i<hIdx){
var extra = (i<=2) ? ' <span style="color:#dc2626">Внимание: очень активный металл — с кислотами реагирует бурно (для получения водорода используют Zn, Fe).</span>' : '';
out.innerHTML = '<b>'+el+'</b> стоит левее H₂ → <b>вытесняет водород</b> из соляной и серной кислот: образуются соль и $H_2\\uparrow$.'+extra;
if (tube && W.Chem7Anim) anim = W.Chem7Anim.bubbleField(tube, { color:'rgba(255,255,255,.85)', h:96 });
} else {
out.innerHTML = '<b>'+el+'</b> стоит правее H₂ → водород из кислот <b>не вытесняет</b> (например, медь и серебро с этими кислотами не реагируют).';
if (tube) tube.innerHTML = '<div class="out" style="text-align:center;color:var(--muted)">реакция не идёт — пузырьков нет</div>';
}
if (W.chem8RenderMath) try { W.chem8RenderMath(out); } catch(e){}
});
});
}
/* ЛО4 — взаимодействие кислот с металлами */
var L4M = [ ['Zn','цинк',1], ['Fe','железо',1], ['Mg','магний',1], ['Cu','медь',0] ];
var L4A = [ ['HCl','соляная'], ['H2SO4','серная'] ];
function mount_lo4() {
var m = $('lo4-rx'); if (!m || m._built) return; m._built = 1;
var mi=0, ai=0;
var EQ = { 'Zn|HCl':'Zn + 2HCl = ZnCl2 + H2^', 'Zn|H2SO4':'Zn + H2SO4 = ZnSO4 + H2^',
'Fe|HCl':'Fe + 2HCl = FeCl2 + H2^', 'Fe|H2SO4':'Fe + H2SO4 = FeSO4 + H2^',
'Mg|HCl':'Mg + 2HCl = MgCl2 + H2^', 'Mg|H2SO4':'Mg + H2SO4 = MgSO4 + H2^' };
function render(){
m.innerHTML = '<div class="fld"><label>Металл</label><select id="lo4-m">'+L4M.map(function(x,i){return '<option value="'+i+'"'+(i===mi?' selected':'')+'>'+x[1]+' ('+x[0]+')</option>';}).join('')+'</select>'
+ '<label>Кислота</label><select id="lo4-a">'+L4A.map(function(x,i){return '<option value="'+i+'"'+(i===ai?' selected':'')+'>'+fml(x[0])+'</option>';}).join('')+'</select>'
+ '<button class="btn primary" id="lo4-go">Провести опыт</button></div><div class="out" id="lo4-out" style="margin-top:8px">Выбери металл и кислоту.</div>';
$('lo4-m').addEventListener('change',function(e){mi=+e.target.value;m._built=0;render();});
$('lo4-a').addEventListener('change',function(e){ai=+e.target.value;m._built=0;render();});
$('lo4-go').addEventListener('click',function(){
var met=L4M[mi], ac=L4A[ai], out=$('lo4-out');
if(!met[2]){ out.className='out bad'; out.innerHTML='<b>'+met[1]+'</b> стоит правее H₂ в ряду активности — реакция <b>не идёт</b>, пузырьки не выделяются.'; return; }
out.className='out ok';
out.innerHTML='Наблюдаем <b>выделение пузырьков газа</b> (водород $H_2\\uparrow$). Металл вытесняет водород из кислоты:<div style="font-size:1.05rem;margin-top:6px">'+ceq(EQ[met[0]+'|'+ac[0]])+'</div>';
if (W.chem8RenderMath) try { W.chem8RenderMath(out); } catch(e){}
});
}
render();
}
/* §22 — конструктор солей (металл + кислотный остаток) */
var SM = [ ['Na',1], ['K',1], ['Ca',2], ['Mg',2], ['Zn',2], ['Al',3] ];
var SR = [ ['Cl',1,'хлорид'], ['NO3',1,'нитрат'], ['SO4',2,'сульфат'], ['CO3',2,'карбонат'] ];
function mount_p22() {
var m = $('p22-salt'); if (!m || m._built) return; m._built = 1;
function render(){
m.innerHTML = '<div class="fld"><label>Металл</label><select id="p22-m">'+SM.map(function(x,i){return '<option value="'+i+'"'+(x[0]==='Ca'?' selected':'')+'>'+x[0]+' ('+rom(x[1])+')</option>';}).join('')+'</select>'
+ '<label>Остаток</label><select id="p22-r">'+SR.map(function(x,i){return '<option value="'+i+'">'+fml(x[0])+' ('+x[2]+', '+rom(x[1])+')</option>';}).join('')+'</select></div><div class="out" id="p22-out"></div>';
$('p22-m').addEventListener('change',upd); $('p22-r').addEventListener('change',upd); upd();
}
function rom(n){ return ['','I','II','III'][n]; }
function upd(){
var me=SM[+$('p22-m').value], re=SR[+$('p22-r').value];
var lcm=me[1]*re[1]/gcd(me[1],re[1]), x=lcm/me[1], y=lcm/re[1];
var poly=/[0-9]/.test(re[0]);
var raw = me[0] + (x>1?x:'') + (poly && y>1 ? '('+re[0]+')'+y : re[0] + (y>1?y:''));
var out=$('p22-out'); out.className='out ok';
out.innerHTML='<span class="bd">Валентности: '+me[0]+' = '+rom(me[1])+', остаток '+fml(re[0])+' = '+rom(re[1])+'<br>Формула соли ('+re[2]+'а): <b style="font-size:1.15rem">'+fml(raw)+'</b></span>';
}
render();
}
/* ПР3 — чистота водорода («гремучий газ») */
function mount_pr3() {
var m = $('pr3-test'); if (!m || m._built) return; m._built = 1;
m.innerHTML = '<div class="fld"><button class="btn primary" id="pr3-mix">Поджечь смесь H₂ с воздухом</button><button class="btn" id="pr3-pure">Поджечь чистый H₂</button></div><div class="out" id="pr3-out" style="margin-top:8px">Чтобы проверить чистоту водорода, его поджигают.</div>';
$('pr3-mix').addEventListener('click',function(){ var o=$('pr3-out'); o.className='out bad'; o.innerHTML='Смесь водорода с воздухом — «<b>гремучий газ</b>» — взрывается с резким <b>хлопком</b>. Значит, водород собран нечисто.'; });
$('pr3-pure').addEventListener('click',function(){ var o=$('pr3-out'); o.className='out ok'; o.innerHTML='Чистый водород горит <b>спокойно</b>, почти без звука. Значит, газ собран чисто.'; });
}
W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, {
p18: mount_p18, p19: mount_p19, p20: mount_p20, lo3: mount_lo3,
p21: mount_p21, lo4: mount_lo4, p22: mount_p22, pr3: mount_pr3
});
W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {});
})(window);