feat(phys11 ch3): Wave 7 — §21-§23 + Финал главы 3 (ThinLens + TwoLensSystem)

- phys-fx.js: PHYS.ThinLens (собирающая/рассеивающая, 2 канон. луча, формула, мнимое=пунктир), PHYS.TwoLensSystem (телескоп Кеплера + микроскоп)
- §21: тонкая линза, формула 1/d + 1/f = 1/F, оптическая сила D = 1/F (дптр), 3 характерных луча
- §22: фотоаппарат (d > 2F) и проектор (F < d < 2F) на одном интерактиве
- §23: лупа Γ = 25/F, микроскоп Γ ≈ Γ_об·25/F_ок, телескоп Кеплера Γ = F_об/F_ок
- 6 квизов (I8-I10 CALC/TH) + 3 босса (b8-b10)
- Финал главы 3: 5 интегрированных боссов (fb1-fb5), +200 XP бонус, ачивка ch3_master
- checkFinalDone() — авто-проверка победы над всеми 5 боссами
- Глава 3 полностью завершена (10 параграфов + финал)
This commit is contained in:
Maxim Dolgolyov
2026-05-29 18:48:16 +03:00
parent 7a703bd184
commit 06db392f6a
2 changed files with 644 additions and 17 deletions
+211
View File
@@ -1329,4 +1329,215 @@ class PrismSpectrum {
}
P.PrismSpectrum = PrismSpectrum;
/* ============================================================ */
/* ThinLens — тонкая линза (собирающая / рассеивающая) */
/* ============================================================ */
class ThinLens {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 640;
this.H = opts.height || 300;
this.F = opts.F !== undefined ? opts.F : 70; /* фокусное (px) */
this.d = opts.d !== undefined ? opts.d : 160; /* расст. до объекта (px) */
this.objH = opts.objH !== undefined ? opts.objH : 50;
this.mode = opts.mode || 'converging'; /* 'converging' | 'diverging' */
this.color = opts.color || '#d97706';
this.paused = true;
this.render();
}
setF(v){ this.F = Math.max(20, v); this.render(); }
setD(v){ this.d = Math.max(20, v); this.render(); }
setMode(m){ this.mode = m; this.render(); }
update(){}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const cy = H / 2 + 10;
const lensX = W / 2;
const Fsign = (this.mode === 'converging') ? this.F : -this.F;
const d = this.d;
/* 1/d + 1/f = 1/F → f = 1/(1/F - 1/d) */
let f;
if (Math.abs(1/Fsign - 1/d) < 1e-6) f = 1e9;
else f = 1 / (1/Fsign - 1/d);
/* По соглашению: f > 0 справа (действительное), f < 0 слева (мнимое) */
const imgX = lensX + f;
const G = -f / d;
const imgH = this.objH * G;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Главная оптическая ось */
svg += '<line x1="20" y1="' + cy + '" x2="' + (W - 10) + '" y2="' + cy + '" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4 3"/>';
/* Линза */
if (this.mode === 'converging'){
svg += '<line x1="' + lensX + '" y1="40" x2="' + lensX + '" y2="' + (H - 40) + '" stroke="#0f172a" stroke-width="3"/>';
svg += '<polygon points="' + lensX + ',32 ' + (lensX - 7) + ',46 ' + (lensX + 7) + ',46" fill="#0f172a"/>';
svg += '<polygon points="' + lensX + ',' + (H - 32) + ' ' + (lensX - 7) + ',' + (H - 46) + ' ' + (lensX + 7) + ',' + (H - 46) + '" fill="#0f172a"/>';
} else {
svg += '<line x1="' + lensX + '" y1="40" x2="' + lensX + '" y2="' + (H - 40) + '" stroke="#0f172a" stroke-width="3" stroke-dasharray="2 2"/>';
svg += '<polygon points="' + (lensX - 8) + ',32 ' + lensX + ',46 ' + (lensX + 8) + ',32" fill="#0f172a"/>';
svg += '<polygon points="' + (lensX - 8) + ',' + (H - 32) + ' ' + lensX + ',' + (H - 46) + ' ' + (lensX + 8) + ',' + (H - 32) + '" fill="#0f172a"/>';
}
/* Фокусы F и 2F */
const Fleft = lensX - this.F, Fright = lensX + this.F;
svg += '<circle cx="' + Fleft + '" cy="' + cy + '" r="3.5" fill="#dc2626"/><text x="' + Fleft + '" y="' + (cy + 18) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626" font-weight="700">F</text>';
svg += '<circle cx="' + Fright + '" cy="' + cy + '" r="3.5" fill="#dc2626"/><text x="' + Fright + '" y="' + (cy + 18) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626" font-weight="700">F</text>';
if (this.mode === 'converging'){
const F2L = lensX - 2*this.F, F2R = lensX + 2*this.F;
if (F2L > 20) svg += '<circle cx="' + F2L + '" cy="' + cy + '" r="3" fill="#1d4ed8"/><text x="' + F2L + '" y="' + (cy + 18) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#1d4ed8">2F</text>';
if (F2R < W - 10) svg += '<circle cx="' + F2R + '" cy="' + cy + '" r="3" fill="#1d4ed8"/><text x="' + F2R + '" y="' + (cy + 18) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#1d4ed8">2F</text>';
}
/* Объект */
const objX = lensX - d;
const objTopY = cy - this.objH;
svg += '<line x1="' + objX + '" y1="' + cy + '" x2="' + objX + '" y2="' + objTopY + '" stroke="' + this.color + '" stroke-width="3"/>';
svg += '<polygon points="' + objX + ',' + objTopY + ' ' + (objX - 6) + ',' + (objTopY + 10) + ' ' + (objX + 6) + ',' + (objTopY + 10) + '" fill="' + this.color + '"/>';
/* Лучи */
/* Луч 1: параллельный → через F справа (для собирающей); для рассеивающей — как-будто из F слева */
svg += '<line x1="' + objX + '" y1="' + objTopY + '" x2="' + lensX + '" y2="' + objTopY + '" stroke="#16a34a" stroke-width="1.6"/>';
if (this.mode === 'converging'){
const slope = (cy - objTopY) / (Fright - lensX);
const x2 = Math.min(W - 10, imgX > lensX ? imgX : W - 10);
const y2 = objTopY + slope * (x2 - lensX);
svg += '<line x1="' + lensX + '" y1="' + objTopY + '" x2="' + x2.toFixed(1) + '" y2="' + y2.toFixed(1) + '" stroke="#16a34a" stroke-width="1.6"/>';
} else {
/* Рассеивающая: продолжение в сторону F слева */
const slope = (objTopY - cy) / (lensX - Fleft);
svg += '<line x1="' + lensX + '" y1="' + objTopY + '" x2="' + (W - 10) + '" y2="' + (objTopY + slope * (W - 10 - lensX)).toFixed(1) + '" stroke="#16a34a" stroke-width="1.6"/>';
svg += '<line x1="' + lensX + '" y1="' + objTopY + '" x2="' + Fleft + '" y2="' + cy + '" stroke="#16a34a" stroke-width="1" stroke-dasharray="3 3"/>';
}
/* Луч 2: через оптический центр O (без преломления) */
const slope2 = (cy - objTopY) / (lensX - objX);
const ex = Math.min(W - 10, imgX > lensX ? imgX : W - 10);
const ey = objTopY + slope2 * (ex - objX);
svg += '<line x1="' + objX + '" y1="' + objTopY + '" x2="' + ex.toFixed(1) + '" y2="' + ey.toFixed(1) + '" stroke="#1d4ed8" stroke-width="1.6"/>';
/* Изображение */
if (isFinite(imgH) && Math.abs(imgX) < 1e7){
const imgTopY = cy - imgH;
const isVirtual = (this.mode === 'diverging') || (f < 0);
const sd = isVirtual ? ' stroke-dasharray="4 3"' : '';
const op = isVirtual ? 0.7 : 1.0;
const fillCol = '#7c2d12';
if (imgX > 20 && imgX < W - 10){
svg += '<line x1="' + imgX.toFixed(1) + '" y1="' + cy + '" x2="' + imgX.toFixed(1) + '" y2="' + imgTopY.toFixed(1) + '" stroke="' + fillCol + '" stroke-width="2.6"' + sd + ' opacity="' + op + '"/>';
svg += '<polygon points="' + imgX.toFixed(1) + ',' + imgTopY.toFixed(1) + ' ' + (imgX - 5).toFixed(1) + ',' + (imgTopY + (imgH > 0 ? 10 : -10)).toFixed(1) + ' ' + (imgX + 5).toFixed(1) + ',' + (imgTopY + (imgH > 0 ? 10 : -10)).toFixed(1) + '" fill="' + fillCol + '" opacity="' + op + '"/>';
}
}
/* Подписи */
const Glabel = isFinite(G) ? G.toFixed(2) : '—';
const fLabel = (Math.abs(f) < 1e7) ? f.toFixed(0) : '∞';
svg += '<text x="' + (W/2) + '" y="22" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">1/d + 1/f = 1/F · Γ = -f/d = ' + Glabel + ' · f = ' + fLabel + 'px</text>';
svg += '<text x="' + (W/2) + '" y="' + (H - 8) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#64748b">' + (this.mode==='converging'?'собирающая':'рассеивающая') + ' линза · F=' + this.F + 'px · d=' + d + 'px</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.ThinLens = ThinLens;
/* ============================================================ */
/* TwoLensSystem — две линзы (микроскоп / телескоп) */
/* ============================================================ */
class TwoLensSystem {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 700;
this.H = opts.height || 280;
this.F1 = opts.F1 !== undefined ? opts.F1 : 50; /* объектив */
this.F2 = opts.F2 !== undefined ? opts.F2 : 90; /* окуляр */
this.L = opts.L !== undefined ? opts.L : 280; /* расстояние между линзами */
this.mode = opts.mode || 'telescope'; /* 'telescope' | 'microscope' */
this.paused = true;
this.render();
}
setMode(m){ this.mode = m; this.render(); }
setF1(v){ this.F1 = Math.max(20, v); this.render(); }
setF2(v){ this.F2 = Math.max(20, v); this.render(); }
setL(v){ this.L = Math.max(this.F1 + this.F2 + 20, v); this.render(); }
update(){}
drawLens(svg, x, cy, label, F){
let s = svg;
s += '<line x1="' + x + '" y1="' + (cy - 80) + '" x2="' + x + '" y2="' + (cy + 80) + '" stroke="#0f172a" stroke-width="2.6"/>';
s += '<polygon points="' + x + ',' + (cy - 88) + ' ' + (x - 6) + ',' + (cy - 76) + ' ' + (x + 6) + ',' + (cy - 76) + '" fill="#0f172a"/>';
s += '<polygon points="' + x + ',' + (cy + 88) + ' ' + (x - 6) + ',' + (cy + 76) + ' ' + (x + 6) + ',' + (cy + 76) + '" fill="#0f172a"/>';
s += '<text x="' + x + '" y="' + (cy + 102) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a" font-weight="700">' + label + '</text>';
s += '<circle cx="' + (x - F) + '" cy="' + cy + '" r="2.5" fill="#dc2626"/>';
s += '<circle cx="' + (x + F) + '" cy="' + cy + '" r="2.5" fill="#dc2626"/>';
return s;
}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const cy = H / 2 + 10;
const x1 = 130; /* объектив */
const x2 = x1 + this.L; /* окуляр */
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Ось */
svg += '<line x1="20" y1="' + cy + '" x2="' + (W - 10) + '" y2="' + cy + '" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4 3"/>';
/* Линзы */
svg = this.drawLens(svg, x1, cy, 'объектив (F₁=' + this.F1 + ')', this.F1);
svg = this.drawLens(svg, x2, cy, 'окуляр (F₂=' + this.F2 + ')', this.F2);
/* Сценарий */
if (this.mode === 'telescope'){
/* Объект «на бесконечности» — пучок параллельных лучей входит слева */
const angle = -0.06; /* небольшой угол */
for (let i = -2; i <= 2; i++){
const yIn = cy + i * 18;
/* Луч входит горизонтально (или под малым углом α₁) */
const xIn = 25;
svg += '<line x1="' + xIn + '" y1="' + yIn + '" x2="' + x1 + '" y2="' + yIn + '" stroke="#16a34a" stroke-width="1.4"/>';
/* После объектива собирается в фокальной плоскости (x1 + F1) */
const focusX = x1 + this.F1;
const focusY = cy; /* для параллельного пучка вдоль оси — на оси */
svg += '<line x1="' + x1 + '" y1="' + yIn + '" x2="' + focusX + '" y2="' + focusY + '" stroke="#16a34a" stroke-width="1.4"/>';
/* От фокуса (F2 справа от окуляра у телескопа: совмещён с F1 объектива) дальше параллельно */
/* Лучи проходят через окуляр */
const yAtEye = focusY + (cy + i * 12 - focusY); /* выходные углы немного шире */
svg += '<line x1="' + focusX + '" y1="' + focusY + '" x2="' + x2 + '" y2="' + (cy + i * 8) + '" stroke="#16a34a" stroke-width="1.2" stroke-dasharray="3 3"/>';
/* После окуляра — параллельно (изображение в бесконечности) */
svg += '<line x1="' + x2 + '" y1="' + (cy + i * 8) + '" x2="' + (W - 20) + '" y2="' + (cy + i * 14) + '" stroke="#16a34a" stroke-width="1.4"/>';
}
svg += '<text x="60" y="40" font-family="JetBrains Mono,monospace" font-size="11" fill="#1d4ed8" font-weight="700">пучок от удалённого объекта</text>';
svg += '<text x="' + (W - 20) + '" y="40" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#1d4ed8" font-weight="700">в глаз наблюдателя</text>';
svg += '<text x="' + (W/2) + '" y="' + (H - 10) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569">телескоп Кеплера · Γ = F₁/F₂ = ' + (this.F1/this.F2).toFixed(2) + '</text>';
} else {
/* Микроскоп: объект чуть дальше F1 от объектива */
const d1 = this.F1 + 12;
const objX = x1 - d1;
const objH = 40;
/* 1/d + 1/f = 1/F */
const f1 = 1 / (1/this.F1 - 1/d1);
const G1 = -f1 / d1;
const h1 = objH * G1;
const img1X = x1 + f1;
/* Изображение объектива должно лежать чуть ближе F2 от окуляра, чтобы окуляр работал как лупа */
/* Объект */
svg += '<line x1="' + objX + '" y1="' + cy + '" x2="' + objX + '" y2="' + (cy - objH) + '" stroke="#d97706" stroke-width="3"/>';
svg += '<polygon points="' + objX + ',' + (cy - objH) + ' ' + (objX - 5) + ',' + (cy - objH + 9) + ' ' + (objX + 5) + ',' + (cy - objH + 9) + '" fill="#d97706"/>';
/* Промежуточное изображение */
if (img1X > x1 && img1X < x2){
svg += '<line x1="' + img1X.toFixed(1) + '" y1="' + cy + '" x2="' + img1X.toFixed(1) + '" y2="' + (cy - h1).toFixed(1) + '" stroke="#7c2d12" stroke-width="2.4"/>';
svg += '<polygon points="' + img1X.toFixed(1) + ',' + (cy - h1).toFixed(1) + ' ' + (img1X - 4).toFixed(1) + ',' + (cy - h1 + (h1>0?9:-9)).toFixed(1) + ' ' + (img1X + 4).toFixed(1) + ',' + (cy - h1 + (h1>0?9:-9)).toFixed(1) + '" fill="#7c2d12"/>';
svg += '<text x="' + img1X.toFixed(1) + '" y="' + (cy + 14) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="9" fill="#7c2d12">A₁B₁</text>';
}
/* Лучи от верха объекта через объектив (2 канонических) */
svg += '<line x1="' + objX + '" y1="' + (cy - objH) + '" x2="' + x1 + '" y2="' + (cy - objH) + '" stroke="#16a34a" stroke-width="1.4"/>';
svg += '<line x1="' + x1 + '" y1="' + (cy - objH) + '" x2="' + img1X.toFixed(1) + '" y2="' + (cy - h1).toFixed(1) + '" stroke="#16a34a" stroke-width="1.4"/>';
/* Через оптический центр объектива */
svg += '<line x1="' + objX + '" y1="' + (cy - objH) + '" x2="' + img1X.toFixed(1) + '" y2="' + (cy - h1).toFixed(1) + '" stroke="#1d4ed8" stroke-width="1.4"/>';
/* Окуляр работает как лупа — даёт мнимое увеличенное (за окуляром слева) */
/* Показ лучей от верха промежуточного изображения через окуляр параллельно (в глаз) */
svg += '<line x1="' + img1X.toFixed(1) + '" y1="' + (cy - h1).toFixed(1) + '" x2="' + x2 + '" y2="' + (cy - h1).toFixed(1) + '" stroke="#16a34a" stroke-width="1.4"/>';
svg += '<line x1="' + x2 + '" y1="' + (cy - h1).toFixed(1) + '" x2="' + (W - 20) + '" y2="' + (cy - h1 * 1.6).toFixed(1) + '" stroke="#16a34a" stroke-width="1.4"/>';
svg += '<text x="' + (W - 20) + '" y="40" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#1d4ed8" font-weight="700">в глаз</text>';
const Gtotal = G1 * (25 / this.F2); /* приближённо: |Γ_мкс| ≈ |Γ_объ| · 25 см/F_ок */
svg += '<text x="' + (W/2) + '" y="' + (H - 10) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569">микроскоп · Γ ≈ Γ_объ · 25 см / F_ок ≈ ' + Gtotal.toFixed(1) + '</text>';
}
this.el.innerHTML = svg;
}
}
P.TwoLensSystem = TwoLensSystem;
})();
+433 -17
View File
@@ -232,10 +232,10 @@ const PARAS = [
{ id:'p5', num:'§ 18', name:'Сферические зеркала', sub:'$1/d + 1/f = 1/F$', built:true },
{ id:'p6', num:'§ 19', name:'Преломление', sub:'$n_1\\sin\\alpha = n_2\\sin\\beta$', built:true },
{ id:'p7', num:'§ 20', name:'Опт. элементы', sub:'Призма, оптоволокно', built:true },
{ id:'p8', num:'§ 21', name:'Тонкая линза', sub:'Будет в W7', built:false },
{ id:'p9', num:'§ 22', name:'Действ. изображения', sub:'Будет в W7', built:false },
{ id:'p10', num:'§ 23', name:'Угол зрения', sub:'Будет в W7', built:false },
{ id:'final', num:'★', name:'Финал главы', sub:'Будет в W7', final:true, built:false }
{ id:'p8', num:'§ 21', name:'Тонкая линза', sub:'$1/d + 1/f = 1/F$', built:true },
{ id:'p9', num:'§ 22', name:'Действ. изображения', sub:'Фотоаппарат, проектор', built:true },
{ id:'p10', num:'§ 23', name:'Угол зрения', sub:'Лупа, микроскоп, телескоп', built:true },
{ id:'final', num:'★', name:'Финал главы 3', sub:'Все 10 параграфов', final:true, built:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
@@ -250,6 +250,10 @@ const ACH_LABELS = {
p5_done:'§18 — сферические зеркала освоены',
p6_done:'§19 — закон Снелла освоен',
p7_done:'§20 — призма и оптоволокно освоены',
p8_done:'§21 — тонкая линза освоена',
p9_done:'§22 — фотоаппарат и проектор освоены',
p10_done:'§23 — оптические приборы освоены',
ch3_master:'Магистр оптики — пройден финал главы 3!',
start:'Начало главы 3!',
ch3_done:'Глава 3 пройдена — Оптика!'
};
@@ -333,10 +337,8 @@ const BUILT=new Set();
const BUILDERS = {
p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(),
p5:()=>buildP5(), p6:()=>buildP6(), p7:()=>buildP7(),
p8:()=>buildStubP('p8','§ 21','§21 в разработке (W7) — тонкая линза, формула $1/F = 1/d + 1/f$, оптическая сила $D = 1/F$.'),
p9:()=>buildStubP('p9','§ 22','§22 в разработке (W7) — фотоаппарат, проектор.'),
p10:()=>buildStubP('p10','§ 23','§23 в разработке (W7) — лупа, микроскоп, телескоп.'),
final:()=>buildStubP('final','Финал','Финал главы 3 — в волне W7 (5 интегр. боссов + ачивка ch3_done).')
p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildP10(),
final:()=>buildFinal()
};
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
@@ -359,10 +361,10 @@ const SIDEBARS = {
p5:{title:'§ 18 — Сферические зеркала', rows:[['Формула','$\\dfrac{1}{d}+\\dfrac{1}{f}=\\dfrac{1}{F}$'],['Фокус','$F = R/2$'],['Увелич.','$\\Gamma = -f/d$']]},
p6:{title:'§ 19 — Преломление', rows:[['Закон','$n_1\\sin\\alpha = n_2\\sin\\beta$'],['Пок. прелом.','$n = c/v$'],['ПВО','$\\sin\\alpha_{кр}=n_2/n_1$ (если $n_1>n_2$)']]},
p7:{title:'§ 20 — Опт. элементы', rows:[['Призма','дисперсия $n(\\lambda)$'],['Пластинка','параллельный сдвиг'],['Оптоволокно','полное внутр. отражение']]},
p8:{title:'§ 21', rows:[['Тема','Тонкая линза'],['Статус','В разработке (W7)']]},
p9:{title:'§ 22', rows:[['Тема','Действ. изобр.'],['Статус','В разработке (W7)']]},
p10:{title:'§ 23', rows:[['Тема','Угол зрения'],['Статус','В разработке (W7)']]},
final:{title:'Финал главы 3', rows:[['Статус','В разработке (W7)'],['Боссов','5 интегрированных'],['Награда','+150 XP + ачивка']]}
p8:{title:'§ 21 — Тонкая линза', rows:[['Формула','$\\dfrac{1}{d}+\\dfrac{1}{f}=\\dfrac{1}{F}$'],['Опт. сила','$D = 1/F$ (дптр)'],['Увелич.','$\\Gamma = -f/d$']]},
p9:{title:'§ 22 — Фотоаппарат, проектор', rows:[['Фото','$d > 2F$, изобр. уменьш. перевёрнутое'],['Проектор','$F < d < 2F$, увелич. перевёрнутое']]},
p10:{title:'§ 23 — Оптические приборы', rows:[['Лупа','$\\Gamma = 25\\text{см}/F$'],['Микроскоп','$\\Gamma \\approx \\Gamma_{об} \\cdot 25\\text{см}/F_{ок}$'],['Телескоп','$\\Gamma = F_{об}/F_{ок}$']]},
final:{title:'Финал главы 3 — Оптика', rows:[['Боссов','5 интегрированных'],['Покрытие','§14-§23 (10 параграфов)'],['Награда','+200 XP + Магистр оптики']]}
};
const TIPS=[
@@ -373,10 +375,10 @@ const TIPS=[
{sec:'p5',html:'§ 18 — фокус сферического зеркала $F = R/2$. Формула $1/d + 1/f = 1/F$ работает для любого зеркала. Если $f<0$ — изображение мнимое (за зеркалом).'},
{sec:'p6',html:'§ 19 — закон Снелла $n_1\\sin\\alpha = n_2\\sin\\beta$. Если идём из плотной среды в менее плотную и $\\alpha > \\alpha_{кр}$ — полное внутреннее отражение.'},
{sec:'p7',html:'§ 20 — призма разлагает белый свет в спектр, потому что $n(\\lambda)$ для разных $\\lambda$ разный. Оптоволокно работает за счёт ПВО.'},
{sec:'p8',html:'§ 21 — в разработке (W7).'},
{sec:'p9',html:'§ 22 — в разработке (W7).'},
{sec:'p10',html:'§ 23 — в разработке (W7).'},
{sec:'final',html:'Финал главы 3 — в разработке (W7).'}
{sec:'p8',html:'§ 21 — тонкая линза. Если $f>0$ — действительное изображение; если $f<0$ — мнимое. Оптическая сила $D=1/F$ измеряется в дптр (м⁻¹).'},
{sec:'p9',html:'§ 22 — фотоаппарат: $d > 2F$ ⇒ уменьшенное действительное. Проектор: $F < d < 2F$ ⇒ увеличенное действительное.'},
{sec:'p10',html:'§ 23 — лупа = одна линза, $\\Gamma = 25/F$ см. Микроскоп = двойная система. Телескоп: чем больше $F_{об}/F_{ок}$, тем больше увеличение.'},
{sec:'final',html:'Финал главы 3: 5 интегрированных боссов покрывают весь материал §14–§23.'}
];
function buildSidebar(id){
@@ -1004,7 +1006,314 @@ function buildP7(){
renderMath(box);
}
/* ===== Stubs §21-§23 + Final ===== */
/* ===== §21 Тонкая линза ===== */
function buildP8(){
const box = document.getElementById('p8-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Тонкая линза — виды и характеристики', '§ 21.1',
'<p><b>Линза</b> — прозрачное тело, ограниченное двумя сферическими (или одной сферической и плоской) поверхностями.</p>'
+ '<p>«<b>Тонкая</b>» — толщина мала по сравнению с радиусами кривизны поверхностей.</p>'
+ '<p>Различают:</p>'
+ '<ul>'
+ '<li><b>Собирающие</b> (выпуклые) — действуют как «солнечные стеклы», $F > 0$.</li>'
+ '<li><b>Рассеивающие</b> (вогнутые) — расходящие линзы, $F < 0$.</li>'
+ '</ul>'
+ '<p>Основные точки: <b>оптический центр</b> $O$, два <b>фокуса</b> $F_1, F_2$ (симметричны относительно $O$), <b>главная оптическая ось</b>.</p>'
+ '<p><b>Оптическая сила</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$D = \\dfrac{1}{F}, \\quad [D] = \\text{дптр} = \\text{м}^{-1}$$</p>'
+ '<p>Положительная для собирающей, отрицательная для рассеивающей.</p>');
html += makeCard('rule', 'Формула линзы и увеличение', '§ 21.2',
'<p><b>Формула тонкой линзы</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\dfrac{1}{d} + \\dfrac{1}{f} = \\dfrac{1}{F}$$</p>'
+ '<p>где $d$ — расстояние до объекта, $f$ — до изображения, $F$ — фокусное.</p>'
+ '<p><b>Линейное увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{h_{изобр}}{h_{объект}} = -\\dfrac{f}{d}$$</p>'
+ '<p><b>Знаки:</b> $f > 0$ — изображение действительное (за линзой); $f < 0$ — мнимое (с той же стороны, что и объект). $\\Gamma < 0$ — перевёрнутое, $\\Gamma > 0$ — прямое.</p>');
html += makeCard('algo', 'Построение изображения — характерные лучи', '§ 21.3',
'<p>От верха объекта проводят два из трёх лучей:</p>'
+ '<ol>'
+ '<li><b>Параллельный главной оси</b> — после линзы идёт через дальний фокус $F$.</li>'
+ '<li><b>Через оптический центр</b> $O$ — проходит без преломления (прямо).</li>'
+ '<li><b>Через ближний фокус</b> — после линзы выходит параллельно оси.</li>'
+ '</ol>'
+ '<p>Пересечение лучей — изображение верха объекта. Достаточно двух любых.</p>'
+ '<p>Для <b>рассеивающей линзы</b>: лучи расходятся; изображение строится продолжением расходящихся лучей назад (всегда мнимое, прямое, уменьшенное).</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">Тонкая линза — построение</span></div>'
+ '<div class="wg-help">Меняй $F$ и $d$, переключай тип линзы. Зелёный — параллельный луч, синий — через центр $O$. Коричневая стрелка — изображение (пунктир = мнимое).</div>'
+ '<div class="fx-holder" id="fx-lens"></div>'
+ '<div class="fx-sliders" id="fx-lens-sl"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Формула линзы — расчёты</span></div>'
+ '<div class="wg-help">$1/d + 1/f = 1/F$. Решено: <b id="i8-calc-score">0</b> / 5.</div>'
+ '<div id="i8-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i8-calc-inp" placeholder="ответ"><button class="btn primary" id="i8-calc-go">Проверить</button></div><div class="feedback" id="i8-calc-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Теория линз</span></div>'
+ '<div class="wg-help">Решено: <b id="i8-th-score">0</b> / 5.</div>'
+ '<div id="i8-th-q" style="margin:8px 0"></div><div class="opts-row" id="i8-th-opts"></div><div class="feedback" id="i8-th-fb"></div></div>';
html += '<div id="boss-8-slot"></div>';
html += readButton('p8');
html += secNavFor('p8');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-lens');
const l = new PHYS.ThinLens(el, {width:640, height:300, F:70, d:160, mode:'converging'});
const slBox = document.getElementById('fx-lens-sl');
const slMode = '<label style="display:flex;align-items:center;gap:8px;font-size:.82rem;color:#475569;font-weight:600;margin:4px 8px">'
+ '<span style="min-width:90px">Тип линзы</span>'
+ '<select id="fx-lens-mode" style="flex:1;padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;font-family:inherit">'
+ '<option value="converging">Собирающая</option><option value="diverging">Рассеивающая</option>'
+ '</select></label>';
const slF = PHYS.util.slider({label:'F (px)', min:30, max:140, step:5, value:70, fmt:v=>v.toFixed(0), onChange:v=>l.setF(v)});
const slD = PHYS.util.slider({label:'d (px)', min:30, max:280, step:10, value:160, fmt:v=>v.toFixed(0), onChange:v=>l.setD(v)});
slBox.innerHTML = slMode + slF.html + slD.html;
document.getElementById('fx-lens-mode').addEventListener('change', e => l.setMode(e.target.value));
slF.wire(slBox); slD.wire(slBox);
});
runQuizInput('i8-calc', I8_CALC_ITEMS, 18);
runQuizMC('i8-th', I8_TH_ITEMS, 14);
const bs = loadBossState('boss-8') || { stage:0, solved:false };
makeAndBindBoss('boss-8-slot', '8', BOSS_DEFS.b8, bs,
()=>saveBossState('boss-8', bs),
()=>{ bumpProgress('p8', 40); achievement('p8_done'); });
wireReadBtn('p8');
renderMath(box);
}
/* ===== §22 Фотоаппарат, проектор ===== */
function buildP9(){
const box = document.getElementById('p9-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Действительные изображения', '§ 22.1',
'<p>Если объект находится <b>дальше фокуса</b> ($d > F$), собирающая линза даёт <b>действительное</b> изображение — лучи реально пересекаются и его можно поймать на экран.</p>'
+ '<p>Возможны три случая:</p>'
+ '<ul>'
+ '<li><b>$d > 2F$</b> — изображение действительное, перевёрнутое, <b>уменьшенное</b> ($|\\Gamma| < 1$), находится между $F$ и $2F$ с другой стороны.</li>'
+ '<li><b>$d = 2F$</b> — действительное, перевёрнутое, <b>равное</b> ($|\\Gamma| = 1$), $f = 2F$.</li>'
+ '<li><b>$F < d < 2F$</b> — действительное, перевёрнутое, <b>увеличенное</b> ($|\\Gamma| > 1$), $f > 2F$.</li>'
+ '</ul>');
html += makeCard('example', 'Фотоаппарат', '§ 22.2',
'<p><b>Фотоаппарат</b> создаёт уменьшенное действительное изображение далёких объектов на матрице (или плёнке).</p>'
+ '<ul>'
+ '<li>Объект находится далеко: $d \\gg F$. По формуле $1/f \\approx 1/F \\Rightarrow f \\approx F$.</li>'
+ '<li>Изображение в фокальной плоскости — на матрице.</li>'
+ '<li>Увеличение $|\\Gamma| = F/d \\ll 1$ — изображение уменьшенное.</li>'
+ '<li><b>Фокусировка</b> — изменение расстояния между объективом и матрицей.</li>'
+ '<li><b>Диафрагма</b> регулирует освещённость и глубину резкости.</li>'
+ '</ul>'
+ '<p><b>Угол поля зрения</b> зависит от $F$: длиннофокусные («теле») — узкое поле; короткофокусные («широкоугольные») — большое.</p>');
html += makeCard('example', 'Проектор', '§ 22.3',
'<p><b>Проектор</b> создаёт увеличенное действительное изображение слайда (или экрана LCD) на удалённом экране.</p>'
+ '<ul>'
+ '<li>Объект (слайд) располагается чуть дальше $F$: $F < d < 2F$.</li>'
+ '<li>Изображение получается перевёрнутым, увеличенным — поэтому слайд вставляют <b>«вверх ногами»</b>.</li>'
+ '<li>Увеличение $|\\Gamma| = f/d$, где $f$ — расстояние до экрана.</li>'
+ '<li>Конденсор (короткофокусная собирающая линза) равномерно освещает слайд.</li>'
+ '</ul>'
+ '<p>Принцип проектора используется в кинопроекторах, оверхедах, LCD-проекторах.</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">Сценарии: фотоаппарат vs проектор</span></div>'
+ '<div class="wg-help">Ползунок $d$: при $d > 2F$ — режим «фото» (уменьш.), при $F < d < 2F$ — режим «проектор» (увелич.). Смотри изменения $\\Gamma$ и $f$.</div>'
+ '<div class="fx-holder" id="fx-cam"></div>'
+ '<div class="fx-sliders" id="fx-cam-sl"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Расчёты</span></div>'
+ '<div class="wg-help">Решено: <b id="i9-calc-score">0</b> / 5.</div>'
+ '<div id="i9-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i9-calc-inp" placeholder="ответ"><button class="btn primary" id="i9-calc-go">Проверить</button></div><div class="feedback" id="i9-calc-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Узнай прибор</span></div>'
+ '<div class="wg-help">Решено: <b id="i9-th-score">0</b> / 5.</div>'
+ '<div id="i9-th-q" style="margin:8px 0"></div><div class="opts-row" id="i9-th-opts"></div><div class="feedback" id="i9-th-fb"></div></div>';
html += '<div id="boss-9-slot"></div>';
html += readButton('p9');
html += secNavFor('p9');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-cam');
const l = new PHYS.ThinLens(el, {width:640, height:300, F:60, d:200, mode:'converging'});
const slBox = document.getElementById('fx-cam-sl');
const slD = PHYS.util.slider({label:'d (px)', min:80, max:300, step:10, value:200, fmt:v=>v.toFixed(0), onChange:v=>l.setD(v)});
const slF = PHYS.util.slider({label:'F (px)', min:30, max:120, step:5, value:60, fmt:v=>v.toFixed(0), onChange:v=>l.setF(v)});
slBox.innerHTML = slD.html + slF.html;
slD.wire(slBox); slF.wire(slBox);
});
runQuizInput('i9-calc', I9_CALC_ITEMS, 18);
runQuizMC('i9-th', I9_TH_ITEMS, 14);
const bs = loadBossState('boss-9') || { stage:0, solved:false };
makeAndBindBoss('boss-9-slot', '9', BOSS_DEFS.b9, bs,
()=>saveBossState('boss-9', bs),
()=>{ bumpProgress('p9', 40); achievement('p9_done'); });
wireReadBtn('p9');
renderMath(box);
}
/* ===== §23 Угол зрения. Лупа, микроскоп, телескоп ===== */
function buildP10(){
const box = document.getElementById('p10-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Угол зрения и угловое увеличение', '§ 23.1',
'<p>Глаз — оптическая система с переменной аккомодацией. Видимый размер предмета определяется не его абсолютной высотой, а <b>углом зрения</b> $\\varphi$, под которым предмет виден из точки наблюдения.</p>'
+ '<p>Минимальный угол, при котором глаз различает две точки как раздельные, $\\varphi_{мин} \\approx 1\'$ (одна угловая минута).</p>'
+ '<p><b>Оптические приборы</b> позволяют увеличить угол зрения и рассматривать мелкие или далёкие объекты.</p>'
+ '<p><b>Угловое увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{\\varphi}{\\varphi_0}$$</p>'
+ '<p>где $\\varphi$ — угол зрения через прибор, $\\varphi_0$ — без прибора.</p>'
+ '<p><b>Расстояние наилучшего зрения</b> для нормального глаза $L_0 = 25$ см.</p>');
html += makeCard('rule', 'Лупа', '§ 23.2',
'<p><b>Лупа</b> — короткофокусная собирающая линза. Объект помещают <b>между линзой и её фокусом</b>: $d < F$. Тогда $f < 0$ — изображение мнимое, прямое, увеличенное (на расстоянии $L_0 = 25$ см от глаза).</p>'
+ '<p><b>Угловое увеличение лупы</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{25 \\text{ см}}{F}$$</p>'
+ '<p>Чем меньше $F$, тем больше увеличение, но и меньше поле зрения.</p>'
+ '<p>Лупа $F = 5$ см даёт $\\Gamma = 5$ крат.</p>');
html += makeCard('rule', 'Микроскоп', '§ 23.3',
'<p><b>Микроскоп</b> — двухступенчатая система:</p>'
+ '<ul>'
+ '<li><b>Объектив</b> (короткофокусный) даёт <b>действительное увеличенное</b> промежуточное изображение $A_1B_1$ за фокусом окуляра.</li>'
+ '<li><b>Окуляр</b> работает как <b>лупа</b>, рассматривая это промежуточное изображение.</li>'
+ '</ul>'
+ '<p><b>Общее увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma_{мкс} \\approx \\Gamma_{об} \\cdot \\dfrac{25 \\text{ см}}{F_{ок}}$$</p>'
+ '<p>Современные микроскопы достигают увеличений до 1000–2000 крат. Предел разрешения ограничен дифракцией: $\\sim \\lambda/2 \\approx 0{,}2$ мкм.</p>');
html += makeCard('rule', 'Телескоп (Кеплера)', '§ 23.4',
'<p><b>Телескоп Кеплера</b> — двухступенчатая система для рассматривания <b>удалённых</b> объектов.</p>'
+ '<ul>'
+ '<li>Лучи от далёкого объекта идут <b>параллельно</b>. Объектив даёт изображение $A_1B_1$ в своей фокальной плоскости.</li>'
+ '<li>Окуляр расположен так, что его передняя фокальная плоскость совпадает с задней объектива: $L = F_{об} + F_{ок}$.</li>'
+ '<li>Окуляр превращает изображение в <b>параллельный пучок</b> — глаз воспринимает как «бесконечно далёкое».</li>'
+ '</ul>'
+ '<p><b>Угловое увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{F_{об}}{F_{ок}}$$</p>'
+ '<p>Чем больше $F_{об}$, тем больше увеличение. Изображение перевёрнутое — астрономов это не смущает.</p>'
+ '<p>В <b>зрительной трубе</b> (Галилея) окуляр — рассеивающая линза, изображение прямое.</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">Двойная оптическая система</span></div>'
+ '<div class="wg-help">Переключай <b>режим</b>: микроскоп (короткий объектив + лупа-окуляр) или телескоп (длинный объектив + короткий окуляр). Меняй $F_1, F_2$ — наблюдай ход лучей.</div>'
+ '<div class="fx-holder" id="fx-tel"></div>'
+ '<div class="fx-sliders" id="fx-tel-sl"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Расчёты увеличений</span></div>'
+ '<div class="wg-help">Решено: <b id="i10-calc-score">0</b> / 5.</div>'
+ '<div id="i10-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i10-calc-inp" placeholder="ответ"><button class="btn primary" id="i10-calc-go">Проверить</button></div><div class="feedback" id="i10-calc-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Узнай прибор</span></div>'
+ '<div class="wg-help">Решено: <b id="i10-th-score">0</b> / 5.</div>'
+ '<div id="i10-th-q" style="margin:8px 0"></div><div class="opts-row" id="i10-th-opts"></div><div class="feedback" id="i10-th-fb"></div></div>';
html += '<div id="boss-10-slot"></div>';
html += readButton('p10');
html += secNavFor('p10');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-tel');
const t = new PHYS.TwoLensSystem(el, {width:700, height:280, F1:120, F2:40, L:200, mode:'telescope'});
const slBox = document.getElementById('fx-tel-sl');
const slMode = '<label style="display:flex;align-items:center;gap:8px;font-size:.82rem;color:#475569;font-weight:600;margin:4px 8px">'
+ '<span style="min-width:90px">Режим</span>'
+ '<select id="fx-tel-mode" style="flex:1;padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;font-family:inherit">'
+ '<option value="telescope">Телескоп</option><option value="microscope">Микроскоп</option>'
+ '</select></label>';
const slF1 = PHYS.util.slider({label:'F₁ (объектив)', min:30, max:200, step:5, value:120, fmt:v=>v.toFixed(0), onChange:v=>t.setF1(v)});
const slF2 = PHYS.util.slider({label:'F₂ (окуляр)', min:20, max:100, step:5, value:40, fmt:v=>v.toFixed(0), onChange:v=>t.setF2(v)});
slBox.innerHTML = slMode + slF1.html + slF2.html;
document.getElementById('fx-tel-mode').addEventListener('change', e=>{
t.setMode(e.target.value);
if(e.target.value === 'microscope'){ t.setF1(50); t.setF2(80); t.setL(280); }
else { t.setF1(120); t.setF2(40); t.setL(200); }
});
slF1.wire(slBox); slF2.wire(slBox);
});
runQuizInput('i10-calc', I10_CALC_ITEMS, 18);
runQuizMC('i10-th', I10_TH_ITEMS, 14);
const bs = loadBossState('boss-10') || { stage:0, solved:false };
makeAndBindBoss('boss-10-slot', '10', BOSS_DEFS.b10, bs,
()=>saveBossState('boss-10', bs),
()=>{ bumpProgress('p10', 40); achievement('p10_done'); });
wireReadBtn('p10');
renderMath(box);
}
/* ===== Финал главы 3 — 5 интегрированных боссов ===== */
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="card" style="background:linear-gradient(135deg,#fef3c7,#fde68a);border-color:#d97706">'
+ '<div class="card-header"><div class="card-icon rule" style="background:#d97706;color:#fff">'
+ '<svg class="ic" viewBox="0 0 24 24" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>'
+ '</div><div class="card-title">Финал главы 3 — Оптика</div><div class="card-num">★</div></div>'
+ '<div class="card-body">'
+ '<p><b>Поздравляем!</b> Ты добрался до финала главы об оптике. Впереди — <b>5 интегрированных боссов</b>, покрывающих весь материал §14-§23: от природы света и его скорости до сложных оптических приборов.</p>'
+ '<p>Каждый босс — <b>5 этапов</b>. Победив всех пятерых, получишь ачивку <b>Магистр оптики</b> и +200 XP бонус.</p>'
+ '<p style="margin-top:14px;color:#92400e;font-weight:700">Если что-то забыл — пройди ещё раз нужный параграф через карточки слева.</p>'
+ '</div></div>';
for (let i = 1; i <= 5; i++){
html += '<div id="boss-final-'+i+'-slot"></div>';
}
html += '<div class="card" id="final-victory" style="display:none;background:linear-gradient(135deg,#fcd34d,#f59e0b);border-color:#92400e">'
+ '<div class="card-header"><div class="card-icon example" style="background:#92400e;color:#fff">'
+ '<svg class="ic" viewBox="0 0 24 24" stroke-width="2"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>'
+ '</div><div class="card-title">Магистр оптики!</div></div>'
+ '<div class="card-body"><p><b>Все 5 интегральных боссов главы 3 побеждены!</b> Ты разобрался в природе света, интерференции, дифракции, отражении, преломлении, призмах, линзах, фотоаппаратах, проекторах, микроскопах и телескопах.</p>'
+ '<p>Бонус: <b>+200 XP</b>. Впереди — Глава 4 (СТО) и квантовая физика.</p></div></div>';
html += secNavFor('final');
box.innerHTML = html;
for (let i = 1; i <= 5; i++){
const key = 'boss-final-' + i;
const bs = loadBossState(key) || { stage:0, solved:false };
makeAndBindBoss(key+'-slot', 'final-'+i, FINAL_BOSS_DEFS['fb'+i], bs,
()=>{ saveBossState(key, bs); checkFinalDone(); },
()=>{ checkFinalDone(); });
}
renderMath(box);
}
function checkFinalDone(){
let all = true;
for (let i = 1; i <= 5; i++){
const s = loadBossState('boss-final-'+i);
if (!s || !s.solved){ all = false; break; }
}
if (all){
const v = document.getElementById('final-victory'); if (v) v.style.display = '';
bumpProgress('final', 100);
if (!STATE.achievements.has('ch3_master')){
achievement('ch3_master');
addXp(200, 'ch3-master-bonus');
}
if (!STATE.achievements.has('ch3_done')) achievement('ch3_done');
}
}
/* ===== Stubs (для будущих заглушек, если понадобятся) ===== */
function buildStubP(id, label, message){
const box = document.getElementById(id + '-body'); if(!box) return;
let html = '<div class="stub-note"><h3>' + label + ' — в разработке</h3><p>' + message + '</p></div>';
@@ -1206,6 +1515,54 @@ const I7_TH_ITEMS = [
{ q:'В радуге наблюдатель видит красный <b>сверху</b>, фиолетовый снизу потому что:', opts:['Красный преломляется сильнее','Фиолетовый преломляется сильнее','Цвета не отличаются','Капля поглощает синий'], correct:1, explain:'Фиолетовый ($\\lambda<$) преломляется сильнее.' }
];
const I8_CALC_ITEMS = [
{ q:'$F = 0{,}25$ м. $D$ (дптр)?', answer:'4', explain:'$D = 1/F = 1/0{,}25 = 4$ дптр.' },
{ q:'$D = -2$ дптр. $F$ (м)?', answer:['-0.5','-1/2'], explain:'$F = 1/D = -0{,}5$ м (рассеивающая).' },
{ q:'$F = 10$ см, $d = 15$ см. $f$ (см)?', answer:'30', explain:'$1/f = 1/10 - 1/15 = 1/30$.' },
{ q:'$F = 20$ см, $d = 60$ см. $\\Gamma$?', answer:['-0.5','-1/2'], explain:'$f = 30$ см; $\\Gamma = -30/60 = -0{,}5$.' },
{ q:'$F = 15$ см, $d = 10$ см (лупа). $f$ (см)?', answer:['-30','-30см'], explain:'$1/f = 1/15 - 1/10 = -1/30$, мнимое.' }
];
const I8_TH_ITEMS = [
{ q:'Собирающая линза имеет $F$:', opts:['$F < 0$','$F > 0$','$F = 0$','$F = \\infty$'], correct:1, explain:'$F > 0$.' },
{ q:'Рассеивающая линза даёт изображение:', opts:['Действ. перевёрнут.','Мнимое прямое уменьшен.','Действ. увеличен.','Равное'], correct:1, explain:'Всегда мнимое, прямое, уменьшенное.' },
{ q:'Луч через оптический центр:', opts:['Преломляется','Идёт без преломления','Поглощается','Удваивается'], correct:1, explain:'Через $O$ — прямо.' },
{ q:'Оптическая сила измеряется в:', opts:['Дж','Н/м','Дптр (м⁻¹)','Гц'], correct:2, explain:'Дптр = м⁻¹.' },
{ q:'$\\Gamma < 0$ означает:', opts:['Прямое','Перевёрнутое','Без увеличения','Мнимое'], correct:1, explain:'Минус = перевёрнутое.' }
];
const I9_CALC_ITEMS = [
{ q:'$F = 5$ см, $d = 5$ м. $f$ ≈ (см)?', answer:'5', explain:'При $d \\gg F$: $f \\approx F = 5$ см (фото).' },
{ q:'$F = 10$ см, $d = 15$ см (проектор). $\\Gamma$?', answer:'-2', explain:'$f = 30$ см, $\\Gamma = -30/15 = -2$.' },
{ q:'Слайд 5×5 см на проектор. Экран на 5 м. $F = 10$ см. Изображение (см)?', answer:['250','2.5м','250см'], explain:'$\\Gamma \\approx f/d \\approx 500/10 = 50$; изобр. 5×50 = 250 см.' },
{ q:'$d = 2F = 20$ см. $f$ (см)?', answer:'20', explain:'$f = 2F = 20$.' },
{ q:'Фотокамера $F = 50$ мм, объект на 2 м. $f$ (мм)?', answer:['51','51.3','51мм'], explain:'$1/f = 1/50 - 1/2000 \\approx 1/51{,}3$.' }
];
const I9_TH_ITEMS = [
{ q:'В фотоаппарате слайд (объект) находится на расстоянии:', opts:['$d < F$','$F < d < 2F$','$d > 2F$','$d = F$'], correct:2, explain:'$d \\gg F$.' },
{ q:'В проекторе:', opts:['$d > 2F$','$F < d < 2F$','$d < F$','$d = F$'], correct:1, explain:'Чтобы получить увелич. изобр.' },
{ q:'Изображение на матрице фотоаппарата:', opts:['Прямое увелич.','Перевёрнут. уменьш.','Мнимое','Не образуется'], correct:1, explain:'Действ. перевёрн. уменьш.' },
{ q:'Слайды в проекторе вставляют:', opts:['Прямо','Вверх ногами','Боком','Любо'], correct:1, explain:'Изобр. перевёрнутое.' },
{ q:'Чем короче $F$ объектива, тем поле зрения:', opts:['Уже','Шире','Не меняется','Исчезает'], correct:1, explain:'Широкоугольный объектив.' }
];
const I10_CALC_ITEMS = [
{ q:'Лупа $F = 5$ см. $\\Gamma$?', answer:'5', explain:'$\\Gamma = 25/5 = 5$.' },
{ q:'Лупа $\\Gamma = 10$. $F$ (см)?', answer:'2.5', explain:'$F = 25/10 = 2{,}5$ см.' },
{ q:'Микроскоп: $\\Gamma_{об} = 40$, $F_{ок} = 2{,}5$ см. $\\Gamma_{мкс}$?', answer:'400', explain:'$40 \\cdot 25/2{,}5 = 400$.' },
{ q:'Телескоп $F_{об} = 1$ м, $F_{ок} = 2$ см. $\\Gamma$?', answer:'50', explain:'$\\Gamma = 100/2 = 50$.' },
{ q:'Расстояние между линзами телескопа: $F_{об}=80$, $F_{ок}=20$ (мм). $L$ (мм)?', answer:'100', explain:'$L = F_{об} + F_{ок} = 100$ мм.' }
];
const I10_TH_ITEMS = [
{ q:'Угол зрения — это:', opts:['Размер объекта','Угол под которым виден объект','Угол падения','Угол отражения'], correct:1, explain:'Углов. размер.' },
{ q:'Расстояние наилучшего зрения:', opts:['10 см','25 см','50 см','1 м'], correct:1, explain:'$L_0 = 25$ см.' },
{ q:'Лупа использует:', opts:['Рассеивающую линзу','Короткофокусную собирающую','Зеркало','Призму'], correct:1, explain:'Собирающая, $d < F$.' },
{ q:'В телескопе Кеплера окуляр:', opts:['Собирающая','Рассеивающая','Зеркало','Без линзы'], correct:0, explain:'Собирающая.' },
{ q:'Изображение в астрономич. телескопе:', opts:['Прямое','Перевёрнутое','Боком'], correct:1, explain:'Перевёрнутое.' }
];
/* ===== Boss defs ===== */
const BOSS_DEFS = {
b1: { title:'Босс §14 — Скорость света', tag:'§14', xp:65, stages:[
@@ -1256,6 +1613,65 @@ const BOSS_DEFS = {
{ q:'$n_{серд}=1{,}5$, $n_{обол}=1{,}4$. $\\sin\\alpha_{кр}$?', type:'input', a:['0.933','14/15','0.93'], explain:'$1{,}4/1{,}5 \\approx 0{,}933$.' },
{ q:'Плоскопараллельная пластинка ... луч:', type:'mc', opts:['Поворачивает на 90°','Смещает параллельно','Поглощает','Разлагает в спектр'], correct:1, explain:'Параллельное смещение.' },
{ q:'Оптоволокно использует:', type:'mc', opts:['Поляризацию','Полное внутр. отражение','Дифракцию','Радугу'], correct:1, explain:'ПВО в сердцевине.' }
]},
b8: { title:'Босс §21 — Тонкая линза', tag:'§21', xp:80, stages:[
{ q:'Формула тонкой линзы:', type:'mc', opts:['$1/d + 1/f = 1/F$','$d + f = F$','$d \\cdot f = F$','$d - f = F$'], correct:0, explain:'Формула линзы.' },
{ q:'$F = 0{,}5$ м. $D$ (дптр)?', type:'input', a:'2', explain:'$1/0{,}5 = 2$.' },
{ q:'$D = -4$ дптр — линза:', type:'mc', opts:['Собирающая','Рассеивающая','Плоская','Не существует'], correct:1, explain:'$D < 0$ — рассеивающая.' },
{ q:'$F=20$ см, $d=30$ см. $f$ (см)?', type:'input', a:'60', explain:'$1/f = 1/20 - 1/30 = 1/60$.' },
{ q:'Луч через оптический центр $O$:', type:'mc', opts:['Преломляется','Идёт без преломления','Отражается','Поглощается'], correct:1, explain:'Через $O$ прямо.' }
]},
b9: { title:'Босс §22 — Фотоаппарат, проектор', tag:'§22', xp:80, stages:[
{ q:'В фотокамере объект на расстоянии:', type:'mc', opts:['$d < F$','$F < d < 2F$','$d > 2F$','$d = F$'], correct:2, explain:'Далеко.' },
{ q:'$F = 50$ мм, $d = \\infty$. $f \\approx$ (мм)?', type:'input', a:'50', explain:'$f \\approx F$.' },
{ q:'В проекторе слайд вставляют:', type:'mc', opts:['Прямо','Вверх ногами','Боком'], correct:1, explain:'Изобр. перевёрнутое.' },
{ q:'$d = 2F$ — изображение:', type:'mc', opts:['Уменьшенное','Равное','Увеличенное','Мнимое'], correct:1, explain:'$f = 2F$, $|\\Gamma| = 1$.' },
{ q:'$F=10$ см, $d=12$ см. $|\\Gamma|$?', type:'input', a:['5','5.0'], explain:'$f = 60$, $\\Gamma = -5$.' }
]},
b10: { title:'Босс §23 — Оптические приборы', tag:'§23', xp:80, stages:[
{ q:'Лупа $F = 5$ см. $\\Gamma$?', type:'input', a:'5', explain:'$25/5 = 5$.' },
{ q:'Телескоп: $\\Gamma = ?$', type:'mc', opts:['$F_{ок}/F_{об}$','$F_{об}/F_{ок}$','$F_{об} \\cdot F_{ок}$','$F_{об}+F_{ок}$'], correct:1, explain:'$\\Gamma = F_{об}/F_{ок}$.' },
{ q:'Микроскоп: $\\Gamma_{об} = 20$, $F_{ок} = 5$ см. $\\Gamma_{мкс}$?', type:'input', a:'100', explain:'$20 \\cdot 25/5 = 100$.' },
{ q:'Расстояние наилучшего зрения (см)?', type:'input', a:'25', explain:'$L_0 = 25$ см.' },
{ q:'В телескопе Кеплера расстояние между линзами:', type:'mc', opts:['$F_{об} - F_{ок}$','$F_{об} + F_{ок}$','$F_{об} \\cdot F_{ок}$','любое'], correct:1, explain:'Фокусы совпадают.' }
]}
};
const FINAL_BOSS_DEFS = {
fb1: { title:'Финал §14–§16 — Свет и его свойства', tag:'Финал I', xp:50, stages:[
{ q:'$c$ в вакууме (м/с)?', type:'input', a:['3e8','3·10⁸','300000000','3*10^8'], explain:'$3 \\cdot 10^8$.' },
{ q:'Условие max интерференции:', type:'mc', opts:['$\\Delta = k\\lambda$','$\\Delta = (2k+1)\\lambda/2$','$\\Delta = 0$ всегда','$\\Delta = \\lambda^2$'], correct:0, explain:'$\\Delta = k\\lambda$.' },
{ q:'$d = 2$ мкм, $\\lambda = 500$ нм, $k = 1$. $\\sin\\varphi$?', type:'input', a:'0.25', explain:'$500/2000 = 0{,}25$.' },
{ q:'Дифракция — это:', type:'mc', opts:['Отражение','Огибание препятствий','Преломление','Поглощение'], correct:1, explain:'Огибание препятствий.' },
{ q:'Свет в стекле $n = 1{,}5$. $v$ (м/с)?', type:'input', a:['2e8','2·10⁸','200000000'], explain:'$v = c/n = 2 \\cdot 10^8$.' }
]},
fb2: { title:'Финал §17–§19 — Зеркала и преломление', tag:'Финал II', xp:50, stages:[
{ q:'Закон отражения:', type:'mc', opts:['$\\angle_п=\\angle_о$','$\\angle_п>\\angle_о$','$\\angle_п<\\angle_о$','Не связаны'], correct:0, explain:'Равны.' },
{ q:'Сферич. зеркало: $R = 40$ см. $F$ (см)?', type:'input', a:'20', explain:'$R/2 = 20$.' },
{ q:'$n_1\\sin\\alpha = ?$', type:'mc', opts:['$n_2\\sin\\beta$','$n_2\\cos\\beta$','$n_2$','$\\sin\\beta$'], correct:0, explain:'Снелл.' },
{ q:'Вода $n=1{,}33$. $\\sin\\alpha_{кр}$?', type:'input', a:['0.75','3/4'], explain:'$1/1{,}33 \\approx 0{,}75$.' },
{ q:'Изображение в плоском зеркале:', type:'mc', opts:['Действ. перевёрнутое','Мнимое прямое равное','Уменьшенное','Увеличенное'], correct:1, explain:'Мнимое прямое равное.' }
]},
fb3: { title:'Финал §20–§21 — Призма, линза', tag:'Финал III', xp:50, stages:[
{ q:'Дисперсия — это:', type:'mc', opts:['Отражение','$n = n(\\lambda)$','Дифракция','Поляризация'], correct:1, explain:'Зависимость $n$ от $\\lambda$.' },
{ q:'Оптоволокно работает на:', type:'mc', opts:['Дисперсии','Интерференции','ПВО','Поляризации'], correct:2, explain:'Полное внутреннее отражение.' },
{ q:'$F = 0{,}25$ м. $D$ (дптр)?', type:'input', a:'4', explain:'$1/F = 4$.' },
{ q:'Рассеивающая линза имеет:', type:'mc', opts:['$F > 0$','$F < 0$','$F = 0$','$F = \\infty$'], correct:1, explain:'Отрицательное $F$.' },
{ q:'$F = 10$ см, $d = 30$ см. $\\Gamma$?', type:'input', a:['-0.5','-1/2'], explain:'$f = 15$, $-15/30$.' }
]},
fb4: { title:'Финал §22 — Фотоаппарат, проектор', tag:'Финал IV', xp:50, stages:[
{ q:'В фотокамере $d$:', type:'mc', opts:['$< F$','$F < d < 2F$','$> 2F$'], correct:2, explain:'Далеко.' },
{ q:'В проекторе $d$:', type:'mc', opts:['$< F$','$F < d < 2F$','$> 2F$'], correct:1, explain:'Между $F$ и $2F$.' },
{ q:'$F = 50$ мм, объект на 1 м. $f$ ≈ (мм)?', type:'input', a:['52.6','53','52'], explain:'$1/f = 1/50 - 1/1000 = 19/1000 \\Rightarrow f \\approx 52{,}6$.' },
{ q:'Изображение в проекторе:', type:'mc', opts:['Прямое','Перевёрнутое','Мнимое','Уменьшенное'], correct:1, explain:'Действ. перевёрн. увеличенное.' },
{ q:'$d = 15$ см, $F = 10$ см. $|\\Gamma|$?', type:'input', a:'2', explain:'$f = 30$, $|\\Gamma| = 2$.' }
]},
fb5: { title:'Финал §23 — Лупа, микроскоп, телескоп', tag:'Финал V', xp:50, stages:[
{ q:'$L_0$ — расстояние наилучшего зрения (см)?', type:'input', a:'25', explain:'25 см.' },
{ q:'Лупа $\\Gamma = 25/F$. $F = 2{,}5$ см. $\\Gamma$?', type:'input', a:'10', explain:'$25/2{,}5 = 10$.' },
{ q:'Телескоп: $\\Gamma = ?$', type:'mc', opts:['$F_{об}/F_{ок}$','$F_{ок}/F_{об}$','$F_{об}+F_{ок}$','$F_{об}\\cdot F_{ок}$'], correct:0, explain:'$\\Gamma = F_{об}/F_{ок}$.' },
{ q:'$F_{об} = 1{,}5$ м, $F_{ок} = 3$ см. $\\Gamma$?', type:'input', a:'50', explain:'$150/3 = 50$.' },
{ q:'Микроскоп: $\\Gamma_{об}=25$, $F_{ок}=5$ см. $\\Gamma$ всего?', type:'input', a:'125', explain:'$25 \\cdot 25/5 = 125$.' }
]}
};