feat(phys11 ch3): Wave 6 — §18-§20 + phys-fx SphericalMirror/RefractionLab/PrismSpectrum

- phys-fx.js: PHYS.SphericalMirror (вогнутые/выпуклые с формулой и 3 лучами), PHYS.RefractionLab (закон Снелла + ПВО), PHYS.PrismSpectrum (дисперсия, 7 цветов через модель Коши)
- §18: сферические зеркала, формула 1/d + 1/f = 1/F, увеличение Γ = -f/d, построение
- §19: показатель преломления n=c/v, закон Снелла, полное внутр. отражение
- §20: призма + дисперсия, плоскопараллельная пластинка, оптоволокно
- 6 квизов (I5-I7 CALC/TH) + 3 босса (b5-b7)
- §21-§23 + Final остаются заглушками для W7
This commit is contained in:
Maxim Dolgolyov
2026-05-29 18:41:51 +03:00
parent a5ffc624cf
commit 27a67d0866
2 changed files with 605 additions and 13 deletions
+296
View File
@@ -1033,4 +1033,300 @@ class FlatMirror {
}
P.FlatMirror = FlatMirror;
/* ============================================================ */
/* SphericalMirror — вогнутое / выпуклое зеркало */
/* ============================================================ */
class SphericalMirror {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 620;
this.H = opts.height || 280;
this.F = opts.F !== undefined ? opts.F : 80; /* фокусное расстояние в px */
this.d = opts.d !== undefined ? opts.d : 180; /* расстояние до объекта в px */
this.objH = opts.objH !== undefined ? opts.objH : 50; /* высота объекта в px */
this.mode = opts.mode || 'concave'; /* 'concave' | 'convex' */
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;
const mirrorX = W - 80; /* зеркало справа */
const F = (this.mode === 'concave') ? this.F : -this.F;
const focusX = mirrorX - F;
const centerX = mirrorX - 2 * F;
const d = this.d;
const objX = mirrorX - d;
/* Формула 1/d + 1/f = 1/F → f = 1/(1/F - 1/d) */
let f;
if (Math.abs(1/F - 1/d) < 1e-6) f = 1e9;
else f = 1 / (1/F - 1/d);
const imgX = mirrorX - f;
/* Линейное увеличение Γ = -f/d (мнимое: f<0 → прямое, действ.: f>0 → перевёрнутое) */
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"/>';
/* Зеркало (дуга) */
const R = 2 * Math.abs(F);
if (this.mode === 'concave'){
svg += '<path d="M ' + mirrorX + ' ' + (cy - 100) + ' Q ' + (mirrorX - 30) + ' ' + cy + ' ' + mirrorX + ' ' + (cy + 100) + '" stroke="#0f172a" stroke-width="3" fill="none"/>';
} else {
svg += '<path d="M ' + mirrorX + ' ' + (cy - 100) + ' Q ' + (mirrorX + 30) + ' ' + cy + ' ' + mirrorX + ' ' + (cy + 100) + '" stroke="#0f172a" stroke-width="3" fill="none"/>';
}
/* Штриховка зеркала */
for (let i = 0; i < 8; i++){
const y = cy - 90 + i * 22;
const dx = this.mode === 'concave' ? 8 : -8;
svg += '<line x1="' + (mirrorX + (this.mode==='concave'?1:-1)) + '" y1="' + y + '" x2="' + (mirrorX + dx) + '" y2="' + (y + 6) + '" stroke="#0f172a" stroke-width="1.1"/>';
}
/* Точки F и C */
svg += '<circle cx="' + focusX + '" cy="' + cy + '" r="3.5" fill="#dc2626"/>';
svg += '<text x="' + focusX + '" y="' + (cy + 18) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626" font-weight="700">F</text>';
if (this.mode === 'concave'){
svg += '<circle cx="' + centerX + '" cy="' + cy + '" r="3.5" fill="#1d4ed8"/>';
svg += '<text x="' + centerX + '" y="' + (cy + 18) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#1d4ed8" font-weight="700">2F</text>';
}
/* Объект — красная стрелка вверх */
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 + '"/>';
/* Лучи (3 канонических) */
/* Луч 1: параллельный оптической оси → отражается через F */
svg += '<line x1="' + objX + '" y1="' + objTopY + '" x2="' + mirrorX + '" y2="' + objTopY + '" stroke="#16a34a" stroke-width="1.6"/>';
if (this.mode === 'concave'){
svg += '<line x1="' + mirrorX + '" y1="' + objTopY + '" x2="' + focusX + '" y2="' + cy + '" stroke="#16a34a" stroke-width="1.6"/>';
/* Продолжение до изображения */
const slope1 = (cy - objTopY) / (focusX - mirrorX);
const x2 = imgX, y2 = cy + slope1 * (x2 - focusX);
svg += '<line x1="' + focusX + '" y1="' + cy + '" x2="' + x2 + '" y2="' + y2.toFixed(1) + '" stroke="#16a34a" stroke-width="1" stroke-dasharray="3 3"/>';
} else {
/* Выпуклое: отражается так, словно вышел из мнимого F справа */
const slope = (objTopY - cy) / (mirrorX - focusX);
svg += '<line x1="' + mirrorX + '" y1="' + objTopY + '" x2="20" y2="' + (objTopY + slope * (20 - mirrorX)).toFixed(1) + '" stroke="#16a34a" stroke-width="1.6"/>';
svg += '<line x1="' + mirrorX + '" y1="' + objTopY + '" x2="' + (mirrorX + 50) + '" y2="' + (objTopY + slope * 50).toFixed(1) + '" stroke="#16a34a" stroke-width="1" stroke-dasharray="3 3"/>';
}
/* Луч 2: через F → отражается параллельно оси (только вогнутое) */
if (this.mode === 'concave' && objX !== focusX){
const slope2 = (cy - objTopY) / (focusX - objX);
const hitY = objTopY + slope2 * (mirrorX - objX);
svg += '<line x1="' + objX + '" y1="' + objTopY + '" x2="' + mirrorX + '" y2="' + hitY.toFixed(1) + '" stroke="#1d4ed8" stroke-width="1.6"/>';
svg += '<line x1="' + mirrorX + '" y1="' + hitY.toFixed(1) + '" x2="' + Math.max(20, imgX) + '" y2="' + hitY.toFixed(1) + '" stroke="#1d4ed8" stroke-width="1.6"/>';
}
/* Изображение */
if (Math.abs(imgX) < 1e8 && imgX > 20 && imgX < mirrorX){
const imgTopY = cy - imgH;
const dashed = (this.mode === 'convex' || imgH > 0); /* мнимое = пунктир */
const isVirtual = (this.mode === 'convex') || (f < 0);
const sd = isVirtual ? ' stroke-dasharray="4 3"' : '';
const op = isVirtual ? 0.7 : 1.0;
svg += '<line x1="' + imgX + '" y1="' + cy + '" x2="' + imgX + '" y2="' + imgTopY + '" stroke="#7c2d12" stroke-width="2.6"' + sd + ' opacity="' + op + '"/>';
svg += '<polygon points="' + imgX + ',' + imgTopY + ' ' + (imgX - 5) + ',' + (imgTopY + (imgH > 0 ? 10 : -10)) + ' ' + (imgX + 5) + ',' + (imgTopY + (imgH > 0 ? 10 : -10)) + '" fill="#7c2d12" opacity="' + op + '"/>';
}
/* Подпись параметров */
const Glabel = isFinite(G) ? G.toFixed(2) : '—';
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 + '</text>';
svg += '<text x="' + (W/2) + '" y="' + (H - 8) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#64748b">' + (this.mode==='concave'?'вогнутое':'выпуклое') + ' · F=' + Math.abs(F).toFixed(0) + 'px · d=' + d.toFixed(0) + 'px</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.SphericalMirror = SphericalMirror;
/* ============================================================ */
/* RefractionLab — преломление на границе двух сред (Снелл) */
/* ============================================================ */
class RefractionLab {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 540;
this.H = opts.height || 320;
this.n1 = opts.n1 !== undefined ? opts.n1 : 1.0; /* воздух */
this.n2 = opts.n2 !== undefined ? opts.n2 : 1.5; /* стекло */
this.alpha = opts.alpha !== undefined ? opts.alpha : 35; /* градусов */
this.color = opts.color || '#d97706';
this.paused = true;
this.render();
}
setN1(v){ this.n1 = Math.max(1, v); this.render(); }
setN2(v){ this.n2 = Math.max(1, v); this.render(); }
setAlpha(v){ this.alpha = Math.max(0, Math.min(89, v)); this.render(); }
update(){}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const cx = W / 2, cy = H / 2;
/* Углы (рад) */
const a = this.alpha * Math.PI / 180;
const sinB = this.n1 / this.n2 * Math.sin(a);
const totalInternal = Math.abs(sinB) > 1;
const b = totalInternal ? null : Math.asin(sinB);
/* Критический угол n1>n2 */
let critDeg = null;
if (this.n1 > this.n2) critDeg = Math.asin(this.n2 / this.n1) * 180 / Math.PI;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Среда 1 (верх) */
svg += '<rect x="0" y="0" width="' + W + '" height="' + cy + '" fill="#e0f2fe" opacity="0.5"/>';
/* Среда 2 (низ) */
svg += '<rect x="0" y="' + cy + '" width="' + W + '" height="' + cy + '" fill="#fef3c7" opacity="0.6"/>';
/* Граница */
svg += '<line x1="0" y1="' + cy + '" x2="' + W + '" y2="' + cy + '" stroke="#0f172a" stroke-width="2"/>';
/* Нормаль (пунктир) */
svg += '<line x1="' + cx + '" y1="40" x2="' + cx + '" y2="' + (H - 40) + '" stroke="#94a3b8" stroke-width="1" stroke-dasharray="5 4"/>';
svg += '<text x="' + (cx + 8) + '" y="55" font-family="JetBrains Mono,monospace" font-size="10" fill="#64748b">нормаль</text>';
/* Падающий луч (из верхней-левой области) */
const L = 130;
const ix = cx - L * Math.sin(a), iy = cy - L * Math.cos(a);
svg += '<line x1="' + ix.toFixed(1) + '" y1="' + iy.toFixed(1) + '" x2="' + cx + '" y2="' + cy + '" stroke="#dc2626" stroke-width="2.4"/>';
/* Стрелка падающего */
const ang_i = Math.atan2(cy - iy, cx - ix);
const arrPx = cx - 30 * Math.cos(ang_i), arrPy = cy - 30 * Math.sin(ang_i);
svg += '<polygon points="' + (arrPx + 7 * Math.cos(ang_i - 0.4)).toFixed(1) + ',' + (arrPy + 7 * Math.sin(ang_i - 0.4)).toFixed(1) + ' ' + arrPx.toFixed(1) + ',' + arrPy.toFixed(1) + ' ' + (arrPx + 7 * Math.cos(ang_i + 0.4)).toFixed(1) + ',' + (arrPy + 7 * Math.sin(ang_i + 0.4)).toFixed(1) + '" fill="#dc2626"/>';
/* Дуга угла падения */
svg += '<path d="M ' + (cx - 30 * Math.sin(a/2)) + ' ' + (cy - 30 * Math.cos(a/2)) + ' A 30 30 0 0 1 ' + cx + ' ' + (cy - 30) + '" stroke="#dc2626" stroke-width="1.2" fill="none"/>';
svg += '<text x="' + (cx - 16 * Math.sin(a/2) - 12).toFixed(1) + '" y="' + (cy - 16 * Math.cos(a/2)).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626" font-weight="700">α</text>';
/* Отражённый луч */
const rx = cx + L * Math.sin(a), ry = cy - L * Math.cos(a);
svg += '<line x1="' + cx + '" y1="' + cy + '" x2="' + rx.toFixed(1) + '" y2="' + ry.toFixed(1) + '" stroke="#7c2d12" stroke-width="1.6" stroke-dasharray="4 2"/>';
svg += '<text x="' + (rx + 4).toFixed(1) + '" y="' + (ry - 4).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="10" fill="#7c2d12">отраж.</text>';
/* Преломлённый луч или полное отражение */
if (totalInternal){
svg += '<text x="' + (cx + 10) + '" y="' + (cy + 30) + '" font-family="Outfit,sans-serif" font-size="13" fill="#dc2626" font-weight="800">полное внутреннее отражение</text>';
} else {
const tx = cx + L * Math.sin(b), ty = cy + L * Math.cos(b);
svg += '<line x1="' + cx + '" y1="' + cy + '" x2="' + tx.toFixed(1) + '" y2="' + ty.toFixed(1) + '" stroke="#16a34a" stroke-width="2.4"/>';
const ang_t = Math.atan2(ty - cy, tx - cx);
const apx = cx + 30 * Math.cos(ang_t), apy = cy + 30 * Math.sin(ang_t);
svg += '<polygon points="' + (apx - 7 * Math.cos(ang_t - 0.4)).toFixed(1) + ',' + (apy - 7 * Math.sin(ang_t - 0.4)).toFixed(1) + ' ' + apx.toFixed(1) + ',' + apy.toFixed(1) + ' ' + (apx - 7 * Math.cos(ang_t + 0.4)).toFixed(1) + ',' + (apy - 7 * Math.sin(ang_t + 0.4)).toFixed(1) + '" fill="#16a34a"/>';
svg += '<path d="M ' + cx + ' ' + (cy + 30) + ' A 30 30 0 0 0 ' + (cx + 30 * Math.sin(b/2)) + ' ' + (cy + 30 * Math.cos(b/2)) + '" stroke="#16a34a" stroke-width="1.2" fill="none"/>';
svg += '<text x="' + (cx + 18 * Math.sin(b/2) + 4).toFixed(1) + '" y="' + (cy + 18 * Math.cos(b/2) + 4).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="#16a34a" font-weight="700">β</text>';
}
/* Подписи сред */
svg += '<text x="20" y="22" font-family="JetBrains Mono,monospace" font-size="11" fill="#0369a1" font-weight="700">n₁ = ' + this.n1.toFixed(2) + '</text>';
svg += '<text x="20" y="' + (H - 12) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="#a16207" font-weight="700">n₂ = ' + this.n2.toFixed(2) + '</text>';
/* Формула + углы */
const bDeg = totalInternal ? '—' : (b * 180 / Math.PI).toFixed(1);
svg += '<text x="' + (W - 12) + '" y="22" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">n₁ sin α = n₂ sin β</text>';
svg += '<text x="' + (W - 12) + '" y="' + (H - 12) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">α=' + this.alpha.toFixed(0) + '° · β=' + bDeg + '°' + (critDeg!==null?' · αкр=' + critDeg.toFixed(1) + '°':'') + '</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.RefractionLab = RefractionLab;
/* ============================================================ */
/* PrismSpectrum — призма, дисперсия белого света */
/* ============================================================ */
class PrismSpectrum {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 580;
this.H = opts.height || 260;
this.alpha = opts.alpha !== undefined ? opts.alpha : 50; /* угол падения, градусы */
this.paused = true;
this.render();
}
setAlpha(v){ this.alpha = Math.max(20, Math.min(75, v)); this.render(); }
update(){}
/* Показатели преломления стекла для разных длин волн (упрощённая модель Коши) */
nForColor(lamNm){
return 1.5 + 6500 / (lamNm * lamNm); /* красный ~1.518, фиолет ~1.530 */
}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
/* Геометрия равностороннего треугольника-призмы */
const cx = W / 2, cy = H / 2 + 10;
const side = 140;
const h = side * Math.sqrt(3) / 2;
const A = { x: cx, y: cy - h * 2/3 }; /* верхняя вершина */
const B = { x: cx - side/2, y: cy + h/3 }; /* нижняя левая */
const C = { x: cx + side/2, y: cy + h/3 }; /* нижняя правая */
let svg = util.svgFrame(W, H, {bg:'#0f172a'});
/* Призма (полупрозрачная) */
svg += '<polygon points="' + A.x + ',' + A.y.toFixed(1) + ' ' + B.x + ',' + B.y.toFixed(1) + ' ' + C.x + ',' + C.y.toFixed(1) + '" fill="rgba(255,255,255,0.08)" stroke="#cbd5e1" stroke-width="1.6"/>';
/* Падающий белый луч на грань AB */
const a = this.alpha * Math.PI / 180;
/* Точка входа — середина грани AB */
const Pin = { x: (A.x + B.x) / 2, y: (A.y + B.y) / 2 };
/* Нормаль к AB (наружу, влево-вверх) */
const ABx = B.x - A.x, ABy = B.y - A.y;
const Lab = Math.hypot(ABx, ABy);
const nABx = -ABy / Lab, nABy = ABx / Lab; /* перпендикуляр */
/* Источник падающего луча */
const inLen = 160;
const inx = Pin.x + inLen * (nABx * Math.cos(a) - (ABx/Lab) * Math.sin(a));
const iny = Pin.y + inLen * (nABy * Math.cos(a) - (ABy/Lab) * Math.sin(a));
svg += '<line x1="' + inx.toFixed(1) + '" y1="' + iny.toFixed(1) + '" x2="' + Pin.x.toFixed(1) + '" y2="' + Pin.y.toFixed(1) + '" stroke="#fff" stroke-width="3"/>';
svg += '<text x="' + (inx - 8).toFixed(1) + '" y="' + (iny - 6).toFixed(1) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#fff">белый</text>';
/* 7 цветов спектра — каждый со своим n и углом преломления */
const colors = [
{ lam: 700, c: '#dc2626' }, /* красный */
{ lam: 620, c: '#ea580c' }, /* оранжевый */
{ lam: 580, c: '#facc15' }, /* жёлтый */
{ lam: 520, c: '#16a34a' }, /* зелёный */
{ lam: 480, c: '#06b6d4' }, /* голубой */
{ lam: 450, c: '#1d4ed8' }, /* синий */
{ lam: 420, c: '#7c3aed' } /* фиолетовый */
];
/* Нормаль к BC (наружу, вправо-вниз) */
const BCx = C.x - B.x, BCy = C.y - B.y;
const Lbc = Math.hypot(BCx, BCy);
const nBCx = BCy / Lbc, nBCy = -BCx / Lbc;
/* Точки выхода — равномерно по грани BC */
const outLen = 180;
for (let i = 0; i < colors.length; i++){
const co = colors[i];
const n = this.nForColor(co.lam);
/* Преломление при входе: sin β = sin α / n */
const sinB = Math.sin(a) / n;
if (Math.abs(sinB) > 1) continue;
const beta = Math.asin(sinB);
/* Внутри призмы луч идёт от Pin под углом beta от нормали к AB, в сторону BC */
/* Направление внутри: поворот нормали-в-стекло (-nAB) на beta */
const dirX = -nABx * Math.cos(beta) + (ABx/Lab) * Math.sin(beta);
const dirY = -nABy * Math.cos(beta) + (ABy/Lab) * Math.sin(beta);
/* Найти точку выхода на BC (параметрический луч / линия BC) */
const denom = dirX * (-(C.y - B.y)) + dirY * (C.x - B.x);
if (Math.abs(denom) < 1e-6) continue;
const t = ((B.x - Pin.x) * (-(C.y - B.y)) + (B.y - Pin.y) * (C.x - B.x)) / denom;
const Pout = { x: Pin.x + t * dirX, y: Pin.y + t * dirY };
/* Луч внутри */
svg += '<line x1="' + Pin.x.toFixed(1) + '" y1="' + Pin.y.toFixed(1) + '" x2="' + Pout.x.toFixed(1) + '" y2="' + Pout.y.toFixed(1) + '" stroke="' + co.c + '" stroke-width="1.6" opacity="0.85"/>';
/* Преломление при выходе: угол к нормали BC внутри */
const cosIn = -(dirX * nBCx + dirY * nBCy); /* направление к внешней нормали */
const sinIn = Math.sqrt(Math.max(0, 1 - cosIn * cosIn));
const sinOut = sinIn * n;
if (sinOut > 1) continue;
const cosOut = Math.sqrt(1 - sinOut * sinOut);
/* Тангенциальная составляющая (вдоль BC) */
const tBCx = BCx / Lbc, tBCy = BCy / Lbc;
const tanSign = (dirX * tBCx + dirY * tBCy) >= 0 ? 1 : -1;
const outX = nBCx * cosOut + tBCx * sinOut * tanSign;
const outY = nBCy * cosOut + tBCy * sinOut * tanSign;
const Pend = { x: Pout.x + outLen * outX, y: Pout.y + outLen * outY };
svg += '<line x1="' + Pout.x.toFixed(1) + '" y1="' + Pout.y.toFixed(1) + '" x2="' + Pend.x.toFixed(1) + '" y2="' + Pend.y.toFixed(1) + '" stroke="' + co.c + '" stroke-width="2.4"/>';
}
/* Подпись */
svg += '<text x="' + (W/2) + '" y="20" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#cbd5e1" font-weight="700">дисперсия: n(λ) растёт при уменьшении λ → фиолетовый отклоняется сильнее красного</text>';
svg += '<text x="' + (W/2) + '" y="' + (H - 8) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#94a3b8">α = ' + this.alpha.toFixed(0) + '° · стекло (модель Коши)</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.PrismSpectrum = PrismSpectrum;
})();
+309 -13
View File
@@ -229,9 +229,9 @@ const PARAS = [
{ id:'p2', num:'§ 15', name:'Интерференция', sub:'$\\Delta = k\\lambda$', built:true },
{ id:'p3', num:'§ 16', name:'Дифракция', sub:'$d\\sin\\varphi = k\\lambda$', built:true },
{ id:'p4', num:'§ 17', name:'Отражение, зеркала', sub:'$\\angle_{пад} = \\angle_{отр}$', built:true },
{ id:'p5', num:'§ 18', name:'Сферические зеркала', sub:'Будет в W6', built:false },
{ id:'p6', num:'§ 19', name:'Преломление', sub:'Будет в W6', built:false },
{ id:'p7', num:'§ 20', name:'Опт. элементы', sub:'Будет в W6', built:false },
{ 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 },
@@ -247,6 +247,9 @@ const ACH_LABELS = {
p2_done:'§15 — интерференция освоена',
p3_done:'§16 — дифракция освоена',
p4_done:'§17 — отражение и зеркала освоены',
p5_done:'§18 — сферические зеркала освоены',
p6_done:'§19 — закон Снелла освоен',
p7_done:'§20 — призма и оптоволокно освоены',
start:'Начало главы 3!',
ch3_done:'Глава 3 пройдена — Оптика!'
};
@@ -329,9 +332,7 @@ function buildParaSelector(){
const BUILT=new Set();
const BUILDERS = {
p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(),
p5:()=>buildStubP('p5','§ 18','§18 в разработке (W6) — вогнутые и выпуклые сферические зеркала, фокус $F = R/2$, построение изображений.'),
p6:()=>buildStubP('p6','§ 19','§19 в разработке (W6) — закон Снелла $n_1\\sin\\alpha = n_2\\sin\\beta$, полное внутреннее отражение.'),
p7:()=>buildStubP('p7','§ 20','§20 в разработке (W6) — призмы, плоскопараллельные пластинки, оптоволокно.'),
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) — лупа, микроскоп, телескоп.'),
@@ -355,9 +356,9 @@ const SIDEBARS = {
p2:{title:'Шпаргалка § 15', rows:[['Условие max','$\\Delta = k\\lambda$'],['Условие min','$\\Delta = (2k+1)\\lambda/2$'],['Когерентность','$\\omega$ и $\\Delta\\varphi$ = const'],['Опыт Юнга','2 щели → полосы'],['Кольца Ньютона','тонкие плёнки']]},
p3:{title:'Шпаргалка § 16', rows:[['Гюйгенс','каждая точка фронта — источник'],['Френель','+ интерференция вторичных'],['Решётка','$d\\sin\\varphi = k\\lambda$'],['$k$','порядок'],['$d$','период решётки']]},
p4:{title:'Шпаргалка § 17', rows:[['Прямолинейное','тени'],['Закон отражения','$\\angle_{пад} = \\angle_{отр}$'],['Плоское зеркало','мнимое, прямое, равное'],['Симметрия','$d_{объект} = d_{изобр}$ от зеркала']]},
p5:{title:'§ 18', rows:[['Тема','Сферические зеркала'],['Статус','В разработке (W6)']]},
p6:{title:'§ 19', rows:[['Тема','Преломление'],['Статус','В разработке (W6)']]},
p7:{title:'§ 20', rows:[['Тема','Опт. элементы'],['Статус','В разработке (W6)']]},
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)']]},
@@ -369,9 +370,9 @@ const TIPS=[
{sec:'p2',html:'§ 15 — интерференция = наложение когерентных волн. Условие max: $\\Delta = k\\lambda$. Опыт Юнга с двумя щелями.'},
{sec:'p3',html:'§ 16 — двигай $d$ и $\\lambda$. Чем меньше $d$, тем шире разлёт спектров. Главный max — $k=0$ (прямо).'},
{sec:'p4',html:'§ 17 — закон отражения: угол падения = углу отражения. Изображение в плоском зеркале — мнимое, симметричное.'},
{sec:'p5',html:'§ 18 — в разработке (W6).'},
{sec:'p6',html:'§ 19 — в разработке (W6).'},
{sec:'p7',html:'§ 20 — в разработке (W6).'},
{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).'},
@@ -777,7 +778,233 @@ function buildP4(){
renderMath(box);
}
/* ===== Stubs §18-§23 + Final ===== */
/* ===== §18 Сферические зеркала ===== */
function buildP5(){
const box = document.getElementById('p5-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Сферические зеркала', '§ 18.1',
'<p><b>Сферическое зеркало</b> — отполированная часть сферической поверхности. Бывают двух типов:</p>'
+ '<ul>'
+ '<li><b>Вогнутое</b> (собирающее) — отражающая поверхность с внутренней стороны сферы.</li>'
+ '<li><b>Выпуклое</b> (рассеивающее) — отражающая поверхность с наружной стороны.</li>'
+ '</ul>'
+ '<p>Основные точки зеркала: <b>оптический центр</b> $O$ (середина зеркала), <b>центр сферы</b> $C$, <b>главный фокус</b> $F$. Прямая, проходящая через $O$ и $C$, — <b>главная оптическая ось</b>.</p>'
+ '<p><b>Главный фокус</b> $F$ — точка, в которой пересекаются (или кажутся пересекающимися) отражённые лучи, параллельные главной оси.</p>'
+ '<p style="text-align:center;margin:8px 0">$$F = \\dfrac{R}{2}$$</p>'
+ '<p>где $R$ — радиус кривизны зеркала. Для выпуклого зеркала $F < 0$ (мнимый фокус).</p>');
html += makeCard('rule', 'Формула зеркала и увеличение', '§ 18.2',
'<p><b>Формула сферического зеркала</b> связывает расстояние до объекта $d$, расстояние до изображения $f$ и фокусное расстояние $F$:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\dfrac{1}{d} + \\dfrac{1}{f} = \\dfrac{1}{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></p>'
+ '<ul>'
+ '<li>$f > 0$ — действительное изображение (перед зеркалом).</li>'
+ '<li>$f < 0$ — мнимое (за зеркалом).</li>'
+ '<li>$\\Gamma < 0$ — перевёрнутое; $\\Gamma > 0$ — прямое.</li>'
+ '</ul>');
html += makeCard('algo', 'Построение изображения — 3 луча', '§ 18.3',
'<p>Чтобы найти изображение объекта в сферическом зеркале, используют <b>три характерных луча</b> от верхней точки объекта:</p>'
+ '<ol>'
+ '<li><b>Параллельный главной оси</b> → отражается через фокус $F$.</li>'
+ '<li><b>Через фокус $F$</b> → отражается параллельно главной оси.</li>'
+ '<li><b>Через центр сферы $C$</b> → отражается обратно по тому же пути (нормаль к поверхности).</li>'
+ '</ol>'
+ '<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>F</b> — фокусное расстояние, <b>d</b> — расстояние до объекта. Переключай тип зеркала. Зелёный луч — параллельный, синий — через фокус. Тёмно-коричневая стрелка — изображение (пунктир = мнимое).</div>'
+ '<div class="fx-holder" id="fx-mir2"></div>'
+ '<div class="fx-sliders" id="fx-mir2-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="i5-calc-score">0</b> / 5.</div>'
+ '<div id="i5-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i5-calc-inp" placeholder="ответ"><button class="btn primary" id="i5-calc-go">Проверить</button></div><div class="feedback" id="i5-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="i5-th-score">0</b> / 5.</div>'
+ '<div id="i5-th-q" style="margin:8px 0"></div><div class="opts-row" id="i5-th-opts"></div><div class="feedback" id="i5-th-fb"></div></div>';
html += '<div id="boss-5-slot"></div>';
html += readButton('p5');
html += secNavFor('p5');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-mir2');
const m = new PHYS.SphericalMirror(el, {width:620, height:280, F:80, d:180, objH:50, mode:'concave'});
const slBox = document.getElementById('fx-mir2-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-mir2-mode" style="flex:1;padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;font-family:inherit">'
+ '<option value="concave">Вогнутое</option><option value="convex">Выпуклое</option>'
+ '</select></label>';
const slF = PHYS.util.slider({label:'F (px)', min:30, max:140, step:5, value:80, fmt:v=>v.toFixed(0), onChange:v=>m.setF(v)});
const slD = PHYS.util.slider({label:'d (px)', min:40, max:280, step:10, value:180, fmt:v=>v.toFixed(0), onChange:v=>m.setD(v)});
slBox.innerHTML = slMode + slF.html + slD.html;
document.getElementById('fx-mir2-mode').addEventListener('change', e => m.setMode(e.target.value));
slF.wire(slBox); slD.wire(slBox);
});
runQuizInput('i5-calc', I5_CALC_ITEMS, 16);
runQuizMC('i5-th', I5_TH_ITEMS, 12);
const bs = loadBossState('boss-5') || { stage:0, solved:false };
makeAndBindBoss('boss-5-slot', '5', BOSS_DEFS.b5, bs,
()=>saveBossState('boss-5', bs),
()=>{ bumpProgress('p5', 40); achievement('p5_done'); });
wireReadBtn('p5');
renderMath(box);
}
/* ===== §19 Преломление света ===== */
function buildP6(){
const box = document.getElementById('p6-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Показатель преломления', '§ 19.1',
'<p>При переходе света из одной прозрачной среды в другую на границе луч <b>преломляется</b> — изменяет направление. Причина — разная скорость света в средах.</p>'
+ '<p><b>Абсолютный показатель преломления</b> среды:</p>'
+ '<p style="text-align:center;margin:8px 0">$$n = \\dfrac{c}{v}$$</p>'
+ '<p>где $c$ — скорость света в вакууме, $v$ — в данной среде. Поскольку $v < c$, всегда $n > 1$.</p>'
+ '<p><b>Типичные значения:</b> воздух $\\approx 1{,}00$ (≈ вакуум), вода 1,33, стекло 1,5, алмаз 2,42.</p>'
+ '<p><b>Относительный показатель</b> для перехода из среды 1 в среду 2:</p>'
+ '<p style="text-align:center;margin:6px 0">$$n_{21} = \\dfrac{n_2}{n_1} = \\dfrac{v_1}{v_2}$$</p>');
html += makeCard('rule', 'Закон преломления (Снелл)', '§ 19.2',
'<p><b>Закон преломления света (Снелла – Декарта):</b></p>'
+ '<ol>'
+ '<li>Луч падающий, преломлённый и нормаль к границе в точке падения лежат в одной плоскости.</li>'
+ '<li>Отношение синуса угла падения к синусу угла преломления равно отношению показателей преломления:</li>'
+ '</ol>'
+ '<p style="text-align:center;margin:8px 0">$$n_1 \\sin\\alpha = n_2 \\sin\\beta$$</p>'
+ '<p>или эквивалентно: $\\dfrac{\\sin\\alpha}{\\sin\\beta} = n_{21} = \\dfrac{n_2}{n_1}$.</p>'
+ '<p><b>Запоминалка:</b> луч «прижимается» к нормали при переходе в более плотную среду ($n_2 > n_1$ ⇒ $\\beta < \\alpha$).</p>');
html += makeCard('example', 'Полное внутреннее отражение', '§ 19.3',
'<p>Если свет идёт из более плотной среды в менее плотную ($n_1 > n_2$), угол преломления $\\beta > \\alpha$. При некотором угле падения $\\beta = 90°$ — луч скользит по границе.</p>'
+ '<p><b>Критический угол</b> $\\alpha_{кр}$ (предельный угол ПВО):</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\sin\\alpha_{кр} = \\dfrac{n_2}{n_1}$$</p>'
+ '<p>При $\\alpha > \\alpha_{кр}$ преломление отсутствует — весь свет отражается обратно. Это <b>полное внутреннее отражение (ПВО)</b>.</p>'
+ '<p><b>Применения:</b> оптоволокно, перископы, бинокли, призмы оборачивающие.</p>'
+ '<p><b>Пример:</b> вода → воздух ($n_1=1{,}33$, $n_2=1$): $\\sin\\alpha_{кр} = 1/1{,}33 \\approx 0{,}75$, $\\alpha_{кр} \\approx 48{,}6°$.</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">Меняй угол падения $\\alpha$ и показатели сред. Красный — падающий, зелёный — преломлённый, пунктир — отражённый. При $n_1 > n_2$ можно поймать полное внутреннее отражение.</div>'
+ '<div class="fx-holder" id="fx-ref"></div>'
+ '<div class="fx-sliders" id="fx-ref-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">$n_1\\sin\\alpha = n_2\\sin\\beta$. Решено: <b id="i6-calc-score">0</b> / 5.</div>'
+ '<div id="i6-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i6-calc-inp" placeholder="ответ"><button class="btn primary" id="i6-calc-go">Проверить</button></div><div class="feedback" id="i6-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="i6-th-score">0</b> / 5.</div>'
+ '<div id="i6-th-q" style="margin:8px 0"></div><div class="opts-row" id="i6-th-opts"></div><div class="feedback" id="i6-th-fb"></div></div>';
html += '<div id="boss-6-slot"></div>';
html += readButton('p6');
html += secNavFor('p6');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-ref');
const r = new PHYS.RefractionLab(el, {width:540, height:320, n1:1, n2:1.5, alpha:35});
const slBox = document.getElementById('fx-ref-sl');
const slA = PHYS.util.slider({label:'α (град)', min:0, max:89, step:1, value:35, fmt:v=>v.toFixed(0), onChange:v=>r.setAlpha(v)});
const slN2 = PHYS.util.slider({label:'n₂', min:0.5, max:2.5, step:0.05, value:1.5, fmt:v=>v.toFixed(2), onChange:v=>r.setN2(v)});
const slN1 = PHYS.util.slider({label:'n₁', min:1, max:2.5, step:0.05, value:1, fmt:v=>v.toFixed(2), onChange:v=>r.setN1(v)});
slBox.innerHTML = slA.html + slN1.html + slN2.html;
slA.wire(slBox); slN1.wire(slBox); slN2.wire(slBox);
});
runQuizInput('i6-calc', I6_CALC_ITEMS, 16);
runQuizMC('i6-th', I6_TH_ITEMS, 12);
const bs = loadBossState('boss-6') || { stage:0, solved:false };
makeAndBindBoss('boss-6-slot', '6', BOSS_DEFS.b6, bs,
()=>saveBossState('boss-6', bs),
()=>{ bumpProgress('p6', 40); achievement('p6_done'); });
wireReadBtn('p6');
renderMath(box);
}
/* ===== §20 Оптические элементы ===== */
function buildP7(){
const box = document.getElementById('p7-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Призма и дисперсия', '§ 20.1',
'<p><b>Призма</b> — прозрачное тело с двумя плоскими преломляющими гранями, образующими двугранный угол $A$ (преломляющий угол призмы).</p>'
+ '<p>Луч, проходя через призму, преломляется на обеих гранях и отклоняется от исходного направления на угол отклонения $\\delta$.</p>'
+ '<p><b>Дисперсия света</b> — зависимость показателя преломления от длины волны (частоты): $n = n(\\lambda)$.</p>'
+ '<p>Для большинства прозрачных сред $n$ <b>растёт с уменьшением $\\lambda$</b>: фиолетовый свет ($\\lambda\\approx 400$ нм) преломляется сильнее красного ($\\lambda\\approx 700$ нм).</p>'
+ '<p>Поэтому призма <b>разлагает белый свет в спектр</b> — Ньютон (1666).</p>'
+ '<p><b>Радуга</b> — природный пример дисперсии: капля воды работает как призма + зеркало (внутреннее отражение).</p>');
html += makeCard('rule', 'Плоскопараллельная пластинка', '§ 20.2',
'<p><b>Плоскопараллельная пластинка</b> — стекло с двумя параллельными гранями.</p>'
+ '<p>Луч, проходящий через пластинку, выходит <b>параллельно</b> падающему, но смещённым в сторону. Смещение:</p>'
+ '<p style="text-align:center;margin:8px 0">$$x = h \\cdot \\dfrac{\\sin(\\alpha - \\beta)}{\\cos\\beta}$$</p>'
+ '<p>где $h$ — толщина пластинки, $\\alpha$ — угол падения, $\\beta$ — угол преломления внутри.</p>'
+ '<p>При $\\alpha = 0$ смещения нет — луч идёт перпендикулярно.</p>');
html += makeCard('example', 'Оптоволокно — полное внутреннее отражение', '§ 20.3',
'<p><b>Оптоволокно</b> — тонкая стеклянная или пластиковая нить, передающая свет на большие расстояния благодаря многократному <b>полному внутреннему отражению</b>.</p>'
+ '<p>Строение: <b>сердцевина</b> (core) с показателем $n_1$ и <b>оболочка</b> (cladding) с $n_2 < n_1$. Свет, попавший в сердцевину под углом $\\alpha > \\alpha_{кр}$, отражается от границы без потерь и распространяется вдоль волокна.</p>'
+ '<p><b>Применения:</b></p>'
+ '<ul>'
+ '<li>Интернет и телефонная связь (магистральные сети).</li>'
+ '<li>Эндоскопы в медицине.</li>'
+ '<li>Освещение в зданиях, декоративные подсветки.</li>'
+ '</ul>'
+ '<p>Потери в современных волокнах — менее 0,2 дБ/км, что позволяет передавать сигнал на сотни километров без усиления.</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">Белый луч слева попадает на грань стеклянной призмы. Из-за дисперсии $n(\\lambda)$ цвета преломляются под разными углами. Двигай ползунок угла падения — наблюдай, как меняется спектр.</div>'
+ '<div class="fx-holder" id="fx-pr"></div>'
+ '<div class="fx-sliders" id="fx-pr-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">$\\sin\\alpha_{кр} = n_2/n_1$ (если $n_1 > n_2$). Решено: <b id="i7-calc-score">0</b> / 5.</div>'
+ '<div id="i7-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i7-calc-inp" placeholder="ответ"><button class="btn primary" id="i7-calc-go">Проверить</button></div><div class="feedback" id="i7-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="i7-th-score">0</b> / 5.</div>'
+ '<div id="i7-th-q" style="margin:8px 0"></div><div class="opts-row" id="i7-th-opts"></div><div class="feedback" id="i7-th-fb"></div></div>';
html += '<div id="boss-7-slot"></div>';
html += readButton('p7');
html += secNavFor('p7');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-pr');
const p = new PHYS.PrismSpectrum(el, {width:580, height:260, alpha:50});
const slBox = document.getElementById('fx-pr-sl');
const slA = PHYS.util.slider({label:'α (град)', min:20, max:75, step:1, value:50, fmt:v=>v.toFixed(0), onChange:v=>p.setAlpha(v)});
slBox.innerHTML = slA.html;
slA.wire(slBox);
});
runQuizInput('i7-calc', I7_CALC_ITEMS, 16);
runQuizMC('i7-th', I7_TH_ITEMS, 12);
const bs = loadBossState('boss-7') || { stage:0, solved:false };
makeAndBindBoss('boss-7-slot', '7', BOSS_DEFS.b7, bs,
()=>saveBossState('boss-7', bs),
()=>{ bumpProgress('p7', 40); achievement('p7_done'); });
wireReadBtn('p7');
renderMath(box);
}
/* ===== Stubs §21-§23 + Final ===== */
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>';
@@ -931,6 +1158,54 @@ const I4_IMG_ITEMS = [
{ q:'Угол падения отсчитывается от:', opts:['Поверхности','Нормали','Любого направления','Зеркала'], correct:1, explain:'От перпендикуляра к поверхности.' }
];
const I5_CALC_ITEMS = [
{ q:'$R = 40$ см. Фокус $F$ (см)?', answer:'20', explain:'$F = R/2 = 20$ см.' },
{ q:'$F = 10$ см, $d = 30$ см. $f$ (см)?', answer:'15', explain:'$1/f = 1/10 - 1/30 = 2/30 \\Rightarrow f = 15$ см.' },
{ q:'$F = 20$ см, $d = 60$ см. $\\Gamma$?', answer:['-0.5','-1/2'], explain:'$1/f = 1/20 - 1/60 = 1/30 \\Rightarrow 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 \\Rightarrow f = -30$ (мнимое).' },
{ q:'$d = 2F$. $f$ относительно $F$?', answer:['2f','2','2*f'], explain:'$1/f = 1/F - 1/(2F) = 1/(2F) \\Rightarrow f = 2F$.' }
];
const I5_TH_ITEMS = [
{ q:'Фокусное расстояние сферического зеркала:', opts:['$F = R$','$F = R/2$','$F = 2R$','$F = R^2$'], correct:1, explain:'$F = R/2$.' },
{ q:'Выпуклое зеркало даёт изображение:', opts:['Действительное, перевёрнутое','Мнимое, прямое, уменьшенное','Действительное, увеличенное','Точечное'], correct:1, explain:'Мнимое за зеркалом.' },
{ q:'Луч через центр сферы $C$:', opts:['Отражается обратно','Параллельно оси','Через фокус','Поглощается'], correct:0, explain:'По нормали — обратно.' },
{ q:'Если объект между $F$ и зеркалом (вогнутое), изображение:', opts:['Действительное','Мнимое, увеличенное, прямое','Уменьшенное','Не образуется'], correct:1, explain:'Лупа — мнимое прямое.' },
{ q:'$\\Gamma = -2$ означает:', opts:['Уменьшенное прямое','Увеличенное прямое','Увеличенное перевёрнутое в 2 раза','Без увеличения'], correct:2, explain:'$|\\Gamma|=2$, минус = перевёрнутое.' }
];
const I6_CALC_ITEMS = [
{ q:'$n_1 = 1$, $\\alpha = 30°$, $n_2 = 1{,}5$. $\\sin\\beta$?', answer:['1/3','0.333','0.33'], explain:'$\\sin\\beta = \\sin 30°/1{,}5 = 0{,}5/1{,}5 = 1/3$.' },
{ q:'Вода → воздух ($n_1=1{,}33$). $\\sin\\alpha_{кр}$?', answer:['0.75','3/4'], explain:'$\\sin\\alpha_{кр} = 1/1{,}33 \\approx 0{,}75$.' },
{ q:'Алмаз $n = 2{,}42$, $\\sin\\alpha_{кр}$?', answer:['0.41','0.413'], explain:'$1/2{,}42 \\approx 0{,}413$ ⇒ $\\alpha_{кр} \\approx 24{,}4°$.' },
{ q:'$n_{ст} = 1{,}5$. $v$ в стекле (м/с)?', answer:['2e8','2·10⁸','200000000'], explain:'$v = c/n = 3 \\cdot 10^8/1{,}5 = 2 \\cdot 10^8$.' },
{ q:'$\\alpha = 60°$, $n_2/n_1 = 1{,}5$. $\\sin\\beta$?', answer:['0.577','√3/3','0.58'], explain:'$\\sin\\beta = \\sin 60°/1{,}5 = (\\sqrt 3/2)/1{,}5 \\approx 0{,}577$.' }
];
const I6_TH_ITEMS = [
{ q:'Закон Снелла:', opts:['$n_1 \\sin\\alpha = n_2 \\sin\\beta$','$n_1 \\cos\\alpha = n_2 \\cos\\beta$','$n_1/\\alpha = n_2/\\beta$','$n_1 + n_2 = const$'], correct:0, explain:'Закон Снелла.' },
{ q:'$n = c/v$ означает: в среде свет идёт:', opts:['Быстрее $c$','Медленнее $c$','С такой же скоростью','Не движется'], correct:1, explain:'$v = c/n < c$ при $n>1$.' },
{ q:'ПВО возможно при переходе:', opts:['Воздух → стекло','Стекло → воздух','Вода → стекло','Никогда'], correct:1, explain:'Из более плотной в менее плотную ($n_1 > n_2$).' },
{ q:'Если $n_2 > n_1$, луч:', opts:['Прижимается к нормали','Удаляется от нормали','Идёт по оригинальному пути','Останавливается'], correct:0, explain:'$\\beta < \\alpha$ — прижимается.' },
{ q:'Палка в воде кажется сломанной из-за:', opts:['Отражения','Преломления','Дисперсии','Дифракции'], correct:1, explain:'Преломления на границе вода–воздух.' }
];
const I7_CALC_ITEMS = [
{ q:'Стекло $n = 1{,}5$, воздух $n=1$. $\\sin\\alpha_{кр}$?', answer:['0.667','2/3','0.67'], explain:'$1/1{,}5 = 2/3$.' },
{ q:'$n_{сердцевины} = 1{,}5$, $n_{оболочки} = 1{,}4$. $\\sin\\alpha_{кр}$?', answer:['0.933','14/15','0.93'], explain:'$1{,}4/1{,}5 = 14/15 \\approx 0{,}933$ ⇒ $\\alpha_{кр} \\approx 69°$.' },
{ q:'$\\alpha_{кр} = 30°$. $n_{отношение}$?', answer:'0.5', explain:'$\\sin 30° = 0{,}5 = n_2/n_1$.' },
{ q:'Толщина пластинки 4 см, $\\alpha=30°$, $\\beta = 19°$ (стекло). $x$ ≈ толщина $\\cdot \\sin(\\alpha-\\beta)/\\cos\\beta$ (см)?', answer:['0.8','0.79','0.81'], explain:'$x = 4 \\cdot \\sin 11°/\\cos 19° \\approx 4 \\cdot 0{,}19/0{,}945 \\approx 0{,}8$ см.' },
{ q:'$\\lambda_{красн} = 700$ нм отклоняется призмой <b>слабее</b> или сильнее фиолетового 400 нм?', answer:['слабее','меньше'], explain:'$n(700) < n(400)$ ⇒ красный отклоняется слабее.' }
];
const I7_TH_ITEMS = [
{ q:'Дисперсия — это:', opts:['Отражение','Зависимость $n$ от $\\lambda$','Разложение в спектр интерферометром','Дифракция в кристалле'], correct:1, explain:'$n = n(\\lambda)$.' },
{ q:'Призма разлагает белый свет потому что:', opts:['Разные $\\lambda$ имеют разные $n$','Свет частично отражается','Кристаллы поглощают','Стекло поляризует'], correct:0, explain:'Дисперсия.' },
{ q:'Плоскопараллельная пластинка:', opts:['Меняет направление луча','Смещает луч параллельно','Поглощает свет','Поворачивает поляризацию'], correct:1, explain:'Луч сдвигается, но не меняет угла.' },
{ q:'Оптоволокно работает на:', opts:['Интерференции','Дифракции','Полном внутр. отражении','Поляризации'], correct:2, explain:'ПВО внутри сердцевины.' },
{ q:'В радуге наблюдатель видит красный <b>сверху</b>, фиолетовый снизу потому что:', opts:['Красный преломляется сильнее','Фиолетовый преломляется сильнее','Цвета не отличаются','Капля поглощает синий'], correct:1, explain:'Фиолетовый ($\\lambda<$) преломляется сильнее.' }
];
/* ===== Boss defs ===== */
const BOSS_DEFS = {
b1: { title:'Босс §14 — Скорость света', tag:'§14', xp:65, stages:[
@@ -960,6 +1235,27 @@ const BOSS_DEFS = {
{ q:'Изображение в плоском зеркале:', type:'mc', opts:['Действ., перевёрнутое','Мнимое, прямое, равное','Уменьшенное','Увеличенное'], correct:1, explain:'Мнимое, прямое, равное.' },
{ q:'Объект на 3 м от зеркала. Изображение на сколько метров от объекта?', type:'input', a:'6', explain:'$d + d = 6$ м.' },
{ q:'Если повернуть зеркало на 15°, отражённый луч повернётся на:', type:'input', a:'30', explain:'$2\\alpha = 30°$.' }
]},
b5: { title:'Босс §18 — Сферические зеркала', tag:'§18', xp:75, stages:[
{ q:'$R = 60$ см. $F$ (см)?', type:'input', a:'30', explain:'$F = R/2 = 30$ см.' },
{ q:'$F=10$ см, $d=20$ см. $f$ (см)?', type:'input', a:'20', explain:'$1/f = 1/10 - 1/20 = 1/20 \\Rightarrow f = 20$.' },
{ q:'Изображение в выпуклом зеркале:', type:'mc', opts:['Действительное увеличенное','Мнимое прямое уменьшенное','Перевёрнутое','Равное'], correct:1, explain:'Всегда мнимое, прямое, уменьшенное.' },
{ q:'$\\Gamma = -f/d$. $f = 30$, $d = 15$. $\\Gamma$?', type:'input', a:['-2','-2.0'], explain:'$-30/15 = -2$.' },
{ q:'Луч через фокус $F$ после отражения идёт:', type:'mc', opts:['Через $C$','Параллельно главной оси','Обратно','Через $O$'], correct:1, explain:'Стандартный луч 2.' }
]},
b6: { title:'Босс §19 — Закон Снелла', tag:'§19', xp:75, stages:[
{ q:'$n_1\\sin\\alpha = ?$', type:'mc', opts:['$n_2\\cos\\beta$','$n_2\\sin\\beta$','$n_2/\\sin\\beta$','$\\sin\\beta/n_2$'], correct:1, explain:'Снелл.' },
{ q:'$n = c/v$. Если $n = 2$, то $v = ?$ (м/с)', type:'input', a:['1.5e8','1.5·10⁸','150000000'], explain:'$v = c/n = 1{,}5 \\cdot 10^8$.' },
{ q:'$\\sin\\alpha_{кр} = n_2/n_1$ при условии:', type:'mc', opts:['$n_1 < n_2$','$n_1 > n_2$','$n_1 = n_2$','Любое'], correct:1, explain:'ПВО только из плотной в менее плотную.' },
{ q:'$n_{вода} = 1{,}33$. $\\sin\\alpha_{кр}$ (округлить до 0,01)?', type:'input', a:['0.75','3/4'], explain:'$1/1{,}33 \\approx 0{,}75$.' },
{ q:'Луч из стекла в воздух под $\\alpha = 60°$, $n_{ст}=1{,}5$. Что произойдёт?', type:'mc', opts:['Преломится','Полное внутр. отражение','Поглотится','Удвоится'], correct:1, explain:'$\\sin 60° = 0{,}866 > 0{,}667$ ⇒ ПВО.' }
]},
b7: { title:'Босс §20 — Призма, оптоволокно', tag:'§20', xp:75, stages:[
{ q:'Призма разлагает белый свет в спектр благодаря:', type:'mc', opts:['Дифракции','Интерференции','Дисперсии','Отражению'], correct:2, explain:'Дисперсия $n(\\lambda)$.' },
{ q:'В стекле фиолетовый свет отклоняется ... красного:', type:'mc', opts:['Слабее','Сильнее','Одинаково','Не отклоняется'], correct:1, explain:'$n_{фиол} > n_{кр}$.' },
{ 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:'ПВО в сердцевине.' }
]}
};