diff --git a/frontend/textbooks/physics_8_ch3.html b/frontend/textbooks/physics_8_ch3.html
index 642f206..8107502 100644
--- a/frontend/textbooks/physics_8_ch3.html
+++ b/frontend/textbooks/physics_8_ch3.html
@@ -1264,17 +1264,907 @@ function _p35_tasks(){
r();
}
-/* === Заглушки для §36..§40 и финала — будут заполнены ниже === */
-function build_p36(){ _stub_phaseN('p36','Преломление света','Phase 5 Wave 3'); }
-function build_p37(){ _stub_phaseN('p37','Линзы. Оптическая сила','Phase 5 Wave 3'); }
-function build_p38(){ _stub_phaseN('p38','Построение изображений в линзах','Phase 5 Wave 4'); }
-function build_p39(){ _stub_phaseN('p39','Глаз как оптическая система','Phase 5 Wave 4'); }
-function build_p40(){ _stub_phaseN('p40','Дефекты зрения. Очки','Phase 5 Wave 4'); }
-function build_final3(){ _stub_phaseN('final3','Финал главы 3','Phase 5 Wave 4'); }
-function _stub_phaseN(id, name, ph){
- const box = document.getElementById(id+'-body');
- box.innerHTML = buildStub(id, name, ph) + secNavFor(id) + readButton(id);
- renderMath(box); wireReadBtn(id);
+/* ======== §36 — Преломление света ======== */
+function build_p36(){
+ const box = document.getElementById('p36-body'); let h = '';
+ h += makeCard('theory', 'Закон преломления', '§ 36.1',
+ '
При переходе из одной среды в другую луч света меняет направление. Это преломление.
'
+ +'Закон Снеллиуса:
'
+ +'$$\\dfrac{\\sin\\alpha}{\\sin\\beta} = \\dfrac{n_2}{n_1} = n_{2/1}$$
'
+ +'$n$ — показатель преломления среды. Чем больше $n$, тем сильнее замедляется свет.
'
+ );
+ h += makeCard('rule', 'Когда $\\alpha > \\beta$, а когда наоборот', '§ 36.2',
+ ''
+ +'- Из менее плотной в более плотную ($n_1 < n_2$): $\\alpha > \\beta$, луч приближается к нормали.
'
+ +'- Из более плотной в менее плотную: $\\alpha < \\beta$, луч удаляется от нормали.
'
+ +'
'
+ +'При большом $\\alpha$ из плотной среды возможно полное внутреннее отражение: луч вообще не выходит.
'
+ +'Таблица $n$:
'
+ +'- воздух — $1{,}00$;
- вода — $1{,}33$;
- стекло — $1{,}5$;
- алмаз — $2{,}42$.
'
+ );
+ h += makeCard('example', 'Примеры', '§ 36.3',
+ ''
+ +'- Карандаш в стакане воды кажется «сломанным».
'
+ +'- Рыба в воде кажется ближе, чем на самом деле.
'
+ +'- Мираж в пустыне — лучи от неба преломляются в тёплом воздухе.
'
+ +'- Призма раскладывает белый свет в радугу — разные цвета имеют разный $n$.
'
+ +'
'
+ );
+ h += ''
+ +'
Меняй $\\alpha$ и $n$ — увидь, как меняется $\\beta$. При большом $\\alpha$ из плотной среды наступает полное внутреннее отражение.
'
+ +'
'
+ +''
+ +'
'
+ +'
'
+ +'
$\\beta$ = 22°Полное внутреннее отражение!
';
+ h += ''
+ +'
При переходе в указанную среду угол $\\alpha$ — больше или меньше $\\beta$?
'
+ +'
'
+ +'
'
+ +'
Раунд: 1/5Правильно: 0
';
+ h += ''
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+ h += ''
+ +'
4+ — +15 XP.
'
+ +'
'
+ +'
Задача: 1/5Правильно: 0
';
+ box.innerHTML = h + secNavFor('p36') + readButton('p36');
+ renderMath(box); wireReadBtn('p36');
+ _p36_snell(); _p36_quiz(); _p36_dnd(); _p36_tasks();
+}
+function _p36_snell(){
+ const svg = document.getElementById('p36-sim'); if(!svg) return;
+ function d(){
+ const a = +document.getElementById('p36-a').value;
+ const n1 = +document.getElementById('p36-n1').value;
+ const n2 = +document.getElementById('p36-n2').value;
+ document.getElementById('p36-av').textContent = a;
+ const sinB = (n1/n2) * Math.sin(a*Math.PI/180);
+ const tir = Math.abs(sinB) > 1;
+ document.getElementById('p36-tir-lab').style.display = tir ? 'inline' : 'none';
+ if(tir){
+ document.getElementById('p36-bv').textContent = '—';
+ } else {
+ const b = Math.asin(sinB) * 180/Math.PI;
+ document.getElementById('p36-bv').textContent = b.toFixed(1);
+ }
+ const result = window.OPTICS.refractRay(230, 140, a, n1, n2, 90);
+ let bg = '';
+ /* верхняя среда */
+ bg += '';
+ /* нижняя среда */
+ bg += '';
+ bg += 'n₁ = '+n1+'';
+ bg += 'n₂ = '+n2+'';
+ svg.innerHTML = bg + result.svg;
+ }
+ ['p36-a','p36-n1','p36-n2'].forEach(id => document.getElementById(id).addEventListener('input', d));
+ document.getElementById('p36-n1').addEventListener('change', d);
+ document.getElementById('p36-n2').addEventListener('change', d);
+ d();
+}
+function _p36_quiz(){
+ const QS = [
+ {sit:'воздух → вода', ans:'L', why:'В более плотную: $\\alpha > \\beta$, луч ближе к нормали.'},
+ {sit:'вода → воздух', ans:'B', why:'Из более плотной: $\\alpha < \\beta$.'},
+ {sit:'воздух → стекло', ans:'L', why:'$\\alpha > \\beta$.'},
+ {sit:'стекло → воздух', ans:'B', why:'$\\alpha < \\beta$.'},
+ {sit:'воздух → алмаз', ans:'L', why:'$n_{алмаза} = 2{,}42$, очень плотный — $\\alpha \\gg \\beta$.'}
+ ];
+ let i = 0, ok = 0;
+ function r(){
+ const q = QS[i]; const w = document.getElementById('p36-quiz');
+ w.innerHTML = ''+q.sit+'
'
+ +''
+ +'';
+ document.getElementById('p36-quiz-r').textContent = (i+1);
+ document.getElementById('p36-quiz-ok').textContent = ok;
+ w.querySelectorAll('[data-p]').forEach(b=>{
+ b.addEventListener('click', ()=>{
+ if(b.disabled) return; w.querySelectorAll('[data-p]').forEach(x=>x.disabled=true);
+ const fb = document.getElementById('p36-q-fb');
+ if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p36-q'); bumpProgress('p36',4); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
+ document.getElementById('p36-quiz-ok').textContent = ok;
+ renderMath(w);
+ });
+ });
+ renderMath(w);
+ }
+ document.getElementById('p36-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
+ r();
+}
+function _p36_dnd(){
+ /* по возрастанию n: воздух 1, вода 1.33, стекло 1.5, алмаз 2.42 */
+ const items = [
+ {id:'a',cat:'r1',html:'воздух (1)'},
+ {id:'w',cat:'r2',html:'вода (1{,}33)'},
+ {id:'g',cat:'r3',html:'стекло (1{,}5)'},
+ {id:'d',cat:'r4',html:'алмаз (2{,}42)'}
+ ];
+ const dnd = setupSorter({ poolId:'p36-dnd-pool', scopeSelector:'#sec-p36', cats:['r1','r2','r3','r4'], items, columnLayout:false });
+ document.getElementById('p36-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p36-dnd-fb'); let wr = 0;
+ items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
+ if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p36-dnd'); bumpProgress('p36',20); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; }
+ });
+ document.getElementById('p36-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p36-dnd-fb').style.display='none'; });
+}
+function _p36_tasks(){
+ const TASKS = [
+ {q:'$n_1 = 1$, $n_2 = 1{,}5$, $\\alpha = 30$°. Найди $\\beta$ (°, до 0,1).', ans:19.5, tol:0.5, why:'$\\sin\\beta = \\sin30/1{,}5 = 0{,}333$, $\\beta = 19{,}5$°.'},
+ {q:'$n_1 = 1{,}33$ (вода), $n_2 = 1$, $\\alpha = 45$°. Найди $\\beta$ (°).', ans:70.5, tol:1, why:'$\\sin\\beta = 1{,}33 \\sin45/1 = 0{,}941$, $\\beta \\approx 70{,}5$°.'},
+ {q:'$\\alpha = 60$°, воздух → стекло (n=1,5). Найди $\\beta$ (°).', ans:35.3, tol:1, why:'$\\sin\\beta = \\sin60/1{,}5 = 0{,}577$, $\\beta \\approx 35{,}3$°.'},
+ {q:'В алмазе ($n=2{,}42$) полное внутр. отражение начинается при $\\alpha > $? (°)', ans:24.4, tol:1, why:'$\\sin\\alpha_{кр} = 1/n = 1/2{,}42 = 0{,}413$, $\\alpha_{кр} = 24{,}4$°.'},
+ {q:'Свет идёт перпендикулярно поверхности ($\\alpha = 0$). Куда пойдёт?', ans:0, tol:0.5, why:'Без преломления, $\\beta = 0$.'}
+ ];
+ let i = 0, ok = 0, done = 0, aw = false;
+ function r(){
+ const t = TASKS[i]; const w = document.getElementById('p36-task');
+ w.innerHTML = ''+(i+1)+'. '+t.q+'
'
+ +''
+ +''+t.why+'
';
+ document.getElementById('p36-task-i').textContent = (i+1);
+ document.getElementById('p36-task-ok').textContent = ok;
+ document.getElementById('p36-tgo').addEventListener('click', ()=>{
+ const v = parseFloat((document.getElementById('p36-tinp').value || '').replace(',','.'));
+ const fb = document.getElementById('p36-tfb');
+ 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,'p36-t'); bumpProgress('p36',6); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
+ document.getElementById('p36-task-ok').textContent = ok;
+ renderMath(w);
+ if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p36-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p36-bonus'); bumpProgress('p36',15); }, 500); }
+ });
+ document.getElementById('p36-thn').addEventListener('click', ()=>{ document.getElementById('p36-tht').classList.toggle('show'); });
+ document.getElementById('p36-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
+ renderMath(w);
+ }
+ r();
+}
+
+/* ======== §37 — Линзы. Оптическая сила ======== */
+function build_p37(){
+ const box = document.getElementById('p37-body'); let h = '';
+ h += makeCard('theory', 'Что такое линза', '§ 37.1',
+ 'Линза — прозрачное тело, ограниченное двумя сферическими (или одной плоской и одной сферической) поверхностями.
'
+ +'Два типа:
'
+ +''
+ +'- Собирающая (выпуклая): толще в середине. Параллельные лучи собираются в точке $F$ — фокусе.
'
+ +'- Рассеивающая (вогнутая): тоньше в середине. Параллельные лучи расходятся, как будто исходят из мнимого фокуса.
'
+ +'
'
+ +'Главная оптическая ось — прямая через центры обеих сфер.
'
+ );
+ h += makeCard('rule', 'Оптическая сила', '§ 37.2',
+ 'Оптическая сила линзы:
'
+ +'$$D = \\dfrac{1}{F}$$
'
+ +'Единица — диоптрия (дптр) = $1/$м.
'
+ +''
+ +'- $D > 0$ — собирающая линза;
'
+ +'- $D < 0$ — рассеивающая;
'
+ +'- чем больше $|D|$, тем сильнее линза.
'
+ +'
'
+ +'Очки $+2$ дптр $\\to F = 0{,}5$ м (собирающая). Очки $-3$ дптр $\\to F = -0{,}33$ м (рассеивающая).
'
+ );
+ h += makeCard('example', 'Применение', '§ 37.3',
+ ''
+ +'- Очки.
'
+ +'- Лупа, микроскоп, телескоп.
'
+ +'- Объектив фотоаппарата, проектора.
'
+ +'- Хрусталик глаза.
'
+ +'
'
+ );
+ h += ''
+ +'
Выбери тип линзы и фокусное расстояние.
'
+ +'
'
+ +'
'
+ +'
';
+ h += ''
+ +'
'
+ +'
$D$ = 2.0 дптрТип: собирающая
';
+ h += ''
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+ h += ''
+ +'
4+ — +15 XP.
'
+ +'
'
+ +'
Задача: 1/5Правильно: 0
';
+ box.innerHTML = h + secNavFor('p37') + readButton('p37');
+ renderMath(box); wireReadBtn('p37');
+ _p37_lens(); _p37_calc(); _p37_dnd(); _p37_tasks();
+}
+function _p37_lens(){
+ const svg = document.getElementById('p37-sim'); if(!svg) return;
+ function d(){
+ const k = document.getElementById('p37-k').value;
+ const F = +document.getElementById('p37-f').value;
+ document.getElementById('p37-fv').textContent = F;
+ svg.innerHTML = window.OPTICS.thinLens(230, 100, 70, F, k);
+ }
+ ['p37-k','p37-f'].forEach(id => document.getElementById(id).addEventListener('input', d));
+ document.getElementById('p37-k').addEventListener('change', d);
+ d();
+}
+function _p37_calc(){
+ function u(){
+ const F = +document.getElementById('p37-fm').value;
+ document.getElementById('p37-fmv').textContent = F.toFixed(2);
+ if(F === 0){
+ document.getElementById('p37-dv').textContent = '∞';
+ document.getElementById('p37-tk').textContent = '—';
+ return;
+ }
+ const D = 1/F;
+ document.getElementById('p37-dv').textContent = D.toFixed(2);
+ document.getElementById('p37-tk').textContent = F > 0 ? 'собирающая' : 'рассеивающая';
+ }
+ document.getElementById('p37-fm').addEventListener('input', u); u();
+}
+function _p37_dnd(){
+ const items = [
+ {id:'a',cat:'c',html:'выпуклая (+)'},
+ {id:'b',cat:'c',html:'$D = +3$ дптр'},
+ {id:'c',cat:'c',html:'лупа'},
+ {id:'d',cat:'c',html:'хрусталик глаза'},
+ {id:'e',cat:'d',html:'вогнутая (−)'},
+ {id:'f',cat:'d',html:'$D = -2$ дптр'},
+ {id:'g',cat:'d',html:'очки для близоруких'},
+ {id:'h',cat:'d',html:'рассеивает лучи'}
+ ];
+ const dnd = setupSorter({ poolId:'p37-dnd-pool', scopeSelector:'#sec-p37', cats:['c','d'], items, columnLayout:false });
+ document.getElementById('p37-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p37-dnd-fb'); let wr = 0;
+ items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
+ if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p37-dnd'); bumpProgress('p37',20); renderMath(fb); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; renderMath(fb); }
+ });
+ document.getElementById('p37-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p37-dnd-fb').style.display='none'; });
+}
+function _p37_tasks(){
+ const TASKS = [
+ {q:'$F = 25$ см $= 0{,}25$ м. Найди $D$ (дптр).', ans:4, tol:0.1, why:'$D = 1/0{,}25 = 4$ дптр.'},
+ {q:'$D = +5$ дптр. Найди $F$ (см).', ans:20, tol:0.5, why:'$F = 1/5 = 0{,}2$ м $= 20$ см.'},
+ {q:'$D = -2$ дптр. Какой $F$ (м, с знаком)?', ans:-0.5, tol:0.05, why:'$F = 1/(-2) = -0{,}5$ м.'},
+ {q:'Очки +1.5 дптр. Какое фокусное расстояние (см)?', ans:67, tol:2, why:'$F = 1/1{,}5 \\approx 0{,}67$ м $= 67$ см.'},
+ {q:'У хрусталика глаза $D = 60$ дптр. Найди $F$ в мм.', ans:17, tol:1, why:'$F = 1/60 \\approx 0{,}017$ м = $17$ мм.'}
+ ];
+ let i = 0, ok = 0, done = 0, aw = false;
+ function r(){
+ const t = TASKS[i]; const w = document.getElementById('p37-task');
+ w.innerHTML = ''+(i+1)+'. '+t.q+'
'
+ +''
+ +''+t.why+'
';
+ document.getElementById('p37-task-i').textContent = (i+1);
+ document.getElementById('p37-task-ok').textContent = ok;
+ document.getElementById('p37-tgo').addEventListener('click', ()=>{
+ const v = parseFloat((document.getElementById('p37-tinp').value || '').replace(',','.'));
+ const fb = document.getElementById('p37-tfb');
+ 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,'p37-t'); bumpProgress('p37',6); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
+ document.getElementById('p37-task-ok').textContent = ok;
+ renderMath(w);
+ if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p37-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p37-bonus'); bumpProgress('p37',15); }, 500); }
+ });
+ document.getElementById('p37-thn').addEventListener('click', ()=>{ document.getElementById('p37-tht').classList.toggle('show'); });
+ document.getElementById('p37-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
+ renderMath(w);
+ }
+ r();
+}
+
+/* ======== §38 — Построение изображений в линзах ======== */
+function build_p38(){
+ const box = document.getElementById('p38-body'); let h = '';
+ h += makeCard('theory', 'Формула тонкой линзы', '§ 38.1',
+ 'Расстояния от предмета $d$ и от изображения $f$ до линзы связаны:
'
+ +'$$\\dfrac{1}{F} = \\dfrac{1}{d} + \\dfrac{1}{f}$$
'
+ +'Из неё $f = \\dfrac{dF}{d - F}$.
'
+ +'Если $f > 0$ — изображение действительное (с той стороны линзы, на экране). Если $f < 0$ — мнимое (с той же стороны, где предмет).
'
+ );
+ h += makeCard('rule', '3 «золотых» луча', '§ 38.2',
+ ''
+ +'- Луч через центр линзы — не преломляется.
'
+ +'- Луч параллельный оси — после линзы проходит через дальний $F$.
'
+ +'- Луч через ближний $F$ — после линзы идёт параллельно оси.
'
+ +'
'
+ +'Любые два из этих лучей пересекутся в точке изображения.
'
+ );
+ h += makeCard('example', 'Зависимость от расстояния', '§ 38.3',
+ 'В собирающей линзе ($F > 0$):
'
+ +''
+ +'- $d > 2F$ — изображение уменьшенное, перевёрнутое, действительное (фотоаппарат).
'
+ +'- $d = 2F$ — равное, перевёрнутое, действительное.
'
+ +'- $F < d < 2F$ — увеличенное, перевёрнутое, действительное (проектор).
'
+ +'- $d = F$ — на бесконечности (параллельные лучи).
'
+ +'- $d < F$ — увеличенное, прямое, мнимое (лупа).
'
+ +'
'
+ );
+ h += ''
+ +'
Меняй $F$ и $d$ — наблюдай, как меняется изображение. Зелёный — действительное, фиолетовый пунктир — мнимое.
'
+ +'
'
+ +'
'
+ +'
'
+ +'
'
+ +'$f$ = 30 см'
+ +'Увеличение: 2.0x'
+ +'Тип: действительное, перевёрнутое
';
+ h += ''
+ +'
Тип изображения по условиям.
'
+ +'
'
+ +'
'
+ +'
Раунд: 1/5Правильно: 0
';
+ h += ''
+ +'
К какому случаю относится прибор?
'
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+ h += ''
+ +'
4+ — +15 XP.
'
+ +'
'
+ +'
Задача: 1/5Правильно: 0
';
+ box.innerHTML = h + secNavFor('p38') + readButton('p38');
+ renderMath(box); wireReadBtn('p38');
+ _p38_constr(); _p38_quiz(); _p38_dnd(); _p38_tasks();
+}
+function _p38_constr(){
+ const svg = document.getElementById('p38-sim'); if(!svg) return;
+ function d(){
+ const F = +document.getElementById('p38-f').value;
+ const dist = +document.getElementById('p38-d').value;
+ document.getElementById('p38-fv').textContent = F;
+ document.getElementById('p38-dv').textContent = dist;
+ const res = window.OPTICS.buildLensImage(F, dist, 30);
+ const f = res.f, h2 = res.h2, virtual = res.virtual;
+ document.getElementById('p38-fs').textContent = isFinite(f) ? f.toFixed(1) : '∞';
+ document.getElementById('p38-mg').textContent = isFinite(f) ? Math.abs(h2/30).toFixed(2) : '∞';
+ const type = virtual ? (h2 > 0 ? 'мнимое, прямое, увеличенное' : 'мнимое, прямое') : (h2 < 0 ? 'действительное, перевёрнутое' : 'действительное');
+ document.getElementById('p38-tp').textContent = type;
+
+ /* Рисуем SVG */
+ const cx = 300, cy = 130;
+ const sc = 4; /* px per см */
+ let s = '';
+ /* ось */
+ s += '';
+ /* линза */
+ s += window.OPTICS.thinLens(cx, cy, 60, F*sc, 'converging');
+ /* предмет — слева */
+ const objX = cx - dist*sc;
+ const objH = 60; /* высота 30 см = 60 px */
+ s += '';
+ s += '';
+ /* изображение */
+ if(isFinite(f) && !virtual){
+ const imgX = cx + f*sc;
+ const imgY = cy - h2*sc/15; /* h2 от исходн. h=30 — масштабируем */
+ s += '';
+ const tip = cy - h2*2;
+ s += '';
+ } else if(isFinite(f) && virtual){
+ const imgX = cx + f*sc; /* отрицательное f → слева */
+ s += '';
+ const tip = cy - h2*2;
+ s += '';
+ }
+ svg.innerHTML = s;
+ }
+ document.getElementById('p38-f').addEventListener('input', d);
+ document.getElementById('p38-d').addEventListener('input', d);
+ d();
+}
+function _p38_quiz(){
+ const QS = [
+ {sit:'$d > 2F$ (например, $d = 30$, $F = 10$)', ans:'A', why:'Уменьшенное, перевёрнутое, действительное.'},
+ {sit:'$d = 2F$', ans:'B', why:'Равное, перевёрнутое, действительное.'},
+ {sit:'$F < d < 2F$ (проектор)', ans:'C', why:'Увеличенное, перевёрнутое, действительное.'},
+ {sit:'$d < F$ (лупа)', ans:'D', why:'Увеличенное, прямое, мнимое.'},
+ {sit:'$d = F$', ans:'E', why:'На бесконечности — параллельные лучи.'}
+ ];
+ let i = 0, ok = 0;
+ function r(){
+ const q = QS[i]; const w = document.getElementById('p38-quiz');
+ w.innerHTML = ''+q.sit+'
'
+ +''
+ +''
+ +''
+ +''
+ +''
+ +''
+ +'
';
+ document.getElementById('p38-quiz-r').textContent = (i+1);
+ document.getElementById('p38-quiz-ok').textContent = ok;
+ w.querySelectorAll('[data-p]').forEach(b=>{
+ b.addEventListener('click', ()=>{
+ if(b.disabled) return; w.querySelectorAll('[data-p]').forEach(x=>x.disabled=true);
+ const fb = document.getElementById('p38-q-fb');
+ if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p38-q'); bumpProgress('p38',4); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
+ document.getElementById('p38-quiz-ok').textContent = ok;
+ });
+ });
+ }
+ document.getElementById('p38-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
+ r();
+}
+function _p38_dnd(){
+ const items = [
+ {id:'p',cat:'far',html:'фотоаппарат'},
+ {id:'eye',cat:'far',html:'глаз (далёкий предмет)'},
+ {id:'mp',cat:'mid',html:'проектор'},
+ {id:'film',cat:'mid',html:'кинопроектор'},
+ {id:'l1',cat:'lupa',html:'лупа'},
+ {id:'l2',cat:'lupa',html:'часы под лупой'}
+ ];
+ const dnd = setupSorter({ poolId:'p38-dnd-pool', scopeSelector:'#sec-p38', cats:['far','mid','lupa'], items, columnLayout:false });
+ document.getElementById('p38-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p38-dnd-fb'); let wr = 0;
+ items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
+ if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p38-dnd'); bumpProgress('p38',20); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; }
+ });
+ document.getElementById('p38-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p38-dnd-fb').style.display='none'; });
+}
+function _p38_tasks(){
+ const TASKS = [
+ {q:'$F = 20$ см, $d = 30$ см. Найди $f$ (см).', ans:60, tol:1, why:'$f = dF/(d-F) = 30\\cdot20/(30-20) = 60$ см.'},
+ {q:'$F = 10$ см, $d = 15$ см. Найди $f$ (см).', ans:30, tol:1, why:'$f = 15\\cdot10/5 = 30$ см.'},
+ {q:'$F = 20$ см, $d = 60$ см. Найди $f$ (см).', ans:30, tol:1, why:'$f = 60\\cdot20/40 = 30$ см.'},
+ {q:'Предмет высотой 5 см, $d = 30$ см, $f = 60$ см. Найди высоту изображения (см).', ans:10, tol:0.5, why:'$h_2 = h \\cdot f/d = 5 \\cdot 60/30 = 10$ см.'},
+ {q:'$F = 15$ см, $d = 10$ см (лупа). Найди $|f|$ (см).', ans:30, tol:1, why:'$f = 10\\cdot15/(10-15) = -30$, |f| = 30 см. Мнимое.'}
+ ];
+ let i = 0, ok = 0, done = 0, aw = false;
+ function r(){
+ const t = TASKS[i]; const w = document.getElementById('p38-task');
+ w.innerHTML = ''+(i+1)+'. '+t.q+'
'
+ +''
+ +''+t.why+'
';
+ document.getElementById('p38-task-i').textContent = (i+1);
+ document.getElementById('p38-task-ok').textContent = ok;
+ document.getElementById('p38-tgo').addEventListener('click', ()=>{
+ const v = parseFloat((document.getElementById('p38-tinp').value || '').replace(',','.'));
+ const fb = document.getElementById('p38-tfb');
+ 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,'p38-t'); bumpProgress('p38',6); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
+ document.getElementById('p38-task-ok').textContent = ok;
+ renderMath(w);
+ if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p38-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p38-bonus'); bumpProgress('p38',15); }, 500); }
+ });
+ document.getElementById('p38-thn').addEventListener('click', ()=>{ document.getElementById('p38-tht').classList.toggle('show'); });
+ document.getElementById('p38-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
+ renderMath(w);
+ }
+ r();
+}
+
+/* ======== §39 — Глаз как оптическая система ======== */
+function build_p39(){
+ const box = document.getElementById('p39-body'); let h = '';
+ h += makeCard('theory', 'Устройство глаза', '§ 39.1',
+ 'Глаз — сложная оптическая система:
'
+ +''
+ +'- Роговица — прозрачная передняя оболочка, преломляет свет.
'
+ +'- Зрачок — отверстие в радужной оболочке, регулирует количество света.
'
+ +'- Хрусталик — биологическая собирающая линза с переменным $F$.
'
+ +'- Сетчатка — задняя стенка глаза, на ней формируется изображение.
'
+ +'- Зрительный нерв — передаёт сигнал в мозг.
'
+ +'
'
+ );
+ h += makeCard('rule', 'Аккомодация', '§ 39.2',
+ 'Расстояние от хрусталика до сетчатки фиксировано. А расстояние до предметов меняется. Как глаз приспосабливается?
'
+ +'Мышцы вокруг хрусталика меняют его кривизну (форму):
'
+ +''
+ +'- Далёкий предмет: хрусталик уплощается, $F$ растёт.
'
+ +'- Близкий предмет: хрусталик округляется, $F$ уменьшается.
'
+ +'
'
+ +'Это и есть аккомодация.
'
+ );
+ h += makeCard('example', 'Расстояние наилучшего зрения', '§ 39.3',
+ 'Расстояние, на котором глаз видит чётко без напряжения, — около 25 см. На таком расстоянии обычно держат книгу при чтении.
'
+ +'Ближе глаз перенапрягается, дальше — теряется чёткость деталей.
'
+ );
+ h += ''
+ +'
Меняй расстояние до предмета — увидь, как меняется форма хрусталика.
'
+ +'
'
+ +'
';
+ h += ''
+ +'
Какая часть глаза за что отвечает?
'
+ +'
'
+ +'
'
+ +'
Раунд: 1/5Правильно: 0
';
+ h += ''
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+ h += ''
+ +'
'
+ +'
Вопрос: 1/6Правильно: 0
';
+ box.innerHTML = h + secNavFor('p39') + readButton('p39');
+ renderMath(box); wireReadBtn('p39');
+ _p39_eye(); _p39_quiz(); _p39_dnd(); _p39_mcq();
+}
+function _p39_eye(){
+ const svg = document.getElementById('p39-sim'); if(!svg) return;
+ function d(){
+ const dval = +document.getElementById('p39-d').value;
+ /* 0 — далёкий, 100 — близкий */
+ document.getElementById('p39-dv').textContent = dval < 30 ? 'далёкий (бесконечность)' : dval < 70 ? 'на расстоянии 1 м' : 'близкий (25 см)';
+ const acc = dval / 100;
+ let s = '';
+ /* «предмет» слева */
+ s += window.OPTICS.lightObject(60, 130, 40, 'arrow');
+ s += 'предмет';
+ /* линии лучей */
+ s += '';
+ s += '';
+ /* глаз справа */
+ s += window.OPTICS.eyeDiagram(330, 115, 70, acc);
+ /* подпись аккомодации */
+ s += 'хрусталик '+(acc < 0.3 ? 'уплощён' : acc < 0.7 ? 'средний' : 'округлён')+'';
+ svg.innerHTML = s;
+ }
+ document.getElementById('p39-d').addEventListener('input', d); d();
+}
+function _p39_quiz(){
+ const QS = [
+ {sit:'Эта часть преломляет свет на входе.', opts:['Сетчатка','Роговица','Зрачок','Нерв'], ans:1, why:'Роговица — главный преломляющий элемент.'},
+ {sit:'Она меняет $F$ при изменении расстояния.', opts:['Сетчатка','Хрусталик','Радужка','Зрачок'], ans:1, why:'Хрусталик меняет форму — аккомодация.'},
+ {sit:'На ней формируется изображение.', opts:['Радужка','Сетчатка','Зрачок','Зр. нерв'], ans:1, why:'Сетчатка — «экран» глаза.'},
+ {sit:'Она регулирует количество света.', opts:['Радужка/зрачок','Хрусталик','Роговица','Сетчатка'], ans:0, why:'На ярком свету зрачок сужается.'},
+ {sit:'Это сетчатка передаёт сигнал в мозг через…', opts:['артерию','зрительный нерв','лимфу','аккомодацию'], ans:1, why:'Зрительный нерв.'}
+ ];
+ let i = 0, ok = 0;
+ function r(){
+ const q = QS[i]; const w = document.getElementById('p39-quiz');
+ let html = ''+(i+1)+'. '+q.sit+'
';
+ q.opts.forEach((o,k)=>{ html += ''; });
+ html += '
';
+ w.innerHTML = html;
+ document.getElementById('p39-quiz-r').textContent = (i+1);
+ document.getElementById('p39-quiz-ok').textContent = ok;
+ w.querySelectorAll('[data-k]').forEach(b=>{
+ b.addEventListener('click', ()=>{
+ if(b.disabled) return; w.querySelectorAll('[data-k]').forEach(x=>x.disabled=true);
+ const k = +b.dataset.k; const fb = document.getElementById('p39-q-fb');
+ if(k===q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p39-q'); bumpProgress('p39',4); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
+ document.getElementById('p39-quiz-ok').textContent = ok;
+ });
+ });
+ }
+ document.getElementById('p39-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
+ r();
+}
+function _p39_dnd(){
+ const items = [
+ {id:'r',cat:'op',html:'роговица'},{id:'h',cat:'op',html:'хрусталик'},{id:'z',cat:'op',html:'зрачок'},{id:'i',cat:'op',html:'радужка'},
+ {id:'s',cat:'nr',html:'сетчатка'},{id:'n',cat:'nr',html:'зрительный нерв'},{id:'c',cat:'nr',html:'колбочки и палочки'},{id:'br',cat:'nr',html:'мозг (анализ)'}
+ ];
+ const dnd = setupSorter({ poolId:'p39-dnd-pool', scopeSelector:'#sec-p39', cats:['op','nr'], items, columnLayout:false });
+ document.getElementById('p39-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p39-dnd-fb'); let wr = 0;
+ items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
+ if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p39-dnd'); bumpProgress('p39',20); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; }
+ });
+ document.getElementById('p39-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p39-dnd-fb').style.display='none'; });
+}
+function _p39_mcq(){
+ const QS = [
+ {q:'Что играет роль линзы в глазу?', opts:['роговица','хрусталик','сетчатка','зрачок'], ans:1, why:'Хрусталик с переменным F.'},
+ {q:'Что изменяется при аккомодации?', opts:['расст. до сетчатки','форма хрусталика','цвет радужки','диаметр зрачка'], ans:1, why:'Мышцы меняют форму хрусталика.'},
+ {q:'Где формируется изображение?', opts:['в зрачке','на роговице','на сетчатке','в нерве'], ans:2, why:'На сетчатке.'},
+ {q:'Расстояние наилучшего зрения для здорового глаза?', opts:['10 см','25 см','1 м','5 м'], ans:1, why:'25 см.'},
+ {q:'Каким будет изображение на сетчатке?', opts:['прямое, мнимое','перевёрн., действ.','прямое, действ.','мнимое'], ans:1, why:'Глаз — собирающая система: перевёрнутое действительное (мозг переворачивает).'},
+ {q:'Что НЕ оптический элемент глаза?', opts:['роговица','хрусталик','сетчатка','нерв'], ans:3, why:'Нерв передаёт сигнал, не преломляет свет.'}
+ ];
+ let i = 0, ok = 0, done = 0, aw = false;
+ function r(){
+ const q = QS[i]; const w = document.getElementById('p39-mcq');
+ let h = ''+(i+1)+'. '+q.q+'
';
+ q.opts.forEach((o,k)=>{ h += ''; });
+ h += '
';
+ w.innerHTML = h;
+ document.getElementById('p39-mcq-i').textContent = (i+1);
+ document.getElementById('p39-mcq-ok').textContent = ok;
+ w.querySelectorAll('[data-k]').forEach(b=>{
+ b.addEventListener('click', ()=>{
+ if(b.disabled) return; w.querySelectorAll('[data-k]').forEach(x=>x.disabled=true);
+ const k = +b.dataset.k; const fb = document.getElementById('p39-mcq-fb');
+ if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(2,'p39-mcq'); bumpProgress('p39',3); }
+ else { done++; fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
+ document.getElementById('p39-mcq-ok').textContent = ok;
+ if(done >= QS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p39-mcq-fb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p39-bonus'); bumpProgress('p39',15); }, 500); }
+ });
+ });
+ document.getElementById('p39-mcq-n').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
+ }
+ r();
+}
+
+/* ======== §40 — Дефекты зрения. Очки ======== */
+function build_p40(){
+ const box = document.getElementById('p40-body'); let h = '';
+ h += makeCard('theory', 'Близорукость', '§ 40.1',
+ 'Близорукость (миопия) — изображение далёких предметов фокусируется перед сетчаткой.
'
+ +'Причина: глаз слегка вытянут, или хрусталик слишком сильный. Человек хорошо видит вблизи, плохо — вдаль.
'
+ +'Лечение: рассеивающие линзы ($D < 0$). Они «отодвигают» фокус на сетчатку.
'
+ );
+ h += makeCard('rule', 'Дальнозоркость', '§ 40.2',
+ 'Дальнозоркость (гиперметропия) — изображение фокусируется за сетчаткой.
'
+ +'Причина: глаз короче нормы, или хрусталик слабоват. Человек плохо видит вблизи.
'
+ +'Лечение: собирающие линзы ($D > 0$). Они «приближают» фокус к сетчатке.
'
+ );
+ h += makeCard('example', 'Как подобрать очки', '§ 40.3',
+ 'Сила линзы $D$ подбирается врачом-офтальмологом по таблицам.
'
+ +''
+ +'- $-1$ … $-3$ дптр — лёгкая близорукость.
'
+ +'- $-3$ … $-6$ дптр — средняя.
'
+ +'- $-6$ дптр и больше — сильная.
'
+ +'
'
+ +'Современные альтернативы: контактные линзы, лазерная коррекция.
'
+ );
+ h += ''
+ +'
Выбери дефект — увидь, какие очки нужны.
'
+ +'
'
+ +'
'
+ +'
Нужны очки: собирающие, $D > 0$
';
+ h += ''
+ +'
Подбери тип линз для дефекта.
'
+ +'
'
+ +'
'
+ +'
Раунд: 1/5Правильно: 0
';
+ h += ''
+ +'
'
+ +'
'
+ +'
'
+ +'
';
+ h += ''
+ +'
4+ — +15 XP.
'
+ +'
'
+ +'
Задача: 1/5Правильно: 0
';
+ box.innerHTML = h + secNavFor('p40') + readButton('p40');
+ renderMath(box); wireReadBtn('p40');
+ _p40_glass(); _p40_quiz(); _p40_dnd(); _p40_tasks();
+}
+function _p40_glass(){
+ const svg = document.getElementById('p40-sim'); if(!svg) return;
+ function d(){
+ const def = document.getElementById('p40-def').value;
+ let s = '';
+ const cx = 320, cy = 100;
+ /* глаз */
+ s += window.OPTICS.eyeDiagram(cx, cy, 50, 0.3);
+ /* лучи входящие */
+ const labelLens = def === 'm' ? 'рассеивающие, $D < 0$' : def === 'h' ? 'собирающие, $D > 0$' : 'не нужны';
+ document.getElementById('p40-lens').innerHTML = labelLens;
+ /* для близорукости: фокус ПЕРЕД сетчаткой; рассеивающая линза «отодвигает» */
+ /* для дальнозоркости: фокус ЗА; собирающая «приближает» */
+ /* нарисуем фокусное пятно условно */
+ let focusX = 0;
+ let lensType = '';
+ if(def === 'm'){ focusX = cx - 5; lensType = 'diverging'; }
+ else if(def === 'h'){ focusX = cx + 60; lensType = 'converging'; }
+ else { focusX = cx + 40; }
+ /* очки спереди глаза */
+ if(def !== 'n'){
+ s += window.OPTICS.thinLens(200, cy, 50, 80, lensType);
+ }
+ /* лучи */
+ s += '';
+ s += '';
+ s += '';
+ s += '';
+ s += '';
+ s += 'фокус';
+ if(def === 'n') s += 'нормальное зрение';
+ if(def === 'm') s += 'близорукость: фокус до сетчатки';
+ if(def === 'h') s += 'дальнозоркость: фокус за сетчаткой';
+ svg.innerHTML = s;
+ }
+ document.getElementById('p40-def').addEventListener('change', d); d();
+}
+function _p40_quiz(){
+ const QS = [
+ {sit:'У человека близорукость', ans:'-', why:'Нужны рассеивающие, $D < 0$.'},
+ {sit:'Дальнозоркость', ans:'+', why:'Нужны собирающие, $D > 0$.'},
+ {sit:'У школьника плохо видит вдаль', ans:'-', why:'Признак близорукости.'},
+ {sit:'У бабушки плохо видит вблизи (мелкий шрифт)', ans:'+', why:'Возрастная дальнозоркость.'},
+ {sit:'$D = +2$ дптр прописал врач', ans:'+', why:'Собирающие — для дальнозоркости.'}
+ ];
+ let i = 0, ok = 0;
+ function r(){
+ const q = QS[i]; const w = document.getElementById('p40-quiz');
+ w.innerHTML = ''+q.sit+'
'
+ +''
+ +'';
+ document.getElementById('p40-quiz-r').textContent = (i+1);
+ document.getElementById('p40-quiz-ok').textContent = ok;
+ w.querySelectorAll('[data-p]').forEach(b=>{
+ b.addEventListener('click', ()=>{
+ if(b.disabled) return; w.querySelectorAll('[data-p]').forEach(x=>x.disabled=true);
+ const fb = document.getElementById('p40-q-fb');
+ if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p40-q'); bumpProgress('p40',4); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
+ document.getElementById('p40-quiz-ok').textContent = ok;
+ renderMath(w);
+ });
+ });
+ renderMath(w);
+ }
+ document.getElementById('p40-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
+ r();
+}
+function _p40_dnd(){
+ const items = [
+ {id:'a',cat:'m',html:'фокус ПЕРЕД сетчаткой'},
+ {id:'b',cat:'m',html:'плохо видит вдаль'},
+ {id:'c',cat:'m',html:'нужны рассеив. линзы'},
+ {id:'d',cat:'m',html:'$D < 0$'},
+ {id:'e',cat:'h',html:'фокус ЗА сетчаткой'},
+ {id:'f',cat:'h',html:'плохо видит вблизи'},
+ {id:'g',cat:'h',html:'нужны собир. линзы'},
+ {id:'i',cat:'h',html:'$D > 0$'}
+ ];
+ const dnd = setupSorter({ poolId:'p40-dnd-pool', scopeSelector:'#sec-p40', cats:['m','h'], items, columnLayout:false });
+ document.getElementById('p40-dnd-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p40-dnd-fb'); let wr = 0;
+ items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
+ if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p40-dnd'); bumpProgress('p40',20); renderMath(fb); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; renderMath(fb); }
+ });
+ document.getElementById('p40-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p40-dnd-fb').style.display='none'; });
+}
+function _p40_tasks(){
+ const TASKS = [
+ {q:'$F = -0{,}5$ м. Найди $D$ (дптр).', ans:-2, tol:0.1, why:'$D = 1/-0{,}5 = -2$ дптр.'},
+ {q:'Очки $D = +1{,}5$ дптр. Какой $F$ (см)?', ans:67, tol:2, why:'$F = 1/1{,}5 \\approx 0{,}67$ м = 67 см.'},
+ {q:'Близорукому нужны $D = -3$ дптр. Какой |F| (см)?', ans:33, tol:1, why:'|F| = 1/3 м = 33 см.'},
+ {q:'Какой знак $D$ для очков от близорукости? (введи +1 или -1)', ans:-1, tol:0.1, why:'Рассеивающие, $D < 0$.'},
+ {q:'У бабушки прописали +2 дптр. Какой её дефект? (введи 1=близорукость, 2=дальнозоркость)', ans:2, tol:0.1, why:'$D > 0$ → собирающие → дальнозоркость.'}
+ ];
+ let i = 0, ok = 0, done = 0, aw = false;
+ function r(){
+ const t = TASKS[i]; const w = document.getElementById('p40-task');
+ w.innerHTML = ''+(i+1)+'. '+t.q+'
'
+ +''
+ +''+t.why+'
';
+ document.getElementById('p40-task-i').textContent = (i+1);
+ document.getElementById('p40-task-ok').textContent = ok;
+ document.getElementById('p40-tgo').addEventListener('click', ()=>{
+ const v = parseFloat((document.getElementById('p40-tinp').value || '').replace(',','.'));
+ const fb = document.getElementById('p40-tfb');
+ 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,'p40-t'); bumpProgress('p40',6); }
+ else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
+ document.getElementById('p40-task-ok').textContent = ok;
+ renderMath(w);
+ if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p40-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p40-bonus'); bumpProgress('p40',15); }, 500); }
+ });
+ document.getElementById('p40-thn').addEventListener('click', ()=>{ document.getElementById('p40-tht').classList.toggle('show'); });
+ document.getElementById('p40-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
+ renderMath(w);
+ }
+ r();
+}
+
+/* ======== ФИНАЛ ГЛАВЫ 3 ======== */
+function build_final3(){
+ const box = document.getElementById('final3-body'); let h = '';
+ h += ''
+ +''
+ +'
'
+ +'
$c$: $3 \\cdot 10^8$ м/с
'
+ +'
Тень: точечный источник $\\to$ только тень
'
+ +'
Отражение: $\\alpha = \\beta$
'
+ +'
Плоское зеркало: мнимое, прямое, равное
'
+ +'
Снеллиус: $\\sin\\alpha/\\sin\\beta = n_2/n_1$
'
+ +'
$n$ воды: 1,33; стекла: 1,5
'
+ +'
$D$: $D = 1/F$, дптр
'
+ +'
Линза: $1/F = 1/d + 1/f$
'
+ +'
Глаз: хрусталик меняет $F$ (аккомодация)
'
+ +'
Близорукость: $D < 0$; дальнозоркость: $D > 0$
'
+ +'
';
+
+ const BOSSES = [
+ {n:1, title:'Скорость света', q:'За какое время свет пройдёт 600 000 км (мин)?', hint:'$t = 6\\cdot10^8 / 3\\cdot10^8 = 2$ с. В минутах: $1/30 \\approx 0{,}033$ мин.', ans:0.033, tol:0.005, step:'0.001'},
+ {n:2, title:'Закон отражения', q:'$\\alpha = 25$°. Найди $\\beta$ (°).', hint:'$\\beta = \\alpha = 25$°.', ans:25, tol:0.5, step:'0.5'},
+ {n:3, title:'Преломление', q:'$\\alpha = 30$°, воздух → стекло (n=1{,}5). Найди $\\beta$ (°, до 0,1).', hint:'$\\sin\\beta = \\sin30/1{,}5 = 0{,}333$, $\\beta \\approx 19{,}5$°.', ans:19.5, tol:0.5, step:'0.1'},
+ {n:4, title:'$D$ линзы', q:'$F = 0{,}25$ м. Найди $D$ (дптр).', hint:'$D = 1/0{,}25 = 4$ дптр.', ans:4, tol:0.1, step:'0.1'},
+ {n:5, title:'Тонкая линза', q:'$F = 20$ см, $d = 30$ см. Найди $f$ (см).', hint:'$f = 30\\cdot20/(30-20) = 60$ см.', ans:60, tol:1, step:'1'},
+ {n:6, title:'Очки', q:'Близорукому прописали $|F| = 50$ см. Какое $D$ (дптр)?', hint:'$D = 1/F = 1/(-0{,}5) = -2$ дптр.', ans:-2, tol:0.1, step:'0.1'},
+ {n:7, title:'Магистр света', q:'Угол падения на стекло $\\alpha = 60$°, $n = 1{,}5$. Угол между падающим и преломлённым (°, до 0,1).', hint:'$\\sin\\beta = \\sin60/1{,}5 \\approx 0{,}577$, $\\beta \\approx 35{,}3$°. Угол между падающим (продолж.) и преломлённым: $\\alpha - \\beta \\approx 24{,}7$° в относ. направ-х. Но угол между фактич. (падающий до границы) и преломлённым (после) — $180 - \\alpha - \\beta = 180 - 95{,}3 \\approx 84{,}7$°.', ans:84.7, tol:1, step:'0.1'}
+ ];
+
+ h += ''
+ +'
'
+ +'
Боссов побеждено: 0 / 7'
+ +'
'
+ +'
'
+ +'
';
+
+ box.innerHTML = h + secNavFor('final3') + readButton('final3');
+ renderMath(box); wireReadBtn('final3');
+ _initFinal3_bosses(BOSSES);
+}
+function _initFinal3_bosses(BOSSES){
+ const KEY = 'physics8_ch3_bosses';
+ function loadState(){ try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch(e){ return {}; } }
+ function saveState(s){ try { localStorage.setItem(KEY, JSON.stringify(s)); } catch(e){} }
+ function updateBar(){
+ const s = loadState();
+ let won = 0; for(const k in s) if(s[k]) won++;
+ document.getElementById('f3-won').textContent = won;
+ document.getElementById('f3-bar').style.width = Math.round(won*100/BOSSES.length)+'%';
+ if(won >= BOSSES.length && !STATE.achievements.has('light_master')){
+ addXp(50, 'light-master');
+ achievement('light_master');
+ }
+ return won;
+ }
+ function renderAll(){
+ const cont = document.getElementById('f3-bosses');
+ const state = loadState();
+ let html = '';
+ BOSSES.forEach(b=>{
+ const solved = state[b.n];
+ html += ''
+ +'
Босс '+b.n+''+b.title+'
'
+ +'
'+b.q+'
'
+ +'
'
+ +''
+ +''
+ +''
+ +'
'
+ +'
'+b.hint+'
'
+ +'
'+(solved?'✓ Победа! +10 XP. Босс повержен.':'')+'
'
+ +'
';
+ });
+ cont.innerHTML = html;
+ BOSSES.forEach(b=>{
+ const go = document.getElementById('f3-b'+b.n+'-go');
+ const inp = document.getElementById('f3-b'+b.n+'-inp');
+ const fb = document.getElementById('f3-b'+b.n+'-fb');
+ const ht = document.getElementById('f3-b'+b.n+'-ht');
+ const hintBtn = document.getElementById('f3-b'+b.n+'-hint');
+ if(hintBtn) hintBtn.addEventListener('click', ()=>{ ht.style.display = ht.style.display==='block'?'none':'block'; });
+ if(!go || go.disabled) return;
+ go.addEventListener('click', ()=>{
+ const v = parseFloat((inp.value || '').replace(',','.'));
+ if(isNaN(v)){ fb.style.display='block'; fb.className='feedback fail'; fb.innerHTML='Введите число.'; return; }
+ if(Math.abs(v - b.ans) < b.tol){
+ fb.style.display='block'; fb.className='feedback ok'; fb.innerHTML='✓ Победа! +10 XP. '+b.hint;
+ go.disabled = true; inp.disabled = true;
+ document.getElementById('f3-boss-'+b.n).classList.add('solved');
+ const s = loadState();
+ if(!s[b.n]){ s[b.n]=true; saveState(s); addXp(10,'f3-boss-'+b.n); bumpProgress('final3', 14); }
+ updateBar();
+ renderMath(fb);
+ } else {
+ fb.style.display='block'; fb.className='feedback fail'; fb.innerHTML='✗ Не то. Перепроверь и попробуй снова.';
+ }
+ });
+ inp.addEventListener('keydown', e=>{ if(e.key === 'Enter') go.click(); });
+ });
+ renderMath(cont);
+ updateBar();
+ }
+ renderAll();
}
function init(){