feat(phys8 ch2): Phase 2 Wave 2 — §15 элементарный заряд + §16 строение атома

§15 Электрический заряд. Элементарный заряд:
- 3 теории: e = 1.6·10⁻¹⁹ Кл, формула q = Ne, закон сохранения
- IV-1: интерактивный калькулятор q ↔ N со slider в логарифм. шкале
  10⁶..10¹⁸ электронов, выводит q в Кл и нКл
- IV-2: 6 раундов «существует ли такой заряд?» (проверка кратности e)
- IV-3: DnD 8 ситуаций «сохраняется / меняется» (заземление, рентген...)
- IV-4: 5 расчётных задач с допусками и подсказками

§16 Строение атома. Ионы:
- 3 теории: планетарная модель, ионы, таблица атомов и ионов
- IV-1: главный визуал — интерактивная модель атома: slider'ы Z и
  число электронов, электроны распределяются по 3 оболочкам (2/8/18),
  ядро с Z протонов, заряд иона рассчитывается автоматически
- IV-2: 6 викторин по таблице ионов
- IV-3: DnD 9 частиц на 3 категории (+/-/нейтр)
- IV-4: 6 MCQ

Глобальная константа E_CHARGE = 1.6e-19 на верхнем уровне.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 23:18:46 +03:00
parent ed6fea460c
commit 053c2ebfdd
+443 -6
View File
@@ -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:[
["Атом","ядро (+) + электроны (&minus;)"],
["Ядро","протоны + нейтроны"],
["Нейтр. атом","$N_{электр} = Z$ (атомный номер)"],
["Катион (+)","потерял электрон(ы)"],
["Анион (&minus;)","принял электрон(ы)"],
["Электронные оболочки","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:"Потри расчёску о волосы — она начнёт притягивать клочки бумаги. Это <b>электризация</b>. При трении один предмет получает <b>положительный</b> заряд, другой — <b>отрицательный</b>. Одноимённые отталкиваются, разноимённые — притягиваются."},
{sec:'p13',html:"Металлы — отличные проводники: их электроны легко двигаются. Эбонит, стекло, пластик — диэлектрики: электроны связаны атомами. Поэтому ручка отвёртки из пластика — а сама отвёртка металлическая."},
{sec:'p14',html:"Поднеси заряженный шар к незаряженному металлическому шарику — он притянется. В нейтральном проводнике под действием внешнего заряда электроны перераспределяются, и ближний конец получает противоположный знак."},
{sec:'p15',html:"Параграф § 15 будет реализован в Phase 2 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p16',html:"Параграф § 16 будет реализован в Phase 2 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p15',html:"Заряд не бывает «дробным» — все заряды кратны элементарному $e = 1{,}6 \\cdot 10^{-19}$ Кл. Если у тела «лишние» $N$ электронов, его заряд $q = -Ne$. 1 Кл — это очень много: примерно $6 \\cdot 10^{18}$ электронов."},
{sec:'p16',html:"Атом — это плотное положительное ядро (протоны+нейтроны) и облако отрицательных электронов вокруг. В обычном атоме число электронов равно числу протонов — он нейтрален. Если потерять электрон → ион +, если получить → ион &minus;."},
{sec:'p17',html:"Параграф § 17 будет реализован в Phase 2 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p18',html:"Параграф § 18 будет реализован в Phase 2 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p19',html:"Параграф § 19 будет реализован в Phase 3 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
@@ -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',
'<p>Эксперимент Милликена с масляными каплями показал: <b>заряд тела всегда кратен</b> наименьшей порции — <b>элементарному заряду</b>:</p>'
+'<p style="text-align:center;margin:8px 0">$$e = 1{,}6 \\cdot 10^{-19} \\text{ Кл}$$</p>'
+'<p>Это заряд одного электрона (со знаком «&minus;») и одного протона (со знаком «+»). Меньше — не бывает (это <b>квантуется</b>).</p>'
);
h += makeCard('rule', 'Формула $q = N e$', '§ 15.2',
'<p>Если у тела «лишних» $N$ электронов, его заряд:</p>'
+'<p style="text-align:center;margin:8px 0">$$q = N \\cdot e$$</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>$q < 0$ — избыток электронов;</li>'
+'<li>$q > 0$ — нехватка электронов;</li>'
+'<li>$q = 0$ — нейтральное тело (число +зарядов = число &minus;зарядов).</li>'
+'</ul>'
+'<p>Сколько электронов в 1 Кл? $N = 1/e = 6{,}25 \\cdot 10^{18}$ — это огромное число.</p>'
);
h += makeCard('example', 'Закон сохранения заряда', '§ 15.3',
'<p>В замкнутой системе сумма всех зарядов <b>не меняется</b>:</p>'
+'<p style="text-align:center;margin:8px 0">$$\\sum q_i = \\text{const}$$</p>'
+'<p>Электризация трением — не «появление» заряда, а его <b>перенос</b>: было два нейтральных тела (всего 0), стало $+q$ и $-q$ (сумма всё ещё 0).</p>'
);
/* IV1 — калькулятор q ↔ N */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Сколько электронов?</div></div>'
+'<div class="wg-help">Преобразуй между числом электронов $N$ и зарядом $q$ в нанокулонах.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Число электронов $N$: <b id="p15-nv">10<sup>9</sup></b><input type="range" id="p15-n" min="6" max="18" step="0.5" value="9"></label>'
+'</div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$N$ = <b id="p15-nval">1 000 000 000</b></span>'
+'<span>$|q| = N e$ = <b id="p15-qval">1.6&times;10<sup>-10</sup></b> Кл = <b id="p15-qnc">0.16</b> нКл</span>'
+'<span style="font-size:.84rem;color:var(--muted)">Это очень малый заряд — почти неощутим.</span>'
+'</div>'
+'</div>';
/* IV2 — викторина квантования */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Может ли существовать такой заряд?</div></div>'
+'<div class="wg-help">Проверь, кратен ли указанный заряд $e = 1{,}6 \\cdot 10^{-19}$ Кл.</div>'
+'<div id="p15-quiz"></div>'
+'<div class="actions"><button class="btn" id="p15-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p15-quiz-r">1</b> / 6</span><span>Правильно: <b id="p15-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD сохранение заряда */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сохраняется ли заряд?</div></div>'
+'<div class="wg-help">Распредели ситуации: где общий заряд изменился, а где остался прежним.</div>'
+'<div id="p15-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Сохраняется</h5><div class="drop-items" data-cat="keep"></div></div>'
+'<div class="drop-box"><h5>Меняется (внеш. вмешат.)</h5><div class="drop-items" data-cat="ext"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p15-dnd-check">Проверить</button><button class="btn" id="p15-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p15-dnd-fb"></div>'
+'</div>';
/* IV4 — числовые задачи */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP. $e = 1{,}6 \\cdot 10^{-19}$ Кл.</div>'
+'<div id="p15-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p15-task-i">1</b> / 5</span><span>Правильно: <b id="p15-task-ok">0</b></span></div>'
+'</div>';
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<sup>'+exp.toFixed(1)+'</sup>';
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+'&times;10<sup>'+qExp+'</sup>';
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 =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5;font-size:1.05rem">'+q.q+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<button class="btn" data-pick="Y" style="padding:14px"><b>Существует</b></button>'
+'<button class="btn" data-pick="N" style="padding:14px"><b>Не существует</b></button>'
+'</div>'
+'<div class="feedback" id="p15-quiz-fb"></div>';
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='&#10003; Верно. '+q.why; addXp(3,'p15-quiz'); bumpProgress('p15', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+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='&#10003; Идеально! +15 XP. Заряд сохраняется в изолированной системе.'; addXp(15,'p15-dnd'); bumpProgress('p15', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+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 =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p15-task-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p15-task-go">Ответ</button>'
+'<button class="btn" id="p15-task-hint">Подсказка</button>'
+'<button class="btn" id="p15-task-next">Следующая</button></div>'
+'<div class="boss-hint-txt" id="p15-task-hint-txt">'+t.why+'</div>'
+'<div class="feedback" id="p15-task-fb"></div>';
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='&#10003; Верно! '+t.why; addXp(4,'p15-task'); bumpProgress('p15', 6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Правильный ответ: '+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='&#10003; +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',
'<p>Атом устроен как маленькая «солнечная система»:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Ядро</b> в центре — очень маленькое (10<sup>-15</sup> м), но в нём почти вся масса. Состоит из <b>протонов</b> ($+e$) и <b>нейтронов</b> (нейтральные).</li>'
+'<li><b>Электроны</b> ($-e$) движутся вокруг ядра по оболочкам.</li>'
+'<li>Атом в целом размером $\\sim 10^{-10}$ м — ядро в 100 000 раз меньше всего атома.</li>'
+'</ul>'
+'<p>В <b>нейтральном</b> атоме число электронов = число протонов = <b>атомный номер</b> $Z$.</p>'
);
h += makeCard('rule', 'Ионы', '§ 16.2',
'<p>Если атом <b>потерял</b> 1 или больше электронов — он становится <b>положительным ионом (катионом)</b>: $\\text{Na} \\to \\text{Na}^+$.</p>'
+'<p>Если атом <b>принял</b> лишний электрон — он становится <b>отрицательным ионом (анионом)</b>: $\\text{Cl} \\to \\text{Cl}^-$.</p>'
+'<p>Соли в растворах диссоциируют на ионы — поэтому растворы солей проводят ток.</p>'
);
h += makeCard('example', 'Атомы и ионы', '§ 16.3',
'<table style="width:100%;border-collapse:collapse;font-size:.92rem"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px">Элемент</th><th style="padding:6px;text-align:center">$Z$</th><th style="padding:6px;text-align:center">Электр.</th><th style="padding:6px;text-align:center">Заряд иона</th></tr></thead>'
+'<tbody><tr><td style="padding:6px;border-bottom:1px dashed var(--border)">водород H</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">1</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">1</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">H<sup>+</sup></td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">натрий Na</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">11</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">11</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">Na<sup>+</sup></td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">хлор Cl</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">17</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">17</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">Cl<sup>&minus;</sup></td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">кальций Ca</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">20</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">20</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">Ca<sup>2+</sup></td></tr>'
+'<tr><td style="padding:6px">кислород O</td><td style="padding:6px;text-align:center">8</td><td style="padding:6px;text-align:center">8</td><td style="padding:6px;text-align:center">O<sup>2&minus;</sup></td></tr></tbody></table>'
);
/* IV1 — конструктор атома */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Конструктор атома / иона</div></div>'
+'<div class="wg-help">Меняй число протонов и электронов — увидь, нейтрален ли атом и какой ион получится.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Протонов ($Z$): <b id="p16-zv">11</b><input type="range" id="p16-z" min="1" max="20" step="1" value="11"></label>'
+'<label>Электронов: <b id="p16-ev">11</b><input type="range" id="p16-e" min="0" max="20" step="1" value="11"></label>'
+'</div>'
+'<svg id="p16-sim" viewBox="0 0 460 240" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Заряд: <b id="p16-qbal">0</b> (нейтрален)</span>'
+'<span>Состояние: <b id="p16-state">нейтральный атом</b></span>'
+'</div>'
+'</div>';
/* IV2 — викторина по таблице */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Какой заряд у иона?</div></div>'
+'<div class="wg-help">Дан элемент и преобразование — назови заряд получившегося иона.</div>'
+'<div id="p16-quiz"></div>'
+'<div class="actions"><button class="btn" id="p16-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p16-quiz-r">1</b> / 6</span><span>Правильно: <b id="p16-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD сортировка частиц */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сортировка частиц</div></div>'
+'<div class="wg-help">Распредели частицы по знаку заряда.</div>'
+'<div id="p16-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Положительные</h5><div class="drop-items" data-cat="pos"></div></div>'
+'<div class="drop-box"><h5>Отрицательные</h5><div class="drop-items" data-cat="neg"></div></div>'
+'<div class="drop-box"><h5>Нейтральные</h5><div class="drop-items" data-cat="neu"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p16-dnd-check">Проверить</button><button class="btn" id="p16-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p16-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP.</div>'
+'<div id="p16-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p16-mcq-i">1</b> / 6</span><span>Правильно: <b id="p16-mcq-ok">0</b></span></div>'
+'</div>';
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 += '<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="none" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3 3"/>';
/* ядро */
s += '<circle cx="'+cx+'" cy="'+cy+'" r="22" fill="#fef3c7" stroke="#dc2626" stroke-width="2"/>';
s += '<text x="'+cx+'" y="'+(cy+5)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="800" fill="#dc2626">'+Z+'p</text>';
/* электроны на оболочках: 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 += '<circle cx="'+ex.toFixed(1)+'" cy="'+ey.toFixed(1)+'" r="5" fill="#2563eb" stroke="#0f172a" stroke-width="0.8"/>';
s += '<text x="'+ex.toFixed(1)+'" y="'+(ey+3).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" font-weight="800" fill="#fff">&minus;</text>';
}
placed += count;
}
/* пометка заряда */
if(delta !== 0){
const sign = delta > 0 ? '+' : '';
const col = delta > 0 ? '#dc2626' : '#2563eb';
s += '<text x="'+(cx+130)+'" y="'+(cy-90)+'" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="20" font-weight="900" fill="'+col+'">'+sign+delta+'</text>';
}
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<sup>+</sup>','Na<sup>&minus;</sup>','Na<sup>2+</sup>','Na'], ans:0, why:'Потерял 1 электрон → катион +1.'},
{q:'Cl принял 1 электрон. Какой ион?', opts:['Cl<sup>+</sup>','Cl<sup>&minus;</sup>','Cl<sup>2&minus;</sup>','Cl'], ans:1, why:'Принял 1 → анион &minus;1.'},
{q:'Ca отдал 2 электрона. Заряд иона?', opts:['+1','+2','&minus;1','0'], ans:1, why:'2 потерянных электрона → +2.'},
{q:'O принял 2 электрона. Заряд иона?', opts:['+2','&minus;2','&minus;1','0'], ans:1, why:'2 принятых электрона → &minus;2.'},
{q:'Сколько электронов у Na<sup>+</sup>? ($Z_{Na}$=11)', opts:['10','11','12','9'], ans:0, why:'Был 11, отдал 1, остался 10.'},
{q:'Сколько электронов у Cl<sup>&minus;</sup>? ($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 = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p16-quiz-fb"></div>';
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='&#10003; Верно. '+q.why; addXp(3,'p16-quiz'); bumpProgress('p16', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+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<sup>+</sup>'},
{id:'ca', cat:'pos', html:'ион Ca<sup>2+</sup>'},
{id:'e', cat:'neg', html:'электрон'},
{id:'cl', cat:'neg', html:'ион Cl<sup>&minus;</sup>'},
{id:'o', cat:'neg', html:'ион O<sup>2&minus;</sup>'},
{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='&#10003; Идеально! +15 XP. Протон + катион, электрон + анион, нейтрон/атомы — нейтральные.'; addXp(15,'p16-dnd'); bumpProgress('p16', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+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:['&minus;2','&minus;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 = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p16-mcq-fb"></div><div class="actions"><button class="btn" id="p16-mcq-next">Следующий</button></div>';
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='&#10003; Верно. '+q.why; addXp(2,'p16-mcq'); bumpProgress('p16', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+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='&#10003; +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);