diff --git a/frontend/textbooks/physics_8_ch2.html b/frontend/textbooks/physics_8_ch2.html
index 4137bf2..36f6eb7 100644
--- a/frontend/textbooks/physics_8_ch2.html
+++ b/frontend/textbooks/physics_8_ch2.html
@@ -327,8 +327,22 @@ const SIDEBARS = {
["Ближний конец","заряд противоположного знака"],
["Если разделить","на 2 заряженные части"]
]},
- p15:{title:"Шпаргалка § 15",rows:[["В разработке","Phase 2 Wave 2"]]},
- p16:{title:"Шпаргалка § 16",rows:[["В разработке","Phase 2 Wave 2"]]},
+ p15:{title:"Шпаргалка § 15",rows:[
+ ["Элементарный заряд","$e = 1{,}6 \\cdot 10^{-19}$ Кл"],
+ ["Формула","$q = N e$ ($N$ — число избыт. электронов)"],
+ ["Знак $-q$","избыток электронов"],
+ ["Знак $+q$","нехватка электронов"],
+ ["1 Кл","заряд $6{,}25 \\cdot 10^{18}$ электронов"],
+ ["Сохранение","$\\sum q = $ const"]
+ ]},
+ p16:{title:"Шпаргалка § 16",rows:[
+ ["Атом","ядро (+) + электроны (−)"],
+ ["Ядро","протоны + нейтроны"],
+ ["Нейтр. атом","$N_{электр} = Z$ (атомный номер)"],
+ ["Катион (+)","потерял электрон(ы)"],
+ ["Анион (−)","принял электрон(ы)"],
+ ["Электронные оболочки","K, L, M, …"]
+ ]},
p17:{title:"Шпаргалка § 17",rows:[["В разработке","Phase 2 Wave 3"]]},
p18:{title:"Шпаргалка § 18",rows:[["В разработке","Phase 2 Wave 3"]]},
p19:{title:"Шпаргалка § 19",rows:[["В разработке","Phase 3 Wave 1"]]},
@@ -351,8 +365,8 @@ const TIPS=[
{sec:'p12',html:"Потри расчёску о волосы — она начнёт притягивать клочки бумаги. Это электризация. При трении один предмет получает положительный заряд, другой — отрицательный. Одноимённые отталкиваются, разноимённые — притягиваются."},
{sec:'p13',html:"Металлы — отличные проводники: их электроны легко двигаются. Эбонит, стекло, пластик — диэлектрики: электроны связаны атомами. Поэтому ручка отвёртки из пластика — а сама отвёртка металлическая."},
{sec:'p14',html:"Поднеси заряженный шар к незаряженному металлическому шарику — он притянется. В нейтральном проводнике под действием внешнего заряда электроны перераспределяются, и ближний конец получает противоположный знак."},
- {sec:'p15',html:"Параграф § 15 будет реализован в Phase 2 Wave 2. Используем хелперы из phys.js и optics.js."},
- {sec:'p16',html:"Параграф § 16 будет реализован в Phase 2 Wave 2. Используем хелперы из phys.js и optics.js."},
+ {sec:'p15',html:"Заряд не бывает «дробным» — все заряды кратны элементарному $e = 1{,}6 \\cdot 10^{-19}$ Кл. Если у тела «лишние» $N$ электронов, его заряд $q = -Ne$. 1 Кл — это очень много: примерно $6 \\cdot 10^{18}$ электронов."},
+ {sec:'p16',html:"Атом — это плотное положительное ядро (протоны+нейтроны) и облако отрицательных электронов вокруг. В обычном атоме число электронов равно числу протонов — он нейтрален. Если потерять электрон → ион +, если получить → ион −."},
{sec:'p17',html:"Параграф § 17 будет реализован в Phase 2 Wave 3. Используем хелперы из phys.js и optics.js."},
{sec:'p18',html:"Параграф § 18 будет реализован в Phase 2 Wave 3. Используем хелперы из phys.js и optics.js."},
{sec:'p19',html:"Параграф § 19 будет реализован в Phase 3 Wave 1. Используем хелперы из phys.js и optics.js."},
@@ -375,8 +389,8 @@ const BUILDERS = {
p12: ()=>{ build_p12(); },
p13: ()=>{ build_p13(); },
p14: ()=>{ build_p14(); },
- p15: ()=>{ const box=document.getElementById('p15-body'); box.innerHTML = buildStub('p15', 'Электрический заряд. Элементарный заряд', 'Phase 2 Wave 2') + secNavFor('p15') + readButton('p15'); renderMath(box); wireReadBtn('p15'); },
- p16: ()=>{ const box=document.getElementById('p16-body'); box.innerHTML = buildStub('p16', 'Строение атома. Ионы', 'Phase 2 Wave 2') + secNavFor('p16') + readButton('p16'); renderMath(box); wireReadBtn('p16'); },
+ p15: ()=>{ build_p15(); },
+ p16: ()=>{ build_p16(); },
p17: ()=>{ const box=document.getElementById('p17-body'); box.innerHTML = buildStub('p17', 'Электрическое поле. Электрическое напряжение', 'Phase 2 Wave 3') + secNavFor('p17') + readButton('p17'); renderMath(box); wireReadBtn('p17'); },
p18: ()=>{ const box=document.getElementById('p18-body'); box.innerHTML = buildStub('p18', 'Единица электрического напряжения. Расчёт работы в электрическом поле', 'Phase 2 Wave 3') + secNavFor('p18') + readButton('p18'); renderMath(box); wireReadBtn('p18'); },
p19: ()=>{ const box=document.getElementById('p19-body'); box.innerHTML = buildStub('p19', 'Электрический ток. Источники тока', 'Phase 3 Wave 1') + secNavFor('p19') + readButton('p19'); renderMath(box); wireReadBtn('p19'); },
@@ -1418,6 +1432,429 @@ function _initP14_mcq(){
render();
}
+/* ======================================================================
+ PHASE 2 · WAVE 2 — §15, §16
+ ====================================================================== */
+
+const E_CHARGE = 1.6e-19;
+
+/* ======== §15 — Электрический заряд. Элементарный заряд ======== */
+function build_p15(){
+ const box = document.getElementById('p15-body');
+ let h = '';
+
+ h += makeCard('theory', 'Элементарный заряд', '§ 15.1',
+ '
Эксперимент Милликена с масляными каплями показал: заряд тела всегда кратен наименьшей порции — элементарному заряду:
'
+ +'$$e = 1{,}6 \\cdot 10^{-19} \\text{ Кл}$$
'
+ +'Это заряд одного электрона (со знаком «−») и одного протона (со знаком «+»). Меньше — не бывает (это квантуется).
'
+ );
+ h += makeCard('rule', 'Формула $q = N e$', '§ 15.2',
+ 'Если у тела «лишних» $N$ электронов, его заряд:
'
+ +'$$q = N \\cdot e$$
'
+ +''
+ +'- $q < 0$ — избыток электронов;
'
+ +'- $q > 0$ — нехватка электронов;
'
+ +'- $q = 0$ — нейтральное тело (число +зарядов = число −зарядов).
'
+ +'
'
+ +'Сколько электронов в 1 Кл? $N = 1/e = 6{,}25 \\cdot 10^{18}$ — это огромное число.
'
+ );
+ h += makeCard('example', 'Закон сохранения заряда', '§ 15.3',
+ 'В замкнутой системе сумма всех зарядов не меняется:
'
+ +'$$\\sum q_i = \\text{const}$$
'
+ +'Электризация трением — не «появление» заряда, а его перенос: было два нейтральных тела (всего 0), стало $+q$ и $-q$ (сумма всё ещё 0).
'
+ );
+
+ /* IV1 — калькулятор q ↔ N */
+ h += ''
+ +''
+ +'
Преобразуй между числом электронов $N$ и зарядом $q$ в нанокулонах.
'
+ +'
'
+ +''
+ +'
'
+ +'
'
+ +'$N$ = 1 000 000 000'
+ +'$|q| = N e$ = 1.6×10-10 Кл = 0.16 нКл'
+ +'Это очень малый заряд — почти неощутим.'
+ +'
'
+ +'
';
+
+ /* IV2 — викторина квантования */
+ h += ''
+ +''
+ +'
Проверь, кратен ли указанный заряд $e = 1{,}6 \\cdot 10^{-19}$ Кл.
'
+ +'
'
+ +'
'
+ +'
Раунд: 1 / 6Правильно: 0
'
+ +'
';
+
+ /* IV3 — DnD сохранение заряда */
+ h += ''
+ +''
+ +'
Распредели ситуации: где общий заряд изменился, а где остался прежним.
'
+ +'
'
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+
+ /* IV4 — числовые задачи */
+ h += ''
+ +''
+ +'
4+ правильных — +15 XP. $e = 1{,}6 \\cdot 10^{-19}$ Кл.
'
+ +'
'
+ +'
Задача: 1 / 5Правильно: 0
'
+ +'
';
+
+ box.innerHTML = h + secNavFor('p15') + readButton('p15');
+ renderMath(box);
+ wireReadBtn('p15');
+
+ _initP15_calc();
+ _initP15_quiz();
+ _initP15_dnd();
+ _initP15_tasks();
+}
+
+function _initP15_calc(){
+ function update(){
+ const exp = +document.getElementById('p15-n').value;
+ const N = Math.pow(10, exp);
+ document.getElementById('p15-nv').innerHTML = '10'+exp.toFixed(1)+'';
+ document.getElementById('p15-nval').textContent = N.toExponential(2).replace('+','');
+ const q = N * E_CHARGE;
+ const qExp = Math.floor(Math.log10(q));
+ const qMant = (q/Math.pow(10,qExp)).toFixed(2);
+ document.getElementById('p15-qval').innerHTML = qMant+'×10'+qExp+'';
+ document.getElementById('p15-qnc').textContent = (q*1e9).toExponential(2).replace('+','');
+ }
+ document.getElementById('p15-n').addEventListener('input', update);
+ update();
+}
+
+function _initP15_quiz(){
+ const QS = [
+ {q:'$q = 3{,}2 \\cdot 10^{-19}$ Кл', ans:'Y', why:'$N = q/e = 2$ — кратно, существует.'},
+ {q:'$q = 8 \\cdot 10^{-19}$ Кл', ans:'Y', why:'$N = 5$ — целое, существует.'},
+ {q:'$q = 2{,}4 \\cdot 10^{-19}$ Кл', ans:'N', why:'$N = 1{,}5$ — не целое, не существует.'},
+ {q:'$q = 1 \\cdot 10^{-19}$ Кл', ans:'N', why:'$N = 0{,}625$ — не целое.'},
+ {q:'$q = 1{,}6 \\cdot 10^{-18}$ Кл', ans:'Y', why:'$N = 10$ — целое, существует.'},
+ {q:'$q = 5 \\cdot 10^{-19}$ Кл', ans:'N', why:'$N = 3{,}125$ — не целое.'}
+ ];
+ let i = 0, ok = 0;
+ function render(){
+ const q = QS[i]; const wrap = document.getElementById('p15-quiz'); if(!wrap) return;
+ wrap.innerHTML =
+ ''+q.q+'
'
+ +''
+ +''
+ +''
+ +'
'
+ +'';
+ document.getElementById('p15-quiz-r').textContent = (i+1);
+ document.getElementById('p15-quiz-ok').textContent = ok;
+ wrap.querySelectorAll('[data-pick]').forEach(btn=>{
+ btn.addEventListener('click', ()=>{
+ if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
+ const fb = document.getElementById('p15-quiz-fb');
+ if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(3,'p15-quiz'); bumpProgress('p15', 4); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; }
+ document.getElementById('p15-quiz-ok').textContent = ok;
+ renderMath(wrap);
+ });
+ });
+ renderMath(wrap);
+ }
+ document.getElementById('p15-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
+ render();
+}
+
+function _initP15_dnd(){
+ const items = [
+ {id:'a', cat:'keep', html:'трение двух тел в изолированной системе'},
+ {id:'b', cat:'keep', html:'переход электронов внутри проводника'},
+ {id:'c', cat:'keep', html:'индукция в шарике'},
+ {id:'d', cat:'keep', html:'два шара соприкоснулись и разъединились'},
+ {id:'e', cat:'ext', html:'тело заземлили (заряд ушёл в землю)'},
+ {id:'f', cat:'ext', html:'тело потёрли о внешний предмет, а потом убрали'},
+ {id:'g', cat:'ext', html:'к телу поднесли «насос электронов»'},
+ {id:'h', cat:'ext', html:'тело облучили рентгеном (выбил электроны)'}
+ ];
+ const dnd = setupSorter({ poolId:'p15-dnd-pool', scopeSelector:'#sec-p15', cats:['keep','ext'], items, columnLayout:false });
+ document.getElementById('p15-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p15-dnd-fb');
+ let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
+ if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='✓ Идеально! +15 XP. Заряд сохраняется в изолированной системе.'; addXp(15,'p15-dnd'); bumpProgress('p15', 20); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+'. Заземление и внешнее излучение — это внешнее вмешательство.'; }
+ });
+ document.getElementById('p15-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p15-dnd-fb'); fb.style.display='none'; });
+}
+
+function _initP15_tasks(){
+ const TASKS = [
+ {q:'Сколько электронов нужно собрать, чтобы получить заряд $q = -1$ нКл? ($e = 1{,}6 \\cdot 10^{-19}$ Кл) — Ответ в формате $N \\cdot 10^9$, введи $N$ с одним знаком после запятой.', ans:6.25, tol:0.1, why:'$N = q/e = 10^{-9}/(1{,}6\\cdot10^{-19}) = 6{,}25 \\cdot 10^9$. Ответ: $6{,}25$.'},
+ {q:'У тела «лишних» $5 \\cdot 10^{10}$ электронов. Каков его заряд (в нКл, по модулю)?', ans:8, tol:0.2, why:'$q = Ne = 5\\cdot10^{10} \\cdot 1{,}6\\cdot10^{-19} = 8\\cdot10^{-9}$ Кл = $8$ нКл.'},
+ {q:'У одного тела $q_1 = +3$ нКл, у другого $q_2 = -5$ нКл. После их соприкосновения и разделения сумма $q_1 + q_2$ равна (в нКл)?', ans:-2, tol:0.05, why:'Заряд сохраняется: $3 + (-5) = -2$ нКл (так и осталось после контакта).'},
+ {q:'Какой заряд (в Кл, по модулю) получится при $N = 2 \\cdot 10^{19}$? Ответ в формате $a \\cdot 10^0$, введи $a$ (одно число).', ans:3.2, tol:0.05, why:'$q = 2\\cdot10^{19} \\cdot 1{,}6\\cdot10^{-19} = 3{,}2$ Кл — это очень много!'},
+ {q:'Заряд $q = 4{,}8 \\cdot 10^{-19}$ Кл существует? Если да — сколько в нём электронов? Введи $N$.', ans:3, tol:0.1, why:'$N = q/e = 4{,}8/1{,}6 = 3$. Заряд существует, в нём 3 элементарных.'}
+ ];
+ let i = 0, ok = 0, done = 0, awarded = false;
+ function render(){
+ const t = TASKS[i]; const wrap = document.getElementById('p15-task'); if(!wrap) return;
+ wrap.innerHTML =
+ 'Задача '+(i+1)+'. '+t.q+'
'
+ +''
+ +''
+ +''
+ +'
'
+ +''+t.why+'
'
+ +'';
+ document.getElementById('p15-task-i').textContent = (i+1);
+ document.getElementById('p15-task-ok').textContent = ok;
+ document.getElementById('p15-task-go').addEventListener('click', ()=>{
+ const v = parseFloat((document.getElementById('p15-task-inp').value || '').replace(',','.'));
+ const fb = document.getElementById('p15-task-fb');
+ if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Введи число.'; return; }
+ done++;
+ if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно! '+t.why; addXp(4,'p15-task'); bumpProgress('p15', 6); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. Правильный ответ: '+t.ans+'. '+t.why; }
+ document.getElementById('p15-task-ok').textContent = ok;
+ renderMath(wrap);
+ if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p15-task-fb'); wf.className='feedback ok'; wf.innerHTML='✓ +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p15-task-bonus'); bumpProgress('p15', 15); }, 600); }
+ });
+ document.getElementById('p15-task-hint').addEventListener('click', ()=>{ document.getElementById('p15-task-hint-txt').classList.toggle('show'); });
+ document.getElementById('p15-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); });
+ renderMath(wrap);
+ }
+ render();
+}
+
+/* ======== §16 — Строение атома. Ионы ======== */
+function build_p16(){
+ const box = document.getElementById('p16-body');
+ let h = '';
+
+ h += makeCard('theory', 'Планетарная модель атома', '§ 16.1',
+ 'Атом устроен как маленькая «солнечная система»:
'
+ +''
+ +'- Ядро в центре — очень маленькое (10-15 м), но в нём почти вся масса. Состоит из протонов ($+e$) и нейтронов (нейтральные).
'
+ +'- Электроны ($-e$) движутся вокруг ядра по оболочкам.
'
+ +'- Атом в целом размером $\\sim 10^{-10}$ м — ядро в 100 000 раз меньше всего атома.
'
+ +'
'
+ +'В нейтральном атоме число электронов = число протонов = атомный номер $Z$.
'
+ );
+ h += makeCard('rule', 'Ионы', '§ 16.2',
+ 'Если атом потерял 1 или больше электронов — он становится положительным ионом (катионом): $\\text{Na} \\to \\text{Na}^+$.
'
+ +'Если атом принял лишний электрон — он становится отрицательным ионом (анионом): $\\text{Cl} \\to \\text{Cl}^-$.
'
+ +'Соли в растворах диссоциируют на ионы — поэтому растворы солей проводят ток.
'
+ );
+ h += makeCard('example', 'Атомы и ионы', '§ 16.3',
+ '| Элемент | $Z$ | Электр. | Заряд иона |
'
+ +'| водород H | 1 | 1 | H+ |
'
+ +'| натрий Na | 11 | 11 | Na+ |
'
+ +'| хлор Cl | 17 | 17 | Cl− |
'
+ +'| кальций Ca | 20 | 20 | Ca2+ |
'
+ +'| кислород O | 8 | 8 | O2− |
'
+ );
+
+ /* IV1 — конструктор атома */
+ h += ''
+ +''
+ +'
Меняй число протонов и электронов — увидь, нейтрален ли атом и какой ион получится.
'
+ +'
'
+ +''
+ +''
+ +'
'
+ +'
'
+ +'
'
+ +'Заряд: 0 (нейтрален)'
+ +'Состояние: нейтральный атом'
+ +'
'
+ +'
';
+
+ /* IV2 — викторина по таблице */
+ h += ''
+ +''
+ +'
Дан элемент и преобразование — назови заряд получившегося иона.
'
+ +'
'
+ +'
'
+ +'
Раунд: 1 / 6Правильно: 0
'
+ +'
';
+
+ /* IV3 — DnD сортировка частиц */
+ h += ''
+ +''
+ +'
Распредели частицы по знаку заряда.
'
+ +'
'
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+
+ /* IV4 — MCQ */
+ h += ''
+ +''
+ +'
4+ правильных — +15 XP.
'
+ +'
'
+ +'
Вопрос: 1 / 6Правильно: 0
'
+ +'
';
+
+ box.innerHTML = h + secNavFor('p16') + readButton('p16');
+ renderMath(box);
+ wireReadBtn('p16');
+
+ _initP16_atom();
+ _initP16_quiz();
+ _initP16_dnd();
+ _initP16_mcq();
+}
+
+function _initP16_atom(){
+ const svg = document.getElementById('p16-sim'); if(!svg) return;
+ function draw(){
+ const Z = +document.getElementById('p16-z').value;
+ const N = +document.getElementById('p16-e').value;
+ document.getElementById('p16-zv').textContent = Z;
+ document.getElementById('p16-ev').textContent = N;
+ const delta = Z - N;
+ document.getElementById('p16-qbal').textContent = (delta > 0 ? '+'+delta : (delta < 0 ? delta : '0'))+' e';
+ let state = '';
+ if(delta === 0) state = 'нейтральный атом';
+ else if(delta > 0) state = 'катион (+'+delta+')';
+ else state = 'анион ('+delta+')';
+ document.getElementById('p16-state').textContent = state;
+ /* draw */
+ const cx = 230, cy = 120;
+ let s = '';
+ /* электронные оболочки */
+ const shellRs = [50, 80, 110];
+ for(const r of shellRs) s += '';
+ /* ядро */
+ s += '';
+ s += ''+Z+'p';
+ /* электроны на оболочках: 2 на K (50), 8 на L (80), остальное на M (110) */
+ let placed = 0;
+ const shellMax = [2, 8, 18];
+ for(let sh = 0; sh < 3 && placed < N; sh++){
+ const r = shellRs[sh];
+ const count = Math.min(shellMax[sh], N - placed);
+ for(let i = 0; i < count; i++){
+ const ang = 2 * Math.PI * i / count + sh * 0.3;
+ const ex = cx + r*Math.cos(ang);
+ const ey = cy + r*Math.sin(ang);
+ s += '';
+ s += '−';
+ }
+ placed += count;
+ }
+ /* пометка заряда */
+ if(delta !== 0){
+ const sign = delta > 0 ? '+' : '';
+ const col = delta > 0 ? '#dc2626' : '#2563eb';
+ s += ''+sign+delta+'';
+ }
+ svg.innerHTML = s;
+ }
+ document.getElementById('p16-z').addEventListener('input', draw);
+ document.getElementById('p16-e').addEventListener('input', draw);
+ draw();
+}
+
+function _initP16_quiz(){
+ const QS = [
+ {q:'Na отдал 1 электрон. Какой ион?', opts:['Na+','Na−','Na2+','Na'], ans:0, why:'Потерял 1 электрон → катион +1.'},
+ {q:'Cl принял 1 электрон. Какой ион?', opts:['Cl+','Cl−','Cl2−','Cl'], ans:1, why:'Принял 1 → анион −1.'},
+ {q:'Ca отдал 2 электрона. Заряд иона?', opts:['+1','+2','−1','0'], ans:1, why:'2 потерянных электрона → +2.'},
+ {q:'O принял 2 электрона. Заряд иона?', opts:['+2','−2','−1','0'], ans:1, why:'2 принятых электрона → −2.'},
+ {q:'Сколько электронов у Na+? ($Z_{Na}$=11)', opts:['10','11','12','9'], ans:0, why:'Был 11, отдал 1, остался 10.'},
+ {q:'Сколько электронов у Cl−? ($Z_{Cl}$=17)', opts:['18','17','16','19'], ans:0, why:'Принял 1, стало 18.'}
+ ];
+ let i = 0, ok = 0;
+ function render(){
+ const q = QS[i]; const wrap = document.getElementById('p16-quiz'); if(!wrap) return;
+ let h = 'Вопрос '+(i+1)+'. '+q.q+'
';
+ q.opts.forEach((opt,k)=>{ h += ''; });
+ h += '
';
+ wrap.innerHTML = h;
+ document.getElementById('p16-quiz-r').textContent = (i+1);
+ document.getElementById('p16-quiz-ok').textContent = ok;
+ wrap.querySelectorAll('[data-k]').forEach(btn=>{
+ btn.addEventListener('click', ()=>{
+ if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
+ const k = +btn.dataset.k; const fb = document.getElementById('p16-quiz-fb');
+ if(k===q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(3,'p16-quiz'); bumpProgress('p16', 4); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; }
+ document.getElementById('p16-quiz-ok').textContent = ok;
+ });
+ });
+ }
+ document.getElementById('p16-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
+ render();
+}
+
+function _initP16_dnd(){
+ const items = [
+ {id:'p', cat:'pos', html:'протон'},
+ {id:'na', cat:'pos', html:'ион Na+'},
+ {id:'ca', cat:'pos', html:'ион Ca2+'},
+ {id:'e', cat:'neg', html:'электрон'},
+ {id:'cl', cat:'neg', html:'ион Cl−'},
+ {id:'o', cat:'neg', html:'ион O2−'},
+ {id:'n', cat:'neu', html:'нейтрон'},
+ {id:'aH', cat:'neu', html:'атом водорода H'},
+ {id:'aHe',cat:'neu', html:'атом гелия He'}
+ ];
+ const dnd = setupSorter({ poolId:'p16-dnd-pool', scopeSelector:'#sec-p16', cats:['pos','neg','neu'], items, columnLayout:false });
+ document.getElementById('p16-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p16-dnd-fb');
+ let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
+ if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='✓ Идеально! +15 XP. Протон + катион, электрон + анион, нейтрон/атомы — нейтральные.'; addXp(15,'p16-dnd'); bumpProgress('p16', 20); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+'. Подсказка: нейтрон и сами атомы — нейтральные, ионы — заряжены.'; }
+ });
+ document.getElementById('p16-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p16-dnd-fb'); fb.style.display='none'; });
+}
+
+function _initP16_mcq(){
+ const QS = [
+ {q:'Что находится в ядре атома?', opts:['электроны','протоны и нейтроны','только протоны','только нейтроны'], ans:1, why:'Ядро = протоны + нейтроны.'},
+ {q:'Какой заряд у протона?', opts:['$-e$','$+e$','0','$+2e$'], ans:1, why:'Протон несёт элементарный + заряд.'},
+ {q:'Какой заряд у нейтрона?', opts:['$-e$','$+e$','0','$+2e$'], ans:2, why:'Нейтрон нейтрален.'},
+ {q:'В нейтральном атоме число электронов равно…', opts:['числу нейтронов','числу протонов','массовому числу','$Z+N$'], ans:1, why:'$Z$ электронов компенсируют $Z$ протонов.'},
+ {q:'Какой ион получится, если атом отдаст 2 электрона?', opts:['−2','−1','+1','+2'], ans:3, why:'2 потерянных электрона → заряд +2.'},
+ {q:'Что больше: атом или ядро?', opts:['ядро','атом','одинаково','зависит от элемента'], ans:1, why:'Атом примерно в 100 000 раз больше ядра.'}
+ ];
+ let i = 0, ok = 0, done = 0, awarded = false;
+ function render(){
+ const q = QS[i]; const wrap = document.getElementById('p16-mcq'); if(!wrap) return;
+ let h = 'Вопрос '+(i+1)+'. '+q.q+'
';
+ q.opts.forEach((opt,k)=>{ h += ''; });
+ h += '
';
+ wrap.innerHTML = h;
+ document.getElementById('p16-mcq-i').textContent = (i+1);
+ document.getElementById('p16-mcq-ok').textContent = ok;
+ wrap.querySelectorAll('[data-k]').forEach(btn=>{
+ btn.addEventListener('click', ()=>{
+ if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
+ const k = +btn.dataset.k; const fb = document.getElementById('p16-mcq-fb');
+ if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(2,'p16-mcq'); bumpProgress('p16', 3); }
+ else { done++; fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; }
+ document.getElementById('p16-mcq-ok').textContent = ok;
+ renderMath(wrap);
+ if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p16-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='✓ +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p16-mcq-bonus'); bumpProgress('p16', 15); }, 600); }
+ });
+ });
+ const nb = document.getElementById('p16-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
+ renderMath(wrap);
+ }
+ render();
+}
+
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);