Files
Learn_System/frontend/js/chem7_ch2_widgets.js
T
Maxim Dolgolyov e8cb95be55 feat(chemistry7): визуал V2 — звёздный флагман §15 «Горение» (анимация пламени)
Подключён chem7_anim.js в Главу 2. §15: статичное SVG-пламя заменено на
анимированный flameBox с достоверным цветом по веществу — углерод оранжевое,
сера синее, фосфор ярко-белое, железо/магний с искрами; продукт-оксид и
уравнение всплывают. Тесты chem7: 16/16 pass.

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

198 lines
15 KiB
JavaScript

/* chem7_ch2_widgets.js — интерактивы главы 2 «Кислород» (Химия 7).
* Монтируются движком chem8_engine.js: window.CHEM8_WIDGETS[id] / window.FLAG_MOUNTS[id].
* Используют window.Chem8 (chem8_svg.js): chemEq, formula, molarMass, arOf.
* Без эмоджи; 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); }
var COL = { H:'#cbd5e1', O:'#ef4444', N:'#3b82f6', C:'#334155', S:'#eab308', P:'#f97316', Fe:'#b45309', Mg:'#22c55e', Cu:'#ea580c' };
function ball(el, x, r){
return '<circle cx="'+x+'" cy="30" r="'+r+'" 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>';
}
function molSvg(atoms){ // atoms: [['O',2]]
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+=ball(el,x,15); x+=24; });
return '<svg viewBox="0 0 '+(x+10)+' 60" width="100%" style="max-width:'+(x+10)+'px;height:auto">'+svg+'</svg>';
}
/* §13 — состав воздуха (стопочная полоса с кликом) */
var AIR = [
{ g:'Азот N₂', p:78, c:'#3b82f6', note:'не поддерживает горение и дыхание' },
{ g:'Кислород O₂', p:21, c:'#ef4444', note:'нужен для дыхания и горения' },
{ g:'Другие газы (Ar, CO₂…)', p:1, c:'#94a3b8', note:'аргон, углекислый газ и др. — около 1 %' }
];
function mount_p13() {
var m = $('p13-air'); if (!m || m._built) return; m._built = 1;
var bar = AIR.map(function (a, i) {
return '<div class="air-seg" data-i="' + i + '" style="width:' + a.p + '%;background:' + a.c + ';height:46px;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:.82rem;cursor:pointer;border-right:2px solid var(--card)">' + a.p + '%</div>';
}).join('');
m.innerHTML = '<div style="display:flex;border-radius:9px;overflow:hidden;border:1.5px solid var(--border)">' + bar + '</div>'
+ '<div class="out" id="p13-air-out" style="margin-top:8px">Кликни по части диаграммы, чтобы узнать о газе.</div>';
var out = $('p13-air-out');
m.querySelectorAll('.air-seg').forEach(function (s) {
s.addEventListener('click', function () {
var a = AIR[+s.dataset.i]; out.className = 'out ok';
out.innerHTML = '<b>' + esc(a.g) + '</b> — около ' + a.p + ' % воздуха. ' + esc(a.note) + '.';
});
});
}
/* ЛО2 — выбор способа собирания газа */
var GASES = [
{ g:'Кислород O₂', heavier:true, ways:['вытеснением воды', 'в сосуд отверстием вверх (тяжелее воздуха)'] },
{ g:'Водород H₂', heavier:false, ways:['вытеснением воды', 'в сосуд отверстием вниз (легче воздуха)'] },
{ g:'Углекислый газ CO₂', heavier:true, ways:['в сосуд отверстием вверх (тяжелее воздуха)'] }
];
function mount_lo2() {
var m = $('lo2-coll'); if (!m || m._built) return; m._built = 1;
var idx = 0;
function render(){
var g = GASES[idx];
m.innerHTML = '<div class="fld"><label>Газ</label><select id="lo2-pick">'
+ GASES.map(function(x,i){ return '<option value="'+i+'"'+(i===idx?' selected':'')+'>'+esc(x.g)+'</option>'; }).join('') + '</select></div>'
+ '<div class="out ok" style="margin-top:6px"><b>' + esc(g.g) + '</b> ' + (g.heavier?'тяжелее':'легче') + ' воздуха.<br>Способы собирания:<br>'
+ g.ways.map(function(w){ return '&#10003; ' + esc(w); }).join('<br>') + '</div>';
$('lo2-pick').addEventListener('change', function(e){ idx=+e.target.value; m._built=0; render(); });
}
render();
}
/* §14 — кислород: элемент или простое вещество + модели O₂ / O₃ */
function mount_p14() {
var m = $('p14-tog'); if (!m || m._built) return; m._built = 1;
var mode = 'el';
function render(){
m.innerHTML = '<div class="fld"><button class="btn'+(mode==='el'?' primary':'')+'" id="p14-el">Элемент O</button>'
+ '<button class="btn'+(mode==='o2'?' primary':'')+'" id="p14-o2">Простое вещество O₂</button>'
+ '<button class="btn'+(mode==='o3'?' primary':'')+'" id="p14-o3">Озон O₃</button></div>'
+ '<div class="out" style="margin-top:8px">' + (
mode==='el' ? '<b>Кислород — химический элемент</b> (символ O, Z = 8, A_r = 16). Так говорят, когда речь о <i>атомах</i> кислорода в составе веществ (например, в воде H₂O).'
: mode==='o2' ? '<b>Кислород — простое вещество</b> O₂: молекула из двух атомов. Газ без цвета и запаха, немного тяжелее воздуха.' + molSvg([['O',2]])
: '<b>Озон</b> O₃: молекула из трёх атомов кислорода — другое простое вещество того же элемента.' + molSvg([['O',3]])
) + '</div>';
$('p14-el').addEventListener('click',function(){mode='el';m._built=0;render();});
$('p14-o2').addEventListener('click',function(){mode='o2';m._built=0;render();});
$('p14-o3').addEventListener('click',function(){mode='o3';m._built=0;render();});
}
render();
}
/* §15 — симулятор горения: вещество + O₂ → оксид */
var FUELS = [
{ el:'C', name:'углерод', eq:'C + O2 = CO2', flame:'#f97316', sparks:false, note:'горит с образованием углекислого газа' },
{ el:'S', name:'сера', eq:'S + O2 = SO2', flame:'#3b82f6', sparks:false, note:'горит синим пламенем, резкий запах' },
{ el:'P', name:'фосфор', eq:'4P + 5O2 = 2P2O5', flame:'#fde68a', sparks:false, note:'горит ярко, с белым дымом' },
{ el:'Fe', name:'железо', eq:'3Fe + 2O2 = Fe3O4', flame:'#f59e0b', sparks:true, note:'горит, разбрасывая искры' },
{ el:'Mg', name:'магний', eq:'2Mg + O2 = 2MgO', flame:'#e0f2fe', sparks:true, note:'горит ослепительно ярким пламенем' }
];
function flame(){
return '<svg viewBox="0 0 60 70" width="56" height="66" style="vertical-align:middle"><path d="M30 8 C40 26 48 34 38 52 C46 46 46 60 30 64 C14 60 14 46 22 52 C12 34 22 26 30 8 Z" fill="#f97316"/><path d="M30 22 C36 34 40 40 33 52 C39 48 38 58 30 60 C22 58 22 50 26 52 C20 40 26 34 30 22 Z" fill="#fde047"/></svg>';
}
function mount_p15() {
var m = $('p15-burn'); if (!m || m._built) return; m._built = 1;
var idx = 0, anim = null;
function stopAnim() { if (anim) { anim.stop(); anim = null; } }
function render(){
stopAnim();
m.innerHTML = '<div class="fld"><label>Вещество</label><select id="p15-pick">'
+ FUELS.map(function(x,i){ return '<option value="'+i+'"'+(i===idx?' selected':'')+'>'+esc(x.name)+' ('+x.el+')</option>'; }).join('') + '</select>'
+ '<button class="btn primary" id="p15-go">Поджечь в кислороде</button></div>'
+ '<div id="p15-stage" style="margin:8px 0"></div>'
+ '<div class="out" id="p15-out" style="margin-top:8px">Выбери вещество и подожги его в кислороде.</div>';
$('p15-pick').addEventListener('change', function(e){ idx=+e.target.value; render(); });
$('p15-go').addEventListener('click', function(){
var f = FUELS[idx], out = $('p15-out');
stopAnim();
if (W.Chem7Anim) anim = W.Chem7Anim.flameBox($('p15-stage'), { color: f.flame, sparks: f.sparks });
out.className='out ok';
out.innerHTML = '<b>' + esc(f.name[0].toUpperCase()+f.name.slice(1)) + ' горит в кислороде:</b> ' + esc(f.note) + '.<br>'
+ '<div style="margin-top:6px;font-size:1.05rem">' + ceq(f.eq) + '</div>'
+ '<div style="font-size:.84rem;color:var(--muted);margin-top:4px">Продукт — <b>оксид</b> (соединение элемента с кислородом).</div>';
});
}
render();
}
/* §16 — конструктор оксида (элемент + валентность, кислород II) + классификатор */
var OXEL = [ ['Na',1], ['Ca',2], ['Mg',2], ['Cu',2], ['Zn',2], ['Al',3], ['C',4], ['S',4], ['P',5] ];
function mount_p16() {
var b = $('p16-bld');
if (b && !b._built) { b._built = 1;
b.innerHTML = '<div class="fld"><label>Элемент</label><select id="p16-el">'
+ OXEL.map(function(e,i){ return '<option value="'+i+'"'+(e[0]==='Al'?' selected':'')+'>'+e[0]+' (валентность '+rom(e[1])+')</option>'; }).join('')
+ '</select> + кислород (O, II)</div><div class="out" id="p16-out"></div>';
function rom(n){ return ['','I','II','III','IV','V'][n]; }
function upd(){
var e = OXEL[+$('p16-el').value], lcm = e[1]*2/gcd(e[1],2), ix=lcm/e[1], iy=lcm/2;
var raw = e[0] + (ix>1?ix:'') + 'O' + (iy>1?iy:'');
var out=$('p16-out'); out.className='out ok';
out.innerHTML = '<span class="bd">Валентности: '+e[0]+' = '+rom(e[1])+', O = II; НОК = '+lcm+'<br>Формула оксида: <b style="font-size:1.15rem">'+(C().formula?C().formula(raw):raw)+'</b></span>';
}
$('p16-el').addEventListener('change',upd); upd();
}
var cl = $('p16-cls');
if (cl && W.Chem7Classify) W.Chem7Classify(cl);
}
/* §17 — схема получения кислорода + роль катализатора */
var PROD = [
{ name:'Разложение перманганата калия (при нагревании)', eq:'2KMnO4 = K2MnO4 + MnO2 + O2^', cond:'t°', note:'Реакция разложения: из одного вещества — несколько. Идёт при нагревании.' },
{ name:'Разложение пероксида водорода (катализатор MnO₂)', eq:'2H2O2 = 2H2O + O2^', cond:'MnO₂', note:'MnO₂ — катализатор: ускоряет реакцию, но сам в ней не расходуется.' }
];
function mount_p17() {
var m = $('p17-prod'); if (!m || m._built) return; m._built = 1;
var idx = 0;
function render(){
var p = PROD[idx];
m.innerHTML = '<div class="fld"><label>Способ</label><select id="p17-pick">'
+ PROD.map(function(x,i){ return '<option value="'+i+'"'+(i===idx?' selected':'')+'>'+esc(x.name)+'</option>'; }).join('') + '</select></div>'
+ '<div class="out ok" style="margin-top:8px"><div style="font-size:1.05rem">' + ceq(p.eq, {cond:p.cond}) + '</div>'
+ '<div style="font-size:.86rem;color:var(--muted);margin-top:6px">' + esc(p.note) + '</div></div>';
$('p17-pick').addEventListener('change', function(e){ idx=+e.target.value; m._built=0; render(); });
}
render();
}
/* ПР2 — проверка кислорода тлеющей лучинкой */
function mount_pr2() {
var m = $('pr2-test'); if (!m || m._built) return; m._built = 1;
m.innerHTML = '<button class="btn primary" id="pr2-go">Внести тлеющую лучинку в сосуд с газом</button><div class="out" id="pr2-out" style="margin-top:8px">Как доказать, что собранный газ — кислород?</div>';
$('pr2-go').addEventListener('click', function(){
var out=$('pr2-out'); out.className='out ok';
out.innerHTML = flame() + ' Тлеющая лучинка <b>ярко вспыхивает</b> — значит, газ поддерживает горение. Это <b>кислород</b>.';
});
}
/* классификатор «оксид / не оксид» (используется в §16) */
W.Chem7Classify = function(mount){
if (!mount || mount._built) return; mount._built = 1;
var items = [ {t:'CuO',ox:1}, {t:'NaCl',ox:0}, {t:'CO₂',ox:1}, {t:'H₂SO₄',ox:0}, {t:'Fe₂O₃',ox:1}, {t:'HCl',ox:0}, {t:'SO₂',ox:1}, {t:'CaO',ox:1} ];
var pool = items.map(function(_,i){return i;}), placed={}, sel=null;
function render(){
var chips = pool.map(function(i){ return '<button class="c7-chip" data-i="'+i+'" style="padding:7px 12px;border:1.5px solid var(--border);border-radius:9px;background:var(--card);color:var(--text);font-weight:600;cursor:pointer;margin:3px">'+esc(items[i].t)+'</button>'; }).join('') || '<span style="color:var(--muted)">Готово.</span>';
var cols = [['Оксид',1],['Не оксид',0]].map(function(b){
var inb = Object.keys(placed).filter(function(k){return placed[k]===b[1];});
var cells = inb.map(function(k){ var ok=items[k].ox===b[1]; return '<div style="padding:5px 10px;margin:3px 0;border-radius:8px;color:#fff;font-weight:600;background:'+(ok?'#059669':'#dc2626')+'">'+esc(items[k].t)+(ok?' &#10003;':' &#10007;')+'</div>'; }).join('') || '<div style="color:var(--muted);font-size:.82rem">сюда…</div>';
return '<div class="c7-bucket" data-b="'+b[1]+'" style="flex:1;min-width:130px;border:1.5px dashed var(--border);border-radius:11px;padding:10px;background:var(--pri-soft)"><div style="font-weight:700;margin-bottom:6px">'+b[0]+'</div>'+cells+'</div>';
}).join('');
mount.innerHTML = '<div style="margin-bottom:10px">'+chips+'</div><div style="display:flex;gap:10px;flex-wrap:wrap">'+cols+'</div>';
mount.querySelectorAll('.c7-chip').forEach(function(b){ b.addEventListener('click',function(){ mount.querySelectorAll('.c7-chip').forEach(function(x){x.style.outline='';}); sel=+b.dataset.i; b.style.outline='2px solid var(--pri)'; }); });
mount.querySelectorAll('.c7-bucket').forEach(function(col){ col.addEventListener('click',function(){ if(sel==null)return; placed[sel]=+col.dataset.b; pool=pool.filter(function(x){return x!==sel;}); sel=null; render(); }); });
}
render();
};
W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, {
p13: mount_p13, lo2: mount_lo2, p14: mount_p14, p15: mount_p15,
p16: mount_p16, p17: mount_p17, pr2: mount_pr2
});
W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {});
})(window);