diff --git a/frontend/textbooks/physics_10_ch5.html b/frontend/textbooks/physics_10_ch5.html
index fae6c8d..6b9ce94 100644
--- a/frontend/textbooks/physics_10_ch5.html
+++ b/frontend/textbooks/physics_10_ch5.html
@@ -2122,34 +2122,752 @@ function build_p30(){
function build_p31(){
const box = document.getElementById('p31-body');
let html = '';
- html += makeCard('theory', "Магнитный поток. Электромагнитная индукция", "§31", `
-
Магнитный поток. Электромагнитная индукция — этот параграф в разработке (Phase 1+).
-
Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.
-
- Phase 0: создан скелет учебника. Phase 5+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
-
+
+ /* THEORY 1 — Магнитный поток */
+ html += makeCard('theory', "Магнитный поток", "§31", `
+
Магнитный поток $\\Phi$ через плоскую площадку площадью $S$ в однородном магнитном поле $\\vec{B}$:
+
$$\\Phi = B\\,S\\,\\cos\\alpha$$
+
где $\\alpha$ — угол между $\\vec{B}$ и нормалью $\\vec{n}$ к площадке.
`;
+
html += secNav('p30', 'p32');
html += readButton('p31');
+
box.innerHTML = html;
renderMath(box);
+
+ /* IV1 — Магнитный поток через рамку */
+ (function(){
+ const svg = document.getElementById('p31-iv1-svg');
+ const out = document.getElementById('p31-iv1-out');
+ const bS = document.getElementById('p31-iv1-B');
+ const sS = document.getElementById('p31-iv1-S');
+ const aS = document.getElementById('p31-iv1-a');
+ const bL = document.getElementById('p31-iv1-BL');
+ const sL = document.getElementById('p31-iv1-SL');
+ const aL = document.getElementById('p31-iv1-aL');
+ const seen = new Set();
+ let _done = false;
+
+ function draw(){
+ const W = 480, H = 280;
+ const B = +bS.value, S = +sS.value, aDeg = +aS.value;
+ bL.textContent = B.toFixed(2);
+ sL.textContent = S.toFixed(2);
+ aL.textContent = aDeg.toFixed(0);
+ const alpha = aDeg * Math.PI / 180;
+ const Phi = B * S * Math.cos(alpha);
+
+ let g = '';
+ g += '';
+ g += 'B вправо →. Угол между B и нормалью n = '+aDeg+'°';
+
+ // Силовые линии поля B (горизонтальные стрелки слева → направо)
+ for(let i=0;i<5;i++){
+ const yL = 60 + i*40;
+ g += PHYS.drawArrow(40, yL, 440, yL, '#7c3aed', 1.3, 8);
+ }
+ // подпись B
+ g += 'B';
+
+ // Рамка (вид сверху, поворот вокруг вертикальной оси на угол alpha)
+ // Ширина проекции рамки = w0 * |sin(alpha)| — это «как мы видим её сбоку». Но удобнее
+ // показывать рамку с нормалью под углом alpha к B. Изобразим рамку как ромб (3D-проекция).
+ const cx = 240, cy = 150;
+ const halfW = 60; // полуширина рамки в плоскости поля
+ const halfH = 50; // высота рамки (не меняется)
+ // При alpha=0 рамка видна как горизонтальная линия (поток max); при alpha=90 рамка перпенд. → видна как прямоугольник
+ const projW = Math.max(2, halfW * Math.abs(Math.sin(alpha)));
+ // верхний и нижний край рамки — горизонтальные линии длиной 2*projW
+ // боковые стороны — наклонные (для эффекта 3D)
+ const xL = cx - projW, xR = cx + projW;
+ // Заполнение рамки
+ g += '';
+ // Нормаль n
+ const nLen = 60;
+ // Направление нормали — повёрнуто на alpha от B (B вдоль +x). Тогда n = (cos(alpha), sin(alpha)).
+ const nx = cx + nLen * Math.cos(alpha);
+ const ny = cy - nLen * Math.sin(alpha);
+ g += PHYS.drawArrow(cx, cy, nx, ny, '#dc2626', 2.4, 11);
+ g += 'n';
+ // Дуга угла между n и B (B = +x)
+ const arcR = 28;
+ const a1 = 0, a2 = -alpha; // угол в SVG-координатах (y вниз)
+ const aStart = Math.min(a1, a2), aEnd = Math.max(a1, a2);
+ g += '';
+ g += 'α';
+
+ // Подпись параметров рамки
+ g += 'B = '+B.toFixed(2)+' Тл, S = '+S.toFixed(2)+' м², α = '+aDeg+'°';
+ g += 'Φ = '+(Phi*1000).toFixed(2)+' мВб';
+
+ svg.innerHTML = g;
+
+ let note = '';
+ if(aDeg === 0) note = 'α = 0°: $\\vec{B} \\parallel \\vec{n}$ — поток максимален: $\\Phi_{max} = BS = '+(B*S*1000).toFixed(2)+'$ мВб.';
+ else if(aDeg === 90) note = 'α = 90°: $\\vec{B} \\perp \\vec{n}$ — линии поля скользят вдоль рамки. $\\Phi = 0$.';
+ else if(aDeg === 180) note = 'α = 180°: $\\vec{B}$ и $\\vec{n}$ направлены противоположно. $\\Phi = -BS = '+(-B*S*1000).toFixed(2)+'$ мВб.';
+ else note = 'α = '+aDeg+'°: $\\Phi = BS\\cos\\alpha = '+B.toFixed(2)+'\\cdot '+S.toFixed(2)+'\\cdot \\cos '+aDeg+'° = '+(Phi*1000).toFixed(2)+'$ мВб.';
+ out.innerHTML = note;
+ renderMath(out);
+
+ const key = (B>0.5?'B+':'B-') + ':' + (S>0.3?'S+':'S-') + ':' + Math.round(aDeg/30);
+ seen.add(key);
+ if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p31-iv1'); bumpProgress('p31', 15); }
+ }
+ bS.addEventListener('input', draw);
+ sS.addEventListener('input', draw);
+ aS.addEventListener('input', draw);
+ draw();
+ })();
+
+ /* IV2 — Опыт Фарадея */
+ (function(){
+ const svg = document.getElementById('p31-iv2-svg');
+ const out = document.getElementById('p31-iv2-out');
+ const xS = document.getElementById('p31-iv2-x');
+ const xL = document.getElementById('p31-iv2-xL');
+ let lastX = +xS.value, lastT = performance.now();
+ let movesPos = 0, movesNeg = 0, _done = false;
+
+ function draw(){
+ const W = 480, H = 280;
+ const x = +xS.value;
+ xL.textContent = x.toFixed(2);
+ const now = performance.now();
+ const dt = Math.max(0.01, (now - lastT) / 1000);
+ const dx = x - lastX;
+ const vel = dx / dt; // м/с (условно)
+ lastX = x; lastT = now;
+
+ // Катушка справа: центр около (340, 140), длина 130
+ const coilCx = 340, coilCy = 140, coilL = 130, coilR = 40;
+ // Магнит — прямоугольник; позиция: магнит-середина по x
+ // Сопоставим x ∈ [-0.5 .. 0.3] м со SVG-координатой от 70 (далеко слева) до 320 (входит в катушку)
+ const magW = 90, magH = 40;
+ const magCx = 70 + (x + 0.5) * (320 - 70) / 0.8;
+ const magCy = coilCy;
+
+ let g = '';
+ g += '';
+ g += 'Опыт Фарадея: магнит и катушка с амперметром';
+
+ // Катушка — серия дуг (имитация витков)
+ const turns = 10;
+ for(let i=0;i';
+ }
+ // Боковые «проводки» катушки к амперметру
+ g += PHYS.wire(coilCx - coilL/2 - 4, coilCy + coilR, coilCx - coilL/2 - 4, coilCy + coilR + 30);
+ g += PHYS.wire(coilCx + coilL/2 + 4, coilCy + coilR, coilCx + coilL/2 + 4, coilCy + coilR + 30);
+ g += PHYS.wire(coilCx - coilL/2 - 4, coilCy + coilR + 30, 405, coilCy + coilR + 30);
+ g += PHYS.wire(coilCx + coilL/2 + 4, coilCy + coilR + 30, 435, coilCy + coilR + 30);
+ // Амперметр
+ g += PHYS.ammeterSymbol(420, coilCy + coilR + 30, 14);
+
+ // Магнит: N (красный) | S (синий)
+ g += '';
+ g += '';
+ g += 'N';
+ g += 'S';
+
+ // Поле магнита (стрелка из N) — направление движения магнита
+ if(Math.abs(vel) > 0.02){
+ const dir = vel > 0 ? 1 : -1;
+ const arrFromX = magCx + dir * (magW/2 + 6);
+ const arrToX = arrFromX + dir * 40;
+ g += PHYS.drawArrow(arrFromX, magCy - 30, arrToX, magCy - 30, '#0f172a', 2, 9);
+ g += 'движение';
+ }
+
+ // Стрелка амперметра + ток в катушке
+ let curSign = 0; // -1, 0, +1
+ let curMag = 0;
+ if(Math.abs(vel) > 0.02){
+ // приближение N-полюса справа → поток через катушку растёт. Индукционный ток создаёт поле S к магниту,
+ // то есть ток виден против часовой стрелки (со стороны магнита). Возьмём знак произвольно.
+ curSign = vel > 0 ? +1 : -1;
+ curMag = Math.min(1, Math.abs(vel) * 8);
+ }
+ // Стрелка на амперметре
+ const ndlAng = curSign * curMag * 0.7; // рад от вертикали
+ const ax = 420, ay = coilCy + coilR + 30;
+ const ndlEx = ax + 11 * Math.sin(ndlAng);
+ const ndlEy = ay - 11 * Math.cos(ndlAng);
+ g += '';
+
+ // Стрелочки тока в первом витке катушки
+ if(curMag > 0){
+ const dirColor = curSign > 0 ? '#dc2626' : '#0891b2';
+ const ccx = coilCx, ccy = coilCy;
+ const r = coilR + 4;
+ // Верх витка — стрелка вправо/влево
+ const dxArr = curSign > 0 ? +14 : -14;
+ g += PHYS.drawArrow(ccx - dxArr/2, ccy - r, ccx + dxArr/2, ccy - r, dirColor, 2, 8);
+ g += PHYS.drawArrow(ccx + dxArr/2, ccy + r, ccx - dxArr/2, ccy + r, dirColor, 2, 8);
+ g += 'Индукционный ток '+(curSign>0?'→':'←')+'';
+ } else {
+ g += 'Тока нет — магнит покоится';
+ }
+
+ svg.innerHTML = g;
+
+ let note = '';
+ if(curMag < 0.05) note = 'Магнит покоится. Поток $\\Phi$ через катушку не меняется → индукционного тока нет.';
+ else if(curSign > 0) note = 'Магнит приближается к катушке. Поток $\\Phi$ растёт → возникает индукционный ток (одного знака).';
+ else note = 'Магнит удаляется от катушки. Поток $\\Phi$ убывает → возникает индукционный ток противоположного знака.';
+ out.innerHTML = note;
+ renderMath(out);
+
+ if(curSign > 0) movesPos = 1;
+ if(curSign < 0) movesNeg = 1;
+ if(!_done && movesPos && movesNeg){ _done = true; addXp(10, 'p31-iv2'); bumpProgress('p31', 15); }
+ }
+ xS.addEventListener('input', draw);
+ // Тикать чаще, чтобы дать стрелке амперметра «остыть»
+ setInterval(draw, 250);
+ draw();
+ })();
+
+ /* IV3 — Возникает ли индукционный ток? */
+ (function(){
+ const OPTS = ['Возникает', 'Не возникает'];
+ const Q = [
+ { q:'Магнит вдвигают в катушку.', ans:0, why:'$\\Phi$ через катушку меняется → ток есть.' },
+ { q:'Магнит покоится в катушке.', ans:1, why:'$\\Phi$ не меняется → тока нет.' },
+ { q:'Катушка вращается в постоянном магнитном поле.', ans:0, why:'Меняется угол $\\alpha$ → меняется $\\Phi$ → ток есть. (Принцип генератора.)' },
+ { q:'Ток в соседней катушке плавно растёт.', ans:0, why:'$B$ растёт → $\\Phi$ меняется → ток в нашей катушке есть.' },
+ { q:'В соседней катушке течёт постоянный ток.', ans:1, why:'$B$ постоянно → $\\Phi$ не меняется → тока нет.' },
+ { q:'Площадь контура в постоянном поле плавно уменьшается.', ans:0, why:'$S$ меняется → $\\Phi = BS\\cos\\alpha$ меняется → ток есть.' }
+ ];
+ let i = 0, score = 0;
+ const qEl = document.getElementById('p31-iv3-q');
+ const oEl = document.getElementById('p31-iv3-opts');
+ const fb = document.getElementById('p31-iv3-fb');
+ const iEl = document.getElementById('p31-iv3-i');
+ const sEl = document.getElementById('p31-iv3-s');
+
+ function show(){
+ if(i >= Q.length){
+ qEl.innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ oEl.innerHTML = '';
+ if(score === Q.length){ addXp(15, 'p31-iv3'); bumpProgress('p31', 25); }
+ else if(score >= 4){ addXp(8, 'p31-iv3'); bumpProgress('p31', 15); }
+ return;
+ }
+ iEl.textContent = (i+1);
+ sEl.textContent = score;
+ qEl.innerHTML = Q[i].q;
+ oEl.innerHTML = OPTS.map((t, k) => '').join('');
+ fb.style.display = 'none';
+ renderMath(qEl);
+ oEl.querySelectorAll('button').forEach(b => {
+ b.addEventListener('click', () => {
+ const v = +b.dataset.v;
+ if(v === Q[i].ans){ score++; feedback(fb, true, '✓ Верно! ' + Q[i].why + ' Дальше ▶'); }
+ else feedback(fb, false, '✗ Верно: ' + OPTS[Q[i].ans] + '. ' + Q[i].why + ' Дальше ▶');
+ sEl.textContent = score;
+ oEl.querySelectorAll('button').forEach(x => x.disabled = true);
+ i++;
+ setTimeout(show, 1800);
+ });
+ });
+ }
+ document.getElementById('p31-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
+ show();
+ })();
+
+ /* IV4 — Тренажёр */
+ (function(){
+ const Q = [
+ { q:'$B = 0{,}5$ Тл, $S = 0{,}04$ м², $\\alpha = 0°$. Найди $\\Phi$ (в мВб).', ans:20, tol:1, why:'$\\Phi = BS\\cos 0° = 0{,}5\\cdot 0{,}04 = 0{,}02$ Вб = 20 мВб.' },
+ { q:'То же поле и площадь, но $\\alpha = 60°$. Найди $\\Phi$ (в мВб).', ans:10, tol:0.6, why:'$\\Phi = 20\\cdot \\cos 60° = 20\\cdot 0{,}5 = 10$ мВб.' },
+ { q:'$\\alpha = 90°$. Чему равен $\\Phi$ (в мВб)?', ans:0, tol:0.01, why:'$\\cos 90° = 0$ → $\\Phi = 0$. Линии поля скользят вдоль рамки.' },
+ { q:'При $B = 0$ Тл — каким будет $\\Phi$ (в Вб)?', ans:0, tol:0.01, why:'Нет поля → нет потока. $\\Phi = 0$.' },
+ { q:'Если площадь $S$ удвоить (при тех же $B$ и $\\alpha$), во сколько раз изменится $\\Phi$?', ans:2, tol:0.1, why:'$\\Phi \\propto S$ → удвоится.' }
+ ];
+ let i = 0, score = 0;
+ const qEl = document.getElementById('p31-iv4-q');
+ const fb = document.getElementById('p31-iv4-fb');
+ const iEl = document.getElementById('p31-iv4-i');
+ const sEl = document.getElementById('p31-iv4-s');
+ const inp = document.getElementById('p31-iv4-inp');
+ const bGo = document.getElementById('p31-iv4-go');
+
+ function show(){
+ if(i >= Q.length){
+ qEl.innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ inp.disabled = true; bGo.disabled = true;
+ if(score === Q.length){ addXp(15, 'p31-iv4'); bumpProgress('p31', 25); }
+ else if(score >= 3){ addXp(8, 'p31-iv4'); bumpProgress('p31', 15); }
+ return;
+ }
+ iEl.textContent = (i+1);
+ sEl.textContent = score;
+ qEl.innerHTML = Q[i].q;
+ fb.style.display = 'none';
+ inp.value = ''; inp.disabled = false; bGo.disabled = false; inp.focus();
+ renderMath(qEl);
+ }
+ function check(){
+ if(inp.disabled) return;
+ const v = parseFloat(inp.value.replace(',','.'));
+ if(!isFinite(v)){ feedback(fb, false, 'Введи число.'); return; }
+ const tol = Math.max(Q[i].tol, Math.abs(Q[i].ans)*0.05);
+ const ok = Math.abs(v - Q[i].ans) <= tol;
+ if(ok){ score++; feedback(fb, true, '✓ Верно! ' + Q[i].why + ' Дальше ▶'); }
+ else feedback(fb, false, '✗ Верно: ' + Q[i].ans + '. ' + Q[i].why + ' Дальше ▶');
+ sEl.textContent = score;
+ inp.disabled = true; bGo.disabled = true;
+ i++;
+ setTimeout(show, 1900);
+ }
+ bGo.addEventListener('click', check);
+ inp.addEventListener('keydown', e => { if(e.key==='Enter') check(); });
+ document.getElementById('p31-iv4-restart').addEventListener('click', () => { i = 0; score = 0; inp.disabled=false; bGo.disabled=false; show(); });
+ show();
+ })();
+
wireReadBtn('p31');
}
function build_p32(){
const box = document.getElementById('p32-body');
let html = '';
- html += makeCard('theory', "Правило Ленца. Закон Фарадея", "§32", `
-
Правило Ленца. Закон Фарадея — этот параграф в разработке (Phase 1+).
-
Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.
-
- Phase 0: создан скелет учебника. Phase 5+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
-
+
+ /* THEORY 1 — Правило Ленца */
+ html += makeCard('theory', "Правило Ленца", "§32", `
+
Правило Ленца (1834): индукционный ток направлен так, чтобы своим магнитным полем препятствовать изменению магнитного потока, его вызвавшему.
+
Это правило отражает закон сохранения энергии: если бы ток усиливал изменение, можно было бы получать неограниченную энергию из ничего.
+
Пример 1: магнит N-полюсом приближается к катушке.
+
+
Поток $\\Phi$ через катушку растёт.
+
Индукционный ток создаёт встречное магнитное поле (его «N-полюс» обращён к магниту).
+
Магнит отталкивается — приходится приложить силу, чтобы продолжать движение.
+
Эта работа преобразуется в энергию индукционного тока.
+
+
Пример 2: магнит удаляется. Поток убывает. Индукционный ток создаёт поле, поддерживающее убывающий поток — катушка притягивает магнит.
+
Короткая мнемоника. «Природа сопротивляется изменению»: ток всегда против того, кто его вызвал.
`);
+
+ /* THEORY 2 — Закон Фарадея */
+ html += makeCard('rule', "Закон электромагнитной индукции (Фарадея)", "§32", `
+
Величина ЭДС индукции в контуре равна скорости изменения магнитного потока:
Это переменная синусоидальная ЭДС. Так работают электростанции.
+
Трансформатор. Переменный ток в первичной катушке создаёт переменный $\\Phi$ через сердечник → во вторичной катушке возникает ЭДС. Отношение напряжений равно отношению чисел витков:
+
$$\\dfrac{U_2}{U_1} = \\dfrac{N_2}{N_1}$$
+
Пример расчёта. За $\\Delta t = 0{,}1$ с магнитный поток через катушку из $N = 50$ витков изменился с $0$ до $0{,}4$ Вб. Найди модуль ЭДС индукции.
Вывод. Индукция позволяет получать высокие напряжения «из ничего» — но не нарушая закона сохранения энергии: работу совершает тот, кто меняет $\\Phi$.
+ `);
+
+ /* INTERACTIVE 1 — Правило Ленца */
+ html += `
+
ИНТЕРАКТИВ 1
Правило Ленца в действии
+
Двигай магнит к катушке и от неё. Индукционный ток создаёт поле, препятствующее изменению потока — магнит чувствует силу отталкивания (приближается) или притяжения (удаляется).