feat(phys8 ch3): Phase 5 Wave 3+4 — §36-40 + Финал — Глава 3 завершена
§36 Преломление света: - Закон Снеллиуса с интерактивным OPTICS.refractRay - 4 материала (воздух/вода/стекло/алмаз) - Полное внутреннее отражение - 5 численных задач §37 Линзы. Оптическая сила: - OPTICS.thinLens — собирающая и рассеивающая - D = 1/F, дптр - 5 задач (включая F=17 мм для глаза) §38 Построение изображений (ГЛАВНЫЙ ВИЗУАЛ ОПТИКИ): - Конструктор изображения через OPTICS.buildLensImage - slider F и d, увеличение, тип изображения - 5 типов (d>2F/2F/F<d<2F/d=F/d<F) - DnD устройств (фотоаппарат/проектор/лупа) - 5 задач на формулу тонкой линзы §39 Глаз как оптическая система: - OPTICS.eyeDiagram с slider'ом аккомодации - 5 элементов глаза, MCQ - DnD оптические vs нервные части §40 Дефекты зрения. Очки: - Визуализация близоруков. и дальнозоркости с очками - OPTICS.thinLens исправляет фокус - 5 задач (включая «знак D») ФИНАЛ ГЛАВЫ 3: - Шпаргалка из 10 формул - 7 интегрированных боссов: c, отражение, преломление, D, тонкая линза, очки, магистр света - Ачивка light_master (+50 XP) Глава 3 «Световые явления» (§§32-40, 9 параграфов + финал) закончена. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1264,17 +1264,907 @@ function _p35_tasks(){
|
||||
r();
|
||||
}
|
||||
|
||||
/* === Заглушки для §36..§40 и финала — будут заполнены ниже === */
|
||||
function build_p36(){ _stub_phaseN('p36','Преломление света','Phase 5 Wave 3'); }
|
||||
function build_p37(){ _stub_phaseN('p37','Линзы. Оптическая сила','Phase 5 Wave 3'); }
|
||||
function build_p38(){ _stub_phaseN('p38','Построение изображений в линзах','Phase 5 Wave 4'); }
|
||||
function build_p39(){ _stub_phaseN('p39','Глаз как оптическая система','Phase 5 Wave 4'); }
|
||||
function build_p40(){ _stub_phaseN('p40','Дефекты зрения. Очки','Phase 5 Wave 4'); }
|
||||
function build_final3(){ _stub_phaseN('final3','Финал главы 3','Phase 5 Wave 4'); }
|
||||
function _stub_phaseN(id, name, ph){
|
||||
const box = document.getElementById(id+'-body');
|
||||
box.innerHTML = buildStub(id, name, ph) + secNavFor(id) + readButton(id);
|
||||
renderMath(box); wireReadBtn(id);
|
||||
/* ======== §36 — Преломление света ======== */
|
||||
function build_p36(){
|
||||
const box = document.getElementById('p36-body'); let h = '';
|
||||
h += makeCard('theory', 'Закон преломления', '§ 36.1',
|
||||
'<p>При переходе из одной среды в другую луч света <b>меняет направление</b>. Это <b>преломление</b>.</p>'
|
||||
+'<p>Закон Снеллиуса:</p>'
|
||||
+'<p style="text-align:center;margin:8px 0">$$\\dfrac{\\sin\\alpha}{\\sin\\beta} = \\dfrac{n_2}{n_1} = n_{2/1}$$</p>'
|
||||
+'<p>$n$ — <b>показатель преломления</b> среды. Чем больше $n$, тем сильнее замедляется свет.</p>'
|
||||
);
|
||||
h += makeCard('rule', 'Когда $\\alpha > \\beta$, а когда наоборот', '§ 36.2',
|
||||
'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li><b>Из менее плотной в более плотную</b> ($n_1 < n_2$): $\\alpha > \\beta$, луч приближается к нормали.</li>'
|
||||
+'<li><b>Из более плотной в менее плотную</b>: $\\alpha < \\beta$, луч удаляется от нормали.</li>'
|
||||
+'</ul>'
|
||||
+'<p>При большом $\\alpha$ из плотной среды возможно <b>полное внутреннее отражение</b>: луч вообще не выходит.</p>'
|
||||
+'<p>Таблица $n$:</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0"><li>воздух — $1{,}00$;</li><li>вода — $1{,}33$;</li><li>стекло — $1{,}5$;</li><li>алмаз — $2{,}42$.</li></ul>'
|
||||
);
|
||||
h += makeCard('example', 'Примеры', '§ 36.3',
|
||||
'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>Карандаш в стакане воды кажется «сломанным».</li>'
|
||||
+'<li>Рыба в воде кажется ближе, чем на самом деле.</li>'
|
||||
+'<li>Мираж в пустыне — лучи от неба преломляются в тёплом воздухе.</li>'
|
||||
+'<li>Призма раскладывает белый свет в радугу — разные цвета имеют разный $n$.</li>'
|
||||
+'</ul>'
|
||||
);
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Закон Снеллиуса</div></div>'
|
||||
+'<div class="wg-help">Меняй $\\alpha$ и $n$ — увидь, как меняется $\\beta$. При большом $\\alpha$ из плотной среды наступает полное внутреннее отражение.</div>'
|
||||
+'<div class="sliders" style="margin-bottom:10px"><label>$\\alpha$, °: <b id="p36-av">30</b><input type="range" id="p36-a" min="0" max="80" step="1" value="30"></label>'
|
||||
+'<label>Из: <select id="p36-n1" class="tinp" style="width:auto;padding:6px 10px"><option value="1">воздух (n=1)</option><option value="1.33">вода (n=1.33)</option><option value="1.5">стекло (n=1.5)</option></select></label>'
|
||||
+'<label>В: <select id="p36-n2" class="tinp" style="width:auto;padding:6px 10px"><option value="1">воздух (n=1)</option><option value="1.33" selected>вода (n=1.33)</option><option value="1.5">стекло (n=1.5)</option><option value="2.42">алмаз (n=2.42)</option></select></label></div>'
|
||||
+'<svg id="p36-sim" viewBox="0 0 460 280" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
|
||||
+'<div class="score-display" style="margin-top:8px"><span>$\\beta$ = <b id="p36-bv">22</b>°</span><span id="p36-tir-lab" style="color:#dc2626;display:none;font-weight:700">Полное внутреннее отражение!</span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Преломление: больше / меньше</div></div>'
|
||||
+'<div class="wg-help">При переходе в указанную среду угол $\\alpha$ — больше или меньше $\\beta$?</div>'
|
||||
+'<div id="p36-quiz"></div>'
|
||||
+'<div class="actions"><button class="btn" id="p36-quiz-next">Следующий</button></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p36-quiz-r">1</b>/5</span><span>Правильно: <b id="p36-quiz-ok">0</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Расставь по возрастанию $n$</div></div>'
|
||||
+'<div id="p36-dnd-pool"></div>'
|
||||
+'<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:10px"><div class="drop-box"><h5>1</h5><div class="drop-items" data-cat="r1"></div></div><div class="drop-box"><h5>2</h5><div class="drop-items" data-cat="r2"></div></div><div class="drop-box"><h5>3</h5><div class="drop-items" data-cat="r3"></div></div><div class="drop-box"><h5>4</h5><div class="drop-items" data-cat="r4"></div></div></div>'
|
||||
+'<div class="actions"><button class="btn primary" id="p36-dnd-check">Проверить</button><button class="btn" id="p36-dnd-reset">Сброс</button></div>'
|
||||
+'<div class="feedback" id="p36-dnd-fb"></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 задач</div></div>'
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p36-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p36-task-i">1</b>/5</span><span>Правильно: <b id="p36-task-ok">0</b></span></div></div>';
|
||||
box.innerHTML = h + secNavFor('p36') + readButton('p36');
|
||||
renderMath(box); wireReadBtn('p36');
|
||||
_p36_snell(); _p36_quiz(); _p36_dnd(); _p36_tasks();
|
||||
}
|
||||
function _p36_snell(){
|
||||
const svg = document.getElementById('p36-sim'); if(!svg) return;
|
||||
function d(){
|
||||
const a = +document.getElementById('p36-a').value;
|
||||
const n1 = +document.getElementById('p36-n1').value;
|
||||
const n2 = +document.getElementById('p36-n2').value;
|
||||
document.getElementById('p36-av').textContent = a;
|
||||
const sinB = (n1/n2) * Math.sin(a*Math.PI/180);
|
||||
const tir = Math.abs(sinB) > 1;
|
||||
document.getElementById('p36-tir-lab').style.display = tir ? 'inline' : 'none';
|
||||
if(tir){
|
||||
document.getElementById('p36-bv').textContent = '—';
|
||||
} else {
|
||||
const b = Math.asin(sinB) * 180/Math.PI;
|
||||
document.getElementById('p36-bv').textContent = b.toFixed(1);
|
||||
}
|
||||
const result = window.OPTICS.refractRay(230, 140, a, n1, n2, 90);
|
||||
let bg = '';
|
||||
/* верхняя среда */
|
||||
bg += '<rect x="0" y="0" width="460" height="140" fill="rgba(186,230,253,0.25)"/>';
|
||||
/* нижняя среда */
|
||||
bg += '<rect x="0" y="140" width="460" height="140" fill="rgba(59,130,246,0.18)"/>';
|
||||
bg += '<text x="20" y="30" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#0f172a">n₁ = '+n1+'</text>';
|
||||
bg += '<text x="20" y="270" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#0f172a">n₂ = '+n2+'</text>';
|
||||
svg.innerHTML = bg + result.svg;
|
||||
}
|
||||
['p36-a','p36-n1','p36-n2'].forEach(id => document.getElementById(id).addEventListener('input', d));
|
||||
document.getElementById('p36-n1').addEventListener('change', d);
|
||||
document.getElementById('p36-n2').addEventListener('change', d);
|
||||
d();
|
||||
}
|
||||
function _p36_quiz(){
|
||||
const QS = [
|
||||
{sit:'воздух → вода', ans:'L', why:'В более плотную: $\\alpha > \\beta$, луч ближе к нормали.'},
|
||||
{sit:'вода → воздух', ans:'B', why:'Из более плотной: $\\alpha < \\beta$.'},
|
||||
{sit:'воздух → стекло', ans:'L', why:'$\\alpha > \\beta$.'},
|
||||
{sit:'стекло → воздух', ans:'B', why:'$\\alpha < \\beta$.'},
|
||||
{sit:'воздух → алмаз', ans:'L', why:'$n_{алмаза} = 2{,}42$, очень плотный — $\\alpha \\gg \\beta$.'}
|
||||
];
|
||||
let i = 0, ok = 0;
|
||||
function r(){
|
||||
const q = QS[i]; const w = document.getElementById('p36-quiz');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0">'+q.sit+'</div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><button class="btn" data-p="L"><b>$\\alpha > \\beta$</b></button><button class="btn" data-p="B"><b>$\\alpha < \\beta$</b></button></div>'
|
||||
+'<div class="feedback" id="p36-q-fb"></div>';
|
||||
document.getElementById('p36-quiz-r').textContent = (i+1);
|
||||
document.getElementById('p36-quiz-ok').textContent = ok;
|
||||
w.querySelectorAll('[data-p]').forEach(b=>{
|
||||
b.addEventListener('click', ()=>{
|
||||
if(b.disabled) return; w.querySelectorAll('[data-p]').forEach(x=>x.disabled=true);
|
||||
const fb = document.getElementById('p36-q-fb');
|
||||
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p36-q'); bumpProgress('p36',4); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
|
||||
document.getElementById('p36-quiz-ok').textContent = ok;
|
||||
renderMath(w);
|
||||
});
|
||||
});
|
||||
renderMath(w);
|
||||
}
|
||||
document.getElementById('p36-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
|
||||
r();
|
||||
}
|
||||
function _p36_dnd(){
|
||||
/* по возрастанию n: воздух 1, вода 1.33, стекло 1.5, алмаз 2.42 */
|
||||
const items = [
|
||||
{id:'a',cat:'r1',html:'воздух (1)'},
|
||||
{id:'w',cat:'r2',html:'вода (1{,}33)'},
|
||||
{id:'g',cat:'r3',html:'стекло (1{,}5)'},
|
||||
{id:'d',cat:'r4',html:'алмаз (2{,}42)'}
|
||||
];
|
||||
const dnd = setupSorter({ poolId:'p36-dnd-pool', scopeSelector:'#sec-p36', cats:['r1','r2','r3','r4'], items, columnLayout:false });
|
||||
document.getElementById('p36-dnd-check').addEventListener('click', ()=>{
|
||||
const fb = document.getElementById('p36-dnd-fb'); let wr = 0;
|
||||
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
|
||||
if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p36-dnd'); bumpProgress('p36',20); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; }
|
||||
});
|
||||
document.getElementById('p36-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p36-dnd-fb').style.display='none'; });
|
||||
}
|
||||
function _p36_tasks(){
|
||||
const TASKS = [
|
||||
{q:'$n_1 = 1$, $n_2 = 1{,}5$, $\\alpha = 30$°. Найди $\\beta$ (°, до 0,1).', ans:19.5, tol:0.5, why:'$\\sin\\beta = \\sin30/1{,}5 = 0{,}333$, $\\beta = 19{,}5$°.'},
|
||||
{q:'$n_1 = 1{,}33$ (вода), $n_2 = 1$, $\\alpha = 45$°. Найди $\\beta$ (°).', ans:70.5, tol:1, why:'$\\sin\\beta = 1{,}33 \\sin45/1 = 0{,}941$, $\\beta \\approx 70{,}5$°.'},
|
||||
{q:'$\\alpha = 60$°, воздух → стекло (n=1,5). Найди $\\beta$ (°).', ans:35.3, tol:1, why:'$\\sin\\beta = \\sin60/1{,}5 = 0{,}577$, $\\beta \\approx 35{,}3$°.'},
|
||||
{q:'В алмазе ($n=2{,}42$) полное внутр. отражение начинается при $\\alpha > $? (°)', ans:24.4, tol:1, why:'$\\sin\\alpha_{кр} = 1/n = 1/2{,}42 = 0{,}413$, $\\alpha_{кр} = 24{,}4$°.'},
|
||||
{q:'Свет идёт перпендикулярно поверхности ($\\alpha = 0$). Куда пойдёт?', ans:0, tol:0.5, why:'Без преломления, $\\beta = 0$.'}
|
||||
];
|
||||
let i = 0, ok = 0, done = 0, aw = false;
|
||||
function r(){
|
||||
const t = TASKS[i]; const w = document.getElementById('p36-task');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px"><b>'+(i+1)+'.</b> '+t.q+'</div>'
|
||||
+'<div class="boss-row"><input type="number" step="0.1" class="tinp" id="p36-tinp" style="width:140px"><button class="btn primary" id="p36-tgo">Ответ</button><button class="btn" id="p36-thn">Подск.</button><button class="btn" id="p36-tn">След.</button></div>'
|
||||
+'<div class="boss-hint-txt" id="p36-tht">'+t.why+'</div><div class="feedback" id="p36-tfb"></div>';
|
||||
document.getElementById('p36-task-i').textContent = (i+1);
|
||||
document.getElementById('p36-task-ok').textContent = ok;
|
||||
document.getElementById('p36-tgo').addEventListener('click', ()=>{
|
||||
const v = parseFloat((document.getElementById('p36-tinp').value || '').replace(',','.'));
|
||||
const fb = document.getElementById('p36-tfb');
|
||||
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Число.'; return; }
|
||||
done++;
|
||||
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+t.why; addXp(4,'p36-t'); bumpProgress('p36',6); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
|
||||
document.getElementById('p36-task-ok').textContent = ok;
|
||||
renderMath(w);
|
||||
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p36-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p36-bonus'); bumpProgress('p36',15); }, 500); }
|
||||
});
|
||||
document.getElementById('p36-thn').addEventListener('click', ()=>{ document.getElementById('p36-tht').classList.toggle('show'); });
|
||||
document.getElementById('p36-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
|
||||
renderMath(w);
|
||||
}
|
||||
r();
|
||||
}
|
||||
|
||||
/* ======== §37 — Линзы. Оптическая сила ======== */
|
||||
function build_p37(){
|
||||
const box = document.getElementById('p37-body'); let h = '';
|
||||
h += makeCard('theory', 'Что такое линза', '§ 37.1',
|
||||
'<p><b>Линза</b> — прозрачное тело, ограниченное двумя сферическими (или одной плоской и одной сферической) поверхностями.</p>'
|
||||
+'<p>Два типа:</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li><b>Собирающая (выпуклая)</b>: толще в середине. Параллельные лучи собираются в точке $F$ — фокусе.</li>'
|
||||
+'<li><b>Рассеивающая (вогнутая)</b>: тоньше в середине. Параллельные лучи расходятся, как будто исходят из мнимого фокуса.</li>'
|
||||
+'</ul>'
|
||||
+'<p><b>Главная оптическая ось</b> — прямая через центры обеих сфер.</p>'
|
||||
);
|
||||
h += makeCard('rule', 'Оптическая сила', '§ 37.2',
|
||||
'<p><b>Оптическая сила линзы</b>:</p>'
|
||||
+'<p style="text-align:center;margin:8px 0">$$D = \\dfrac{1}{F}$$</p>'
|
||||
+'<p>Единица — <b>диоптрия</b> (дптр) = $1/$м.</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>$D > 0$ — собирающая линза;</li>'
|
||||
+'<li>$D < 0$ — рассеивающая;</li>'
|
||||
+'<li>чем больше $|D|$, тем сильнее линза.</li>'
|
||||
+'</ul>'
|
||||
+'<p>Очки $+2$ дптр $\\to F = 0{,}5$ м (собирающая). Очки $-3$ дптр $\\to F = -0{,}33$ м (рассеивающая).</p>'
|
||||
);
|
||||
h += makeCard('example', 'Применение', '§ 37.3',
|
||||
'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>Очки.</li>'
|
||||
+'<li>Лупа, микроскоп, телескоп.</li>'
|
||||
+'<li>Объектив фотоаппарата, проектора.</li>'
|
||||
+'<li>Хрусталик глаза.</li>'
|
||||
+'</ul>'
|
||||
);
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Линза и фокусы</div></div>'
|
||||
+'<div class="wg-help">Выбери тип линзы и фокусное расстояние.</div>'
|
||||
+'<div class="sliders" style="margin-bottom:10px"><label>Тип: <select id="p37-k" class="tinp" style="width:auto;padding:6px 10px"><option value="converging">собирающая</option><option value="diverging">рассеивающая</option></select></label>'
|
||||
+'<label>$F$ (px): <b id="p37-fv">80</b><input type="range" id="p37-f" min="40" max="160" step="10" value="80"></label></div>'
|
||||
+'<svg id="p37-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Калькулятор $D = 1/F$</div></div>'
|
||||
+'<div class="sliders" style="margin-bottom:10px"><label>$F$, м: <b id="p37-fmv">0.5</b><input type="range" id="p37-fm" min="-2" max="2" step="0.05" value="0.5"></label></div>'
|
||||
+'<div class="score-display" style="margin-top:8px"><span>$D$ = <b id="p37-dv">2.0</b> дптр</span><span>Тип: <b id="p37-tk">собирающая</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Собирающая или рассеивающая?</div></div>'
|
||||
+'<div id="p37-dnd-pool"></div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px"><div class="drop-box"><h5>Собирающая</h5><div class="drop-items" data-cat="c"></div></div><div class="drop-box"><h5>Рассеивающая</h5><div class="drop-items" data-cat="d"></div></div></div>'
|
||||
+'<div class="actions"><button class="btn primary" id="p37-dnd-check">Проверить</button><button class="btn" id="p37-dnd-reset">Сброс</button></div>'
|
||||
+'<div class="feedback" id="p37-dnd-fb"></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 задач</div></div>'
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p37-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p37-task-i">1</b>/5</span><span>Правильно: <b id="p37-task-ok">0</b></span></div></div>';
|
||||
box.innerHTML = h + secNavFor('p37') + readButton('p37');
|
||||
renderMath(box); wireReadBtn('p37');
|
||||
_p37_lens(); _p37_calc(); _p37_dnd(); _p37_tasks();
|
||||
}
|
||||
function _p37_lens(){
|
||||
const svg = document.getElementById('p37-sim'); if(!svg) return;
|
||||
function d(){
|
||||
const k = document.getElementById('p37-k').value;
|
||||
const F = +document.getElementById('p37-f').value;
|
||||
document.getElementById('p37-fv').textContent = F;
|
||||
svg.innerHTML = window.OPTICS.thinLens(230, 100, 70, F, k);
|
||||
}
|
||||
['p37-k','p37-f'].forEach(id => document.getElementById(id).addEventListener('input', d));
|
||||
document.getElementById('p37-k').addEventListener('change', d);
|
||||
d();
|
||||
}
|
||||
function _p37_calc(){
|
||||
function u(){
|
||||
const F = +document.getElementById('p37-fm').value;
|
||||
document.getElementById('p37-fmv').textContent = F.toFixed(2);
|
||||
if(F === 0){
|
||||
document.getElementById('p37-dv').textContent = '∞';
|
||||
document.getElementById('p37-tk').textContent = '—';
|
||||
return;
|
||||
}
|
||||
const D = 1/F;
|
||||
document.getElementById('p37-dv').textContent = D.toFixed(2);
|
||||
document.getElementById('p37-tk').textContent = F > 0 ? 'собирающая' : 'рассеивающая';
|
||||
}
|
||||
document.getElementById('p37-fm').addEventListener('input', u); u();
|
||||
}
|
||||
function _p37_dnd(){
|
||||
const items = [
|
||||
{id:'a',cat:'c',html:'выпуклая (+)'},
|
||||
{id:'b',cat:'c',html:'$D = +3$ дптр'},
|
||||
{id:'c',cat:'c',html:'лупа'},
|
||||
{id:'d',cat:'c',html:'хрусталик глаза'},
|
||||
{id:'e',cat:'d',html:'вогнутая (−)'},
|
||||
{id:'f',cat:'d',html:'$D = -2$ дптр'},
|
||||
{id:'g',cat:'d',html:'очки для близоруких'},
|
||||
{id:'h',cat:'d',html:'рассеивает лучи'}
|
||||
];
|
||||
const dnd = setupSorter({ poolId:'p37-dnd-pool', scopeSelector:'#sec-p37', cats:['c','d'], items, columnLayout:false });
|
||||
document.getElementById('p37-dnd-check').addEventListener('click', ()=>{
|
||||
const fb = document.getElementById('p37-dnd-fb'); let wr = 0;
|
||||
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
|
||||
if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p37-dnd'); bumpProgress('p37',20); renderMath(fb); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; renderMath(fb); }
|
||||
});
|
||||
document.getElementById('p37-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p37-dnd-fb').style.display='none'; });
|
||||
}
|
||||
function _p37_tasks(){
|
||||
const TASKS = [
|
||||
{q:'$F = 25$ см $= 0{,}25$ м. Найди $D$ (дптр).', ans:4, tol:0.1, why:'$D = 1/0{,}25 = 4$ дптр.'},
|
||||
{q:'$D = +5$ дптр. Найди $F$ (см).', ans:20, tol:0.5, why:'$F = 1/5 = 0{,}2$ м $= 20$ см.'},
|
||||
{q:'$D = -2$ дптр. Какой $F$ (м, с знаком)?', ans:-0.5, tol:0.05, why:'$F = 1/(-2) = -0{,}5$ м.'},
|
||||
{q:'Очки +1.5 дптр. Какое фокусное расстояние (см)?', ans:67, tol:2, why:'$F = 1/1{,}5 \\approx 0{,}67$ м $= 67$ см.'},
|
||||
{q:'У хрусталика глаза $D = 60$ дптр. Найди $F$ в мм.', ans:17, tol:1, why:'$F = 1/60 \\approx 0{,}017$ м = $17$ мм.'}
|
||||
];
|
||||
let i = 0, ok = 0, done = 0, aw = false;
|
||||
function r(){
|
||||
const t = TASKS[i]; const w = document.getElementById('p37-task');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px"><b>'+(i+1)+'.</b> '+t.q+'</div>'
|
||||
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p37-tinp" style="width:140px"><button class="btn primary" id="p37-tgo">Ответ</button><button class="btn" id="p37-thn">Подск.</button><button class="btn" id="p37-tn">След.</button></div>'
|
||||
+'<div class="boss-hint-txt" id="p37-tht">'+t.why+'</div><div class="feedback" id="p37-tfb"></div>';
|
||||
document.getElementById('p37-task-i').textContent = (i+1);
|
||||
document.getElementById('p37-task-ok').textContent = ok;
|
||||
document.getElementById('p37-tgo').addEventListener('click', ()=>{
|
||||
const v = parseFloat((document.getElementById('p37-tinp').value || '').replace(',','.'));
|
||||
const fb = document.getElementById('p37-tfb');
|
||||
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Число.'; return; }
|
||||
done++;
|
||||
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+t.why; addXp(4,'p37-t'); bumpProgress('p37',6); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
|
||||
document.getElementById('p37-task-ok').textContent = ok;
|
||||
renderMath(w);
|
||||
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p37-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p37-bonus'); bumpProgress('p37',15); }, 500); }
|
||||
});
|
||||
document.getElementById('p37-thn').addEventListener('click', ()=>{ document.getElementById('p37-tht').classList.toggle('show'); });
|
||||
document.getElementById('p37-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
|
||||
renderMath(w);
|
||||
}
|
||||
r();
|
||||
}
|
||||
|
||||
/* ======== §38 — Построение изображений в линзах ======== */
|
||||
function build_p38(){
|
||||
const box = document.getElementById('p38-body'); let h = '';
|
||||
h += makeCard('theory', 'Формула тонкой линзы', '§ 38.1',
|
||||
'<p>Расстояния от предмета $d$ и от изображения $f$ до линзы связаны:</p>'
|
||||
+'<p style="text-align:center;margin:8px 0">$$\\dfrac{1}{F} = \\dfrac{1}{d} + \\dfrac{1}{f}$$</p>'
|
||||
+'<p>Из неё $f = \\dfrac{dF}{d - F}$.</p>'
|
||||
+'<p>Если $f > 0$ — изображение <b>действительное</b> (с той стороны линзы, на экране). Если $f < 0$ — <b>мнимое</b> (с той же стороны, где предмет).</p>'
|
||||
);
|
||||
h += makeCard('rule', '3 «золотых» луча', '§ 38.2',
|
||||
'<ol style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>Луч <b>через центр</b> линзы — не преломляется.</li>'
|
||||
+'<li>Луч <b>параллельный оси</b> — после линзы проходит через дальний $F$.</li>'
|
||||
+'<li>Луч <b>через ближний $F$</b> — после линзы идёт параллельно оси.</li>'
|
||||
+'</ol>'
|
||||
+'<p>Любые два из этих лучей пересекутся в точке изображения.</p>'
|
||||
);
|
||||
h += makeCard('example', 'Зависимость от расстояния', '§ 38.3',
|
||||
'<p>В собирающей линзе ($F > 0$):</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>$d > 2F$ — изображение <b>уменьшенное</b>, перевёрнутое, действительное (фотоаппарат).</li>'
|
||||
+'<li>$d = 2F$ — равное, перевёрнутое, действительное.</li>'
|
||||
+'<li>$F < d < 2F$ — <b>увеличенное</b>, перевёрнутое, действительное (проектор).</li>'
|
||||
+'<li>$d = F$ — на бесконечности (параллельные лучи).</li>'
|
||||
+'<li>$d < F$ — <b>увеличенное</b>, прямое, мнимое (лупа).</li>'
|
||||
+'</ul>'
|
||||
);
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Конструктор изображения</div></div>'
|
||||
+'<div class="wg-help">Меняй $F$ и $d$ — наблюдай, как меняется изображение. Зелёный — действительное, фиолетовый пунктир — мнимое.</div>'
|
||||
+'<div class="sliders" style="margin-bottom:10px"><label>$F$, см: <b id="p38-fv">10</b><input type="range" id="p38-f" min="5" max="20" step="1" value="10"></label>'
|
||||
+'<label>$d$, см: <b id="p38-dv">15</b><input type="range" id="p38-d" min="3" max="40" step="1" value="15"></label></div>'
|
||||
+'<svg id="p38-sim" viewBox="0 0 600 240" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
|
||||
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:3px">'
|
||||
+'<span>$f$ = <b id="p38-fs">30</b> см</span>'
|
||||
+'<span>Увеличение: <b id="p38-mg">2.0</b>x</span>'
|
||||
+'<span>Тип: <b id="p38-tp">действительное, перевёрнутое</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Какое изображение?</div></div>'
|
||||
+'<div class="wg-help">Тип изображения по условиям.</div>'
|
||||
+'<div id="p38-quiz"></div>'
|
||||
+'<div class="actions"><button class="btn" id="p38-quiz-next">Следующий</button></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p38-quiz-r">1</b>/5</span><span>Правильно: <b id="p38-quiz-ok">0</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">DnD устройств</div></div>'
|
||||
+'<div class="wg-help">К какому случаю относится прибор?</div>'
|
||||
+'<div id="p38-dnd-pool"></div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:10px"><div class="drop-box"><h5>$d > 2F$ (фото)</h5><div class="drop-items" data-cat="far"></div></div><div class="drop-box"><h5>$F < d < 2F$ (проектор)</h5><div class="drop-items" data-cat="mid"></div></div><div class="drop-box"><h5>$d < F$ (лупа)</h5><div class="drop-items" data-cat="lupa"></div></div></div>'
|
||||
+'<div class="actions"><button class="btn primary" id="p38-dnd-check">Проверить</button><button class="btn" id="p38-dnd-reset">Сброс</button></div>'
|
||||
+'<div class="feedback" id="p38-dnd-fb"></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 задач</div></div>'
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p38-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p38-task-i">1</b>/5</span><span>Правильно: <b id="p38-task-ok">0</b></span></div></div>';
|
||||
box.innerHTML = h + secNavFor('p38') + readButton('p38');
|
||||
renderMath(box); wireReadBtn('p38');
|
||||
_p38_constr(); _p38_quiz(); _p38_dnd(); _p38_tasks();
|
||||
}
|
||||
function _p38_constr(){
|
||||
const svg = document.getElementById('p38-sim'); if(!svg) return;
|
||||
function d(){
|
||||
const F = +document.getElementById('p38-f').value;
|
||||
const dist = +document.getElementById('p38-d').value;
|
||||
document.getElementById('p38-fv').textContent = F;
|
||||
document.getElementById('p38-dv').textContent = dist;
|
||||
const res = window.OPTICS.buildLensImage(F, dist, 30);
|
||||
const f = res.f, h2 = res.h2, virtual = res.virtual;
|
||||
document.getElementById('p38-fs').textContent = isFinite(f) ? f.toFixed(1) : '∞';
|
||||
document.getElementById('p38-mg').textContent = isFinite(f) ? Math.abs(h2/30).toFixed(2) : '∞';
|
||||
const type = virtual ? (h2 > 0 ? 'мнимое, прямое, увеличенное' : 'мнимое, прямое') : (h2 < 0 ? 'действительное, перевёрнутое' : 'действительное');
|
||||
document.getElementById('p38-tp').textContent = type;
|
||||
|
||||
/* Рисуем SVG */
|
||||
const cx = 300, cy = 130;
|
||||
const sc = 4; /* px per см */
|
||||
let s = '';
|
||||
/* ось */
|
||||
s += '<line x1="50" y1="'+cy+'" x2="550" y2="'+cy+'" stroke="#cbd5e1" stroke-width="1.2"/>';
|
||||
/* линза */
|
||||
s += window.OPTICS.thinLens(cx, cy, 60, F*sc, 'converging');
|
||||
/* предмет — слева */
|
||||
const objX = cx - dist*sc;
|
||||
const objH = 60; /* высота 30 см = 60 px */
|
||||
s += '<line x1="'+objX+'" y1="'+cy+'" x2="'+objX+'" y2="'+(cy-objH)+'" stroke="#0f172a" stroke-width="3"/>';
|
||||
s += '<polygon points="'+objX+','+(cy-objH)+' '+(objX-4)+','+(cy-objH+7)+' '+(objX+4)+','+(cy-objH+7)+'" fill="#0f172a"/>';
|
||||
/* изображение */
|
||||
if(isFinite(f) && !virtual){
|
||||
const imgX = cx + f*sc;
|
||||
const imgY = cy - h2*sc/15; /* h2 от исходн. h=30 — масштабируем */
|
||||
s += '<line x1="'+imgX+'" y1="'+cy+'" x2="'+imgX+'" y2="'+(cy - h2*2)+'" stroke="#7c3aed" stroke-width="3"/>';
|
||||
const tip = cy - h2*2;
|
||||
s += '<polygon points="'+imgX+','+tip+' '+(imgX-4)+','+(tip+(h2>0?7:-7))+' '+(imgX+4)+','+(tip+(h2>0?7:-7))+'" fill="#7c3aed"/>';
|
||||
} else if(isFinite(f) && virtual){
|
||||
const imgX = cx + f*sc; /* отрицательное f → слева */
|
||||
s += '<line x1="'+imgX+'" y1="'+cy+'" x2="'+imgX+'" y2="'+(cy - h2*2)+'" stroke="#a78bfa" stroke-width="2.5" stroke-dasharray="5 3"/>';
|
||||
const tip = cy - h2*2;
|
||||
s += '<polygon points="'+imgX+','+tip+' '+(imgX-4)+','+(tip+7)+' '+(imgX+4)+','+(tip+7)+'" fill="#a78bfa" opacity="0.6"/>';
|
||||
}
|
||||
svg.innerHTML = s;
|
||||
}
|
||||
document.getElementById('p38-f').addEventListener('input', d);
|
||||
document.getElementById('p38-d').addEventListener('input', d);
|
||||
d();
|
||||
}
|
||||
function _p38_quiz(){
|
||||
const QS = [
|
||||
{sit:'$d > 2F$ (например, $d = 30$, $F = 10$)', ans:'A', why:'Уменьшенное, перевёрнутое, действительное.'},
|
||||
{sit:'$d = 2F$', ans:'B', why:'Равное, перевёрнутое, действительное.'},
|
||||
{sit:'$F < d < 2F$ (проектор)', ans:'C', why:'Увеличенное, перевёрнутое, действительное.'},
|
||||
{sit:'$d < F$ (лупа)', ans:'D', why:'Увеличенное, прямое, мнимое.'},
|
||||
{sit:'$d = F$', ans:'E', why:'На бесконечности — параллельные лучи.'}
|
||||
];
|
||||
let i = 0, ok = 0;
|
||||
function r(){
|
||||
const q = QS[i]; const w = document.getElementById('p38-quiz');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0">'+q.sit+'</div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr;gap:6px">'
|
||||
+'<button class="btn" data-p="A">A. Уменьш., перевёрн., действ.</button>'
|
||||
+'<button class="btn" data-p="B">B. Равное, перевёрн., действ.</button>'
|
||||
+'<button class="btn" data-p="C">C. Увелич., перевёрн., действ.</button>'
|
||||
+'<button class="btn" data-p="D">D. Увелич., прямое, мнимое</button>'
|
||||
+'<button class="btn" data-p="E">E. На бесконечности</button>'
|
||||
+'</div><div class="feedback" id="p38-q-fb"></div>';
|
||||
document.getElementById('p38-quiz-r').textContent = (i+1);
|
||||
document.getElementById('p38-quiz-ok').textContent = ok;
|
||||
w.querySelectorAll('[data-p]').forEach(b=>{
|
||||
b.addEventListener('click', ()=>{
|
||||
if(b.disabled) return; w.querySelectorAll('[data-p]').forEach(x=>x.disabled=true);
|
||||
const fb = document.getElementById('p38-q-fb');
|
||||
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p38-q'); bumpProgress('p38',4); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
|
||||
document.getElementById('p38-quiz-ok').textContent = ok;
|
||||
});
|
||||
});
|
||||
}
|
||||
document.getElementById('p38-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
|
||||
r();
|
||||
}
|
||||
function _p38_dnd(){
|
||||
const items = [
|
||||
{id:'p',cat:'far',html:'фотоаппарат'},
|
||||
{id:'eye',cat:'far',html:'глаз (далёкий предмет)'},
|
||||
{id:'mp',cat:'mid',html:'проектор'},
|
||||
{id:'film',cat:'mid',html:'кинопроектор'},
|
||||
{id:'l1',cat:'lupa',html:'лупа'},
|
||||
{id:'l2',cat:'lupa',html:'часы под лупой'}
|
||||
];
|
||||
const dnd = setupSorter({ poolId:'p38-dnd-pool', scopeSelector:'#sec-p38', cats:['far','mid','lupa'], items, columnLayout:false });
|
||||
document.getElementById('p38-dnd-check').addEventListener('click', ()=>{
|
||||
const fb = document.getElementById('p38-dnd-fb'); let wr = 0;
|
||||
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
|
||||
if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p38-dnd'); bumpProgress('p38',20); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; }
|
||||
});
|
||||
document.getElementById('p38-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p38-dnd-fb').style.display='none'; });
|
||||
}
|
||||
function _p38_tasks(){
|
||||
const TASKS = [
|
||||
{q:'$F = 20$ см, $d = 30$ см. Найди $f$ (см).', ans:60, tol:1, why:'$f = dF/(d-F) = 30\\cdot20/(30-20) = 60$ см.'},
|
||||
{q:'$F = 10$ см, $d = 15$ см. Найди $f$ (см).', ans:30, tol:1, why:'$f = 15\\cdot10/5 = 30$ см.'},
|
||||
{q:'$F = 20$ см, $d = 60$ см. Найди $f$ (см).', ans:30, tol:1, why:'$f = 60\\cdot20/40 = 30$ см.'},
|
||||
{q:'Предмет высотой 5 см, $d = 30$ см, $f = 60$ см. Найди высоту изображения (см).', ans:10, tol:0.5, why:'$h_2 = h \\cdot f/d = 5 \\cdot 60/30 = 10$ см.'},
|
||||
{q:'$F = 15$ см, $d = 10$ см (лупа). Найди $|f|$ (см).', ans:30, tol:1, why:'$f = 10\\cdot15/(10-15) = -30$, |f| = 30 см. Мнимое.'}
|
||||
];
|
||||
let i = 0, ok = 0, done = 0, aw = false;
|
||||
function r(){
|
||||
const t = TASKS[i]; const w = document.getElementById('p38-task');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px"><b>'+(i+1)+'.</b> '+t.q+'</div>'
|
||||
+'<div class="boss-row"><input type="number" step="0.1" class="tinp" id="p38-tinp" style="width:140px"><button class="btn primary" id="p38-tgo">Ответ</button><button class="btn" id="p38-thn">Подск.</button><button class="btn" id="p38-tn">След.</button></div>'
|
||||
+'<div class="boss-hint-txt" id="p38-tht">'+t.why+'</div><div class="feedback" id="p38-tfb"></div>';
|
||||
document.getElementById('p38-task-i').textContent = (i+1);
|
||||
document.getElementById('p38-task-ok').textContent = ok;
|
||||
document.getElementById('p38-tgo').addEventListener('click', ()=>{
|
||||
const v = parseFloat((document.getElementById('p38-tinp').value || '').replace(',','.'));
|
||||
const fb = document.getElementById('p38-tfb');
|
||||
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Число.'; return; }
|
||||
done++;
|
||||
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+t.why; addXp(4,'p38-t'); bumpProgress('p38',6); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
|
||||
document.getElementById('p38-task-ok').textContent = ok;
|
||||
renderMath(w);
|
||||
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p38-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p38-bonus'); bumpProgress('p38',15); }, 500); }
|
||||
});
|
||||
document.getElementById('p38-thn').addEventListener('click', ()=>{ document.getElementById('p38-tht').classList.toggle('show'); });
|
||||
document.getElementById('p38-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
|
||||
renderMath(w);
|
||||
}
|
||||
r();
|
||||
}
|
||||
|
||||
/* ======== §39 — Глаз как оптическая система ======== */
|
||||
function build_p39(){
|
||||
const box = document.getElementById('p39-body'); let h = '';
|
||||
h += makeCard('theory', 'Устройство глаза', '§ 39.1',
|
||||
'<p>Глаз — сложная оптическая система:</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li><b>Роговица</b> — прозрачная передняя оболочка, преломляет свет.</li>'
|
||||
+'<li><b>Зрачок</b> — отверстие в радужной оболочке, регулирует количество света.</li>'
|
||||
+'<li><b>Хрусталик</b> — биологическая собирающая линза с переменным $F$.</li>'
|
||||
+'<li><b>Сетчатка</b> — задняя стенка глаза, на ней формируется изображение.</li>'
|
||||
+'<li><b>Зрительный нерв</b> — передаёт сигнал в мозг.</li>'
|
||||
+'</ul>'
|
||||
);
|
||||
h += makeCard('rule', 'Аккомодация', '§ 39.2',
|
||||
'<p>Расстояние от хрусталика до сетчатки <b>фиксировано</b>. А расстояние до предметов меняется. Как глаз приспосабливается?</p>'
|
||||
+'<p>Мышцы вокруг хрусталика меняют его кривизну (форму):</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>Далёкий предмет: хрусталик <b>уплощается</b>, $F$ растёт.</li>'
|
||||
+'<li>Близкий предмет: хрусталик <b>округляется</b>, $F$ уменьшается.</li>'
|
||||
+'</ul>'
|
||||
+'<p>Это и есть <b>аккомодация</b>.</p>'
|
||||
);
|
||||
h += makeCard('example', 'Расстояние наилучшего зрения', '§ 39.3',
|
||||
'<p>Расстояние, на котором глаз видит чётко без напряжения, — около <b>25 см</b>. На таком расстоянии обычно держат книгу при чтении.</p>'
|
||||
+'<p>Ближе глаз перенапрягается, дальше — теряется чёткость деталей.</p>'
|
||||
);
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Аккомодация хрусталика</div></div>'
|
||||
+'<div class="wg-help">Меняй расстояние до предмета — увидь, как меняется форма хрусталика.</div>'
|
||||
+'<div class="sliders" style="margin-bottom:10px"><label>Расстояние: <b id="p39-dv">далёкий предмет</b><input type="range" id="p39-d" min="0" max="100" step="10" value="0"></label></div>'
|
||||
+'<svg id="p39-sim" viewBox="0 0 460 220" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Элементы глаза</div></div>'
|
||||
+'<div class="wg-help">Какая часть глаза за что отвечает?</div>'
|
||||
+'<div id="p39-quiz"></div>'
|
||||
+'<div class="actions"><button class="btn" id="p39-quiz-next">Следующий</button></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p39-quiz-r">1</b>/5</span><span>Правильно: <b id="p39-quiz-ok">0</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сортировка частей глаза</div></div>'
|
||||
+'<div id="p39-dnd-pool"></div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px"><div class="drop-box"><h5>Оптические</h5><div class="drop-items" data-cat="op"></div></div><div class="drop-box"><h5>Регистрация / нерв</h5><div class="drop-items" data-cat="nr"></div></div></div>'
|
||||
+'<div class="actions"><button class="btn primary" id="p39-dnd-check">Проверить</button><button class="btn" id="p39-dnd-reset">Сброс</button></div>'
|
||||
+'<div class="feedback" id="p39-dnd-fb"></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 MCQ</div></div>'
|
||||
+'<div id="p39-mcq"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p39-mcq-i">1</b>/6</span><span>Правильно: <b id="p39-mcq-ok">0</b></span></div></div>';
|
||||
box.innerHTML = h + secNavFor('p39') + readButton('p39');
|
||||
renderMath(box); wireReadBtn('p39');
|
||||
_p39_eye(); _p39_quiz(); _p39_dnd(); _p39_mcq();
|
||||
}
|
||||
function _p39_eye(){
|
||||
const svg = document.getElementById('p39-sim'); if(!svg) return;
|
||||
function d(){
|
||||
const dval = +document.getElementById('p39-d').value;
|
||||
/* 0 — далёкий, 100 — близкий */
|
||||
document.getElementById('p39-dv').textContent = dval < 30 ? 'далёкий (бесконечность)' : dval < 70 ? 'на расстоянии 1 м' : 'близкий (25 см)';
|
||||
const acc = dval / 100;
|
||||
let s = '';
|
||||
/* «предмет» слева */
|
||||
s += window.OPTICS.lightObject(60, 130, 40, 'arrow');
|
||||
s += '<text x="60" y="170" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">предмет</text>';
|
||||
/* линии лучей */
|
||||
s += '<line x1="60" y1="130" x2="260" y2="110" stroke="#fbbf24" stroke-width="1.3"/>';
|
||||
s += '<line x1="60" y1="90" x2="260" y2="120" stroke="#fbbf24" stroke-width="1.3"/>';
|
||||
/* глаз справа */
|
||||
s += window.OPTICS.eyeDiagram(330, 115, 70, acc);
|
||||
/* подпись аккомодации */
|
||||
s += '<text x="330" y="200" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#7c3aed">хрусталик '+(acc < 0.3 ? 'уплощён' : acc < 0.7 ? 'средний' : 'округлён')+'</text>';
|
||||
svg.innerHTML = s;
|
||||
}
|
||||
document.getElementById('p39-d').addEventListener('input', d); d();
|
||||
}
|
||||
function _p39_quiz(){
|
||||
const QS = [
|
||||
{sit:'Эта часть преломляет свет на входе.', opts:['Сетчатка','Роговица','Зрачок','Нерв'], ans:1, why:'Роговица — главный преломляющий элемент.'},
|
||||
{sit:'Она меняет $F$ при изменении расстояния.', opts:['Сетчатка','Хрусталик','Радужка','Зрачок'], ans:1, why:'Хрусталик меняет форму — аккомодация.'},
|
||||
{sit:'На ней формируется изображение.', opts:['Радужка','Сетчатка','Зрачок','Зр. нерв'], ans:1, why:'Сетчатка — «экран» глаза.'},
|
||||
{sit:'Она регулирует количество света.', opts:['Радужка/зрачок','Хрусталик','Роговица','Сетчатка'], ans:0, why:'На ярком свету зрачок сужается.'},
|
||||
{sit:'Это сетчатка передаёт сигнал в мозг через…', opts:['артерию','зрительный нерв','лимфу','аккомодацию'], ans:1, why:'Зрительный нерв.'}
|
||||
];
|
||||
let i = 0, ok = 0;
|
||||
function r(){
|
||||
const q = QS[i]; const w = document.getElementById('p39-quiz');
|
||||
let html = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px"><b>'+(i+1)+'.</b> '+q.sit+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
|
||||
q.opts.forEach((o,k)=>{ html += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+o+'</button>'; });
|
||||
html += '</div><div class="feedback" id="p39-q-fb"></div>';
|
||||
w.innerHTML = html;
|
||||
document.getElementById('p39-quiz-r').textContent = (i+1);
|
||||
document.getElementById('p39-quiz-ok').textContent = ok;
|
||||
w.querySelectorAll('[data-k]').forEach(b=>{
|
||||
b.addEventListener('click', ()=>{
|
||||
if(b.disabled) return; w.querySelectorAll('[data-k]').forEach(x=>x.disabled=true);
|
||||
const k = +b.dataset.k; const fb = document.getElementById('p39-q-fb');
|
||||
if(k===q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p39-q'); bumpProgress('p39',4); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
|
||||
document.getElementById('p39-quiz-ok').textContent = ok;
|
||||
});
|
||||
});
|
||||
}
|
||||
document.getElementById('p39-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
|
||||
r();
|
||||
}
|
||||
function _p39_dnd(){
|
||||
const items = [
|
||||
{id:'r',cat:'op',html:'роговица'},{id:'h',cat:'op',html:'хрусталик'},{id:'z',cat:'op',html:'зрачок'},{id:'i',cat:'op',html:'радужка'},
|
||||
{id:'s',cat:'nr',html:'сетчатка'},{id:'n',cat:'nr',html:'зрительный нерв'},{id:'c',cat:'nr',html:'колбочки и палочки'},{id:'br',cat:'nr',html:'мозг (анализ)'}
|
||||
];
|
||||
const dnd = setupSorter({ poolId:'p39-dnd-pool', scopeSelector:'#sec-p39', cats:['op','nr'], items, columnLayout:false });
|
||||
document.getElementById('p39-dnd-check').addEventListener('click', ()=>{
|
||||
const fb = document.getElementById('p39-dnd-fb'); let wr = 0;
|
||||
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
|
||||
if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p39-dnd'); bumpProgress('p39',20); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; }
|
||||
});
|
||||
document.getElementById('p39-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p39-dnd-fb').style.display='none'; });
|
||||
}
|
||||
function _p39_mcq(){
|
||||
const QS = [
|
||||
{q:'Что играет роль линзы в глазу?', opts:['роговица','хрусталик','сетчатка','зрачок'], ans:1, why:'Хрусталик с переменным F.'},
|
||||
{q:'Что изменяется при аккомодации?', opts:['расст. до сетчатки','форма хрусталика','цвет радужки','диаметр зрачка'], ans:1, why:'Мышцы меняют форму хрусталика.'},
|
||||
{q:'Где формируется изображение?', opts:['в зрачке','на роговице','на сетчатке','в нерве'], ans:2, why:'На сетчатке.'},
|
||||
{q:'Расстояние наилучшего зрения для здорового глаза?', opts:['10 см','25 см','1 м','5 м'], ans:1, why:'25 см.'},
|
||||
{q:'Каким будет изображение на сетчатке?', opts:['прямое, мнимое','перевёрн., действ.','прямое, действ.','мнимое'], ans:1, why:'Глаз — собирающая система: перевёрнутое действительное (мозг переворачивает).'},
|
||||
{q:'Что НЕ оптический элемент глаза?', opts:['роговица','хрусталик','сетчатка','нерв'], ans:3, why:'Нерв передаёт сигнал, не преломляет свет.'}
|
||||
];
|
||||
let i = 0, ok = 0, done = 0, aw = false;
|
||||
function r(){
|
||||
const q = QS[i]; const w = document.getElementById('p39-mcq');
|
||||
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px"><b>'+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
|
||||
q.opts.forEach((o,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+o+'</button>'; });
|
||||
h += '</div><div class="feedback" id="p39-mcq-fb"></div><div class="actions"><button class="btn" id="p39-mcq-n">Следующий</button></div>';
|
||||
w.innerHTML = h;
|
||||
document.getElementById('p39-mcq-i').textContent = (i+1);
|
||||
document.getElementById('p39-mcq-ok').textContent = ok;
|
||||
w.querySelectorAll('[data-k]').forEach(b=>{
|
||||
b.addEventListener('click', ()=>{
|
||||
if(b.disabled) return; w.querySelectorAll('[data-k]').forEach(x=>x.disabled=true);
|
||||
const k = +b.dataset.k; const fb = document.getElementById('p39-mcq-fb');
|
||||
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(2,'p39-mcq'); bumpProgress('p39',3); }
|
||||
else { done++; fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
|
||||
document.getElementById('p39-mcq-ok').textContent = ok;
|
||||
if(done >= QS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p39-mcq-fb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p39-bonus'); bumpProgress('p39',15); }, 500); }
|
||||
});
|
||||
});
|
||||
document.getElementById('p39-mcq-n').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
|
||||
}
|
||||
r();
|
||||
}
|
||||
|
||||
/* ======== §40 — Дефекты зрения. Очки ======== */
|
||||
function build_p40(){
|
||||
const box = document.getElementById('p40-body'); let h = '';
|
||||
h += makeCard('theory', 'Близорукость', '§ 40.1',
|
||||
'<p><b>Близорукость (миопия)</b> — изображение далёких предметов фокусируется <b>перед</b> сетчаткой.</p>'
|
||||
+'<p>Причина: глаз слегка вытянут, или хрусталик слишком сильный. Человек хорошо видит вблизи, плохо — вдаль.</p>'
|
||||
+'<p>Лечение: <b>рассеивающие</b> линзы ($D < 0$). Они «отодвигают» фокус на сетчатку.</p>'
|
||||
);
|
||||
h += makeCard('rule', 'Дальнозоркость', '§ 40.2',
|
||||
'<p><b>Дальнозоркость (гиперметропия)</b> — изображение фокусируется <b>за</b> сетчаткой.</p>'
|
||||
+'<p>Причина: глаз короче нормы, или хрусталик слабоват. Человек плохо видит вблизи.</p>'
|
||||
+'<p>Лечение: <b>собирающие</b> линзы ($D > 0$). Они «приближают» фокус к сетчатке.</p>'
|
||||
);
|
||||
h += makeCard('example', 'Как подобрать очки', '§ 40.3',
|
||||
'<p>Сила линзы $D$ подбирается врачом-офтальмологом по таблицам.</p>'
|
||||
+'<ul style="padding-left:20px;margin:6px 0">'
|
||||
+'<li>$-1$ … $-3$ дптр — лёгкая близорукость.</li>'
|
||||
+'<li>$-3$ … $-6$ дптр — средняя.</li>'
|
||||
+'<li>$-6$ дптр и больше — сильная.</li>'
|
||||
+'</ul>'
|
||||
+'<p>Современные альтернативы: контактные линзы, лазерная коррекция.</p>'
|
||||
);
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Очки исправляют зрение</div></div>'
|
||||
+'<div class="wg-help">Выбери дефект — увидь, какие очки нужны.</div>'
|
||||
+'<div class="sliders" style="margin-bottom:10px"><label>Дефект: <select id="p40-def" class="tinp" style="width:auto;padding:6px 10px"><option value="m">близорукость</option><option value="h">дальнозоркость</option><option value="n">норма</option></select></label></div>'
|
||||
+'<svg id="p40-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
|
||||
+'<div class="score-display" style="margin-top:8px"><span>Нужны очки: <b id="p40-lens">собирающие, $D > 0$</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Какие очки?</div></div>'
|
||||
+'<div class="wg-help">Подбери тип линз для дефекта.</div>'
|
||||
+'<div id="p40-quiz"></div>'
|
||||
+'<div class="actions"><button class="btn" id="p40-quiz-next">Следующий</button></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p40-quiz-r">1</b>/5</span><span>Правильно: <b id="p40-quiz-ok">0</b></span></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">DnD признаков</div></div>'
|
||||
+'<div id="p40-dnd-pool"></div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px"><div class="drop-box"><h5>Близорукость</h5><div class="drop-items" data-cat="m"></div></div><div class="drop-box"><h5>Дальнозоркость</h5><div class="drop-items" data-cat="h"></div></div></div>'
|
||||
+'<div class="actions"><button class="btn primary" id="p40-dnd-check">Проверить</button><button class="btn" id="p40-dnd-reset">Сброс</button></div>'
|
||||
+'<div class="feedback" id="p40-dnd-fb"></div></div>';
|
||||
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 задач</div></div>'
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p40-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p40-task-i">1</b>/5</span><span>Правильно: <b id="p40-task-ok">0</b></span></div></div>';
|
||||
box.innerHTML = h + secNavFor('p40') + readButton('p40');
|
||||
renderMath(box); wireReadBtn('p40');
|
||||
_p40_glass(); _p40_quiz(); _p40_dnd(); _p40_tasks();
|
||||
}
|
||||
function _p40_glass(){
|
||||
const svg = document.getElementById('p40-sim'); if(!svg) return;
|
||||
function d(){
|
||||
const def = document.getElementById('p40-def').value;
|
||||
let s = '';
|
||||
const cx = 320, cy = 100;
|
||||
/* глаз */
|
||||
s += window.OPTICS.eyeDiagram(cx, cy, 50, 0.3);
|
||||
/* лучи входящие */
|
||||
const labelLens = def === 'm' ? 'рассеивающие, $D < 0$' : def === 'h' ? 'собирающие, $D > 0$' : 'не нужны';
|
||||
document.getElementById('p40-lens').innerHTML = labelLens;
|
||||
/* для близорукости: фокус ПЕРЕД сетчаткой; рассеивающая линза «отодвигает» */
|
||||
/* для дальнозоркости: фокус ЗА; собирающая «приближает» */
|
||||
/* нарисуем фокусное пятно условно */
|
||||
let focusX = 0;
|
||||
let lensType = '';
|
||||
if(def === 'm'){ focusX = cx - 5; lensType = 'diverging'; }
|
||||
else if(def === 'h'){ focusX = cx + 60; lensType = 'converging'; }
|
||||
else { focusX = cx + 40; }
|
||||
/* очки спереди глаза */
|
||||
if(def !== 'n'){
|
||||
s += window.OPTICS.thinLens(200, cy, 50, 80, lensType);
|
||||
}
|
||||
/* лучи */
|
||||
s += '<line x1="80" y1="80" x2="200" y2="80" stroke="#fbbf24" stroke-width="1.4"/>';
|
||||
s += '<line x1="80" y1="120" x2="200" y2="120" stroke="#fbbf24" stroke-width="1.4"/>';
|
||||
s += '<line x1="200" y1="80" x2="'+focusX+'" y2="'+cy+'" stroke="#fbbf24" stroke-width="1.4"/>';
|
||||
s += '<line x1="200" y1="120" x2="'+focusX+'" y2="'+cy+'" stroke="#fbbf24" stroke-width="1.4"/>';
|
||||
s += '<circle cx="'+focusX+'" cy="'+cy+'" r="4" fill="#dc2626"/>';
|
||||
s += '<text x="'+focusX+'" y="'+(cy+18)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#dc2626">фокус</text>';
|
||||
if(def === 'n') s += '<text x="200" y="40" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#10b981">нормальное зрение</text>';
|
||||
if(def === 'm') s += '<text x="200" y="40" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#dc2626">близорукость: фокус до сетчатки</text>';
|
||||
if(def === 'h') s += '<text x="200" y="40" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#dc2626">дальнозоркость: фокус за сетчаткой</text>';
|
||||
svg.innerHTML = s;
|
||||
}
|
||||
document.getElementById('p40-def').addEventListener('change', d); d();
|
||||
}
|
||||
function _p40_quiz(){
|
||||
const QS = [
|
||||
{sit:'У человека близорукость', ans:'-', why:'Нужны рассеивающие, $D < 0$.'},
|
||||
{sit:'Дальнозоркость', ans:'+', why:'Нужны собирающие, $D > 0$.'},
|
||||
{sit:'У школьника плохо видит вдаль', ans:'-', why:'Признак близорукости.'},
|
||||
{sit:'У бабушки плохо видит вблизи (мелкий шрифт)', ans:'+', why:'Возрастная дальнозоркость.'},
|
||||
{sit:'$D = +2$ дптр прописал врач', ans:'+', why:'Собирающие — для дальнозоркости.'}
|
||||
];
|
||||
let i = 0, ok = 0;
|
||||
function r(){
|
||||
const q = QS[i]; const w = document.getElementById('p40-quiz');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0">'+q.sit+'</div>'
|
||||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><button class="btn" data-p="-"><b>$D < 0$ (рассеив.)</b></button><button class="btn" data-p="+"><b>$D > 0$ (собир.)</b></button></div>'
|
||||
+'<div class="feedback" id="p40-q-fb"></div>';
|
||||
document.getElementById('p40-quiz-r').textContent = (i+1);
|
||||
document.getElementById('p40-quiz-ok').textContent = ok;
|
||||
w.querySelectorAll('[data-p]').forEach(b=>{
|
||||
b.addEventListener('click', ()=>{
|
||||
if(b.disabled) return; w.querySelectorAll('[data-p]').forEach(x=>x.disabled=true);
|
||||
const fb = document.getElementById('p40-q-fb');
|
||||
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+q.why; addXp(3,'p40-q'); bumpProgress('p40',4); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+q.why; }
|
||||
document.getElementById('p40-quiz-ok').textContent = ok;
|
||||
renderMath(w);
|
||||
});
|
||||
});
|
||||
renderMath(w);
|
||||
}
|
||||
document.getElementById('p40-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
|
||||
r();
|
||||
}
|
||||
function _p40_dnd(){
|
||||
const items = [
|
||||
{id:'a',cat:'m',html:'фокус ПЕРЕД сетчаткой'},
|
||||
{id:'b',cat:'m',html:'плохо видит вдаль'},
|
||||
{id:'c',cat:'m',html:'нужны рассеив. линзы'},
|
||||
{id:'d',cat:'m',html:'$D < 0$'},
|
||||
{id:'e',cat:'h',html:'фокус ЗА сетчаткой'},
|
||||
{id:'f',cat:'h',html:'плохо видит вблизи'},
|
||||
{id:'g',cat:'h',html:'нужны собир. линзы'},
|
||||
{id:'i',cat:'h',html:'$D > 0$'}
|
||||
];
|
||||
const dnd = setupSorter({ poolId:'p40-dnd-pool', scopeSelector:'#sec-p40', cats:['m','h'], items, columnLayout:false });
|
||||
document.getElementById('p40-dnd-check').addEventListener('click', ()=>{
|
||||
const fb = document.getElementById('p40-dnd-fb'); let wr = 0;
|
||||
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
|
||||
if(wr===0){ fb.className='feedback ok'; fb.innerHTML='✓ +15 XP'; addXp(15,'p40-dnd'); bumpProgress('p40',20); renderMath(fb); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wr+'.'; renderMath(fb); }
|
||||
});
|
||||
document.getElementById('p40-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p40-dnd-fb').style.display='none'; });
|
||||
}
|
||||
function _p40_tasks(){
|
||||
const TASKS = [
|
||||
{q:'$F = -0{,}5$ м. Найди $D$ (дптр).', ans:-2, tol:0.1, why:'$D = 1/-0{,}5 = -2$ дптр.'},
|
||||
{q:'Очки $D = +1{,}5$ дптр. Какой $F$ (см)?', ans:67, tol:2, why:'$F = 1/1{,}5 \\approx 0{,}67$ м = 67 см.'},
|
||||
{q:'Близорукому нужны $D = -3$ дптр. Какой |F| (см)?', ans:33, tol:1, why:'|F| = 1/3 м = 33 см.'},
|
||||
{q:'Какой знак $D$ для очков от близорукости? (введи +1 или -1)', ans:-1, tol:0.1, why:'Рассеивающие, $D < 0$.'},
|
||||
{q:'У бабушки прописали +2 дптр. Какой её дефект? (введи 1=близорукость, 2=дальнозоркость)', ans:2, tol:0.1, why:'$D > 0$ → собирающие → дальнозоркость.'}
|
||||
];
|
||||
let i = 0, ok = 0, done = 0, aw = false;
|
||||
function r(){
|
||||
const t = TASKS[i]; const w = document.getElementById('p40-task');
|
||||
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px"><b>'+(i+1)+'.</b> '+t.q+'</div>'
|
||||
+'<div class="boss-row"><input type="number" step="0.1" class="tinp" id="p40-tinp" style="width:140px"><button class="btn primary" id="p40-tgo">Ответ</button><button class="btn" id="p40-thn">Подск.</button><button class="btn" id="p40-tn">След.</button></div>'
|
||||
+'<div class="boss-hint-txt" id="p40-tht">'+t.why+'</div><div class="feedback" id="p40-tfb"></div>';
|
||||
document.getElementById('p40-task-i').textContent = (i+1);
|
||||
document.getElementById('p40-task-ok').textContent = ok;
|
||||
document.getElementById('p40-tgo').addEventListener('click', ()=>{
|
||||
const v = parseFloat((document.getElementById('p40-tinp').value || '').replace(',','.'));
|
||||
const fb = document.getElementById('p40-tfb');
|
||||
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Число.'; return; }
|
||||
done++;
|
||||
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ '+t.why; addXp(4,'p40-t'); bumpProgress('p40',6); }
|
||||
else { fb.className='feedback fail'; fb.innerHTML='✗ '+t.ans+'. '+t.why; }
|
||||
document.getElementById('p40-task-ok').textContent = ok;
|
||||
renderMath(w);
|
||||
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p40-tfb'); f.className='feedback ok'; f.innerHTML='✓ +15 XP'; addXp(15,'p40-bonus'); bumpProgress('p40',15); }, 500); }
|
||||
});
|
||||
document.getElementById('p40-thn').addEventListener('click', ()=>{ document.getElementById('p40-tht').classList.toggle('show'); });
|
||||
document.getElementById('p40-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
|
||||
renderMath(w);
|
||||
}
|
||||
r();
|
||||
}
|
||||
|
||||
/* ======== ФИНАЛ ГЛАВЫ 3 ======== */
|
||||
function build_final3(){
|
||||
const box = document.getElementById('final3-body'); let h = '';
|
||||
h += '<div class="card" style="background:linear-gradient(135deg,var(--sec-acc-soft),var(--card));border:1.5px solid var(--sec-acc)">'
|
||||
+'<div class="card-header"><div class="card-icon rule">'+ICONS.rule+'</div><div class="card-title">Шпаргалка главы 3</div></div>'
|
||||
+'<div class="card-body" style="display:grid;grid-template-columns:1fr 1fr;gap:14px">'
|
||||
+'<div><b>$c$:</b> $3 \\cdot 10^8$ м/с</div>'
|
||||
+'<div><b>Тень:</b> точечный источник $\\to$ только тень</div>'
|
||||
+'<div><b>Отражение:</b> $\\alpha = \\beta$</div>'
|
||||
+'<div><b>Плоское зеркало:</b> мнимое, прямое, равное</div>'
|
||||
+'<div><b>Снеллиус:</b> $\\sin\\alpha/\\sin\\beta = n_2/n_1$</div>'
|
||||
+'<div><b>$n$ воды:</b> 1,33; <b>стекла:</b> 1,5</div>'
|
||||
+'<div><b>$D$:</b> $D = 1/F$, дптр</div>'
|
||||
+'<div><b>Линза:</b> $1/F = 1/d + 1/f$</div>'
|
||||
+'<div><b>Глаз:</b> хрусталик меняет $F$ (аккомодация)</div>'
|
||||
+'<div><b>Близорукость:</b> $D < 0$; <b>дальнозоркость:</b> $D > 0$</div>'
|
||||
+'</div></div>';
|
||||
|
||||
const BOSSES = [
|
||||
{n:1, title:'Скорость света', q:'За какое время свет пройдёт 600 000 км (мин)?', hint:'$t = 6\\cdot10^8 / 3\\cdot10^8 = 2$ с. В минутах: $1/30 \\approx 0{,}033$ мин.', ans:0.033, tol:0.005, step:'0.001'},
|
||||
{n:2, title:'Закон отражения', q:'$\\alpha = 25$°. Найди $\\beta$ (°).', hint:'$\\beta = \\alpha = 25$°.', ans:25, tol:0.5, step:'0.5'},
|
||||
{n:3, title:'Преломление', q:'$\\alpha = 30$°, воздух → стекло (n=1{,}5). Найди $\\beta$ (°, до 0,1).', hint:'$\\sin\\beta = \\sin30/1{,}5 = 0{,}333$, $\\beta \\approx 19{,}5$°.', ans:19.5, tol:0.5, step:'0.1'},
|
||||
{n:4, title:'$D$ линзы', q:'$F = 0{,}25$ м. Найди $D$ (дптр).', hint:'$D = 1/0{,}25 = 4$ дптр.', ans:4, tol:0.1, step:'0.1'},
|
||||
{n:5, title:'Тонкая линза', q:'$F = 20$ см, $d = 30$ см. Найди $f$ (см).', hint:'$f = 30\\cdot20/(30-20) = 60$ см.', ans:60, tol:1, step:'1'},
|
||||
{n:6, title:'Очки', q:'Близорукому прописали $|F| = 50$ см. Какое $D$ (дптр)?', hint:'$D = 1/F = 1/(-0{,}5) = -2$ дптр.', ans:-2, tol:0.1, step:'0.1'},
|
||||
{n:7, title:'Магистр света', q:'Угол падения на стекло $\\alpha = 60$°, $n = 1{,}5$. Угол между падающим и преломлённым (°, до 0,1).', hint:'$\\sin\\beta = \\sin60/1{,}5 \\approx 0{,}577$, $\\beta \\approx 35{,}3$°. Угол между падающим (продолж.) и преломлённым: $\\alpha - \\beta \\approx 24{,}7$° в относ. направ-х. Но угол между фактич. (падающий до границы) и преломлённым (после) — $180 - \\alpha - \\beta = 180 - 95{,}3 \\approx 84{,}7$°.', ans:84.7, tol:1, step:'0.1'}
|
||||
];
|
||||
|
||||
h += '<div class="card" style="margin-top:14px"><div class="card-header"><div class="card-icon example">'+ICONS.example+'</div><div class="card-title">7 боссов главы 3</div></div><div class="card-body">'
|
||||
+'<div style="background:linear-gradient(135deg,rgba(15,23,42,.04),rgba(8,145,178,.06));border-radius:11px;padding:12px;display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-bottom:14px">'
|
||||
+'<span style="font-weight:700">Боссов побеждено: <b id="f3-won">0</b> / 7</span>'
|
||||
+'<div style="flex:1;min-width:160px;height:8px;background:rgba(0,0,0,.08);border-radius:4px;overflow:hidden">'
|
||||
+'<div id="f3-bar" style="height:100%;background:linear-gradient(90deg,var(--sec-acc),var(--sec-acc-d));width:0%;transition:width .4s"></div>'
|
||||
+'</div></div>'
|
||||
+'<div id="f3-bosses"></div>'
|
||||
+'</div></div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('final3') + readButton('final3');
|
||||
renderMath(box); wireReadBtn('final3');
|
||||
_initFinal3_bosses(BOSSES);
|
||||
}
|
||||
function _initFinal3_bosses(BOSSES){
|
||||
const KEY = 'physics8_ch3_bosses';
|
||||
function loadState(){ try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch(e){ return {}; } }
|
||||
function saveState(s){ try { localStorage.setItem(KEY, JSON.stringify(s)); } catch(e){} }
|
||||
function updateBar(){
|
||||
const s = loadState();
|
||||
let won = 0; for(const k in s) if(s[k]) won++;
|
||||
document.getElementById('f3-won').textContent = won;
|
||||
document.getElementById('f3-bar').style.width = Math.round(won*100/BOSSES.length)+'%';
|
||||
if(won >= BOSSES.length && !STATE.achievements.has('light_master')){
|
||||
addXp(50, 'light-master');
|
||||
achievement('light_master');
|
||||
}
|
||||
return won;
|
||||
}
|
||||
function renderAll(){
|
||||
const cont = document.getElementById('f3-bosses');
|
||||
const state = loadState();
|
||||
let html = '';
|
||||
BOSSES.forEach(b=>{
|
||||
const solved = state[b.n];
|
||||
html += '<div class="boss-card'+(solved?' solved':'')+'" id="f3-boss-'+b.n+'" style="border:2px solid '+(solved?'#10b981':'var(--border)')+';border-radius:12px;padding:14px;margin-bottom:10px;background:var(--card)">'
|
||||
+'<div style="display:flex;gap:10px;align-items:center;margin-bottom:8px"><span style="font-family:Unbounded,sans-serif;font-size:.7rem;font-weight:800;padding:3px 8px;border-radius:99px;background:var(--sec-acc-soft);color:var(--sec-acc-d);text-transform:uppercase">Босс '+b.n+'</span><span style="font-weight:700">'+b.title+'</span></div>'
|
||||
+'<div style="padding:10px 12px;background:rgba(15,23,42,.04);border-radius:8px;margin-bottom:8px;font-size:.94rem;line-height:1.5">'+b.q+'</div>'
|
||||
+'<div class="boss-row">'
|
||||
+'<input type="number" step="'+b.step+'" class="tinp" id="f3-b'+b.n+'-inp" placeholder="число" style="width:140px"'+(solved?' value="'+b.ans+'" disabled':'')+'>'
|
||||
+'<button class="btn primary" id="f3-b'+b.n+'-go"'+(solved?' disabled':'')+'>Атаковать</button>'
|
||||
+'<button class="btn" id="f3-b'+b.n+'-hint">Подсказка</button>'
|
||||
+'</div>'
|
||||
+'<div class="boss-hint-txt" id="f3-b'+b.n+'-ht" style="margin-top:8px;padding:9px 13px;background:rgba(245,158,11,.12);border-left:3px solid #f59e0b;border-radius:6px;font-size:.86rem;display:none;line-height:1.5">'+b.hint+'</div>'
|
||||
+'<div class="feedback'+(solved?' ok':'')+'" id="f3-b'+b.n+'-fb" style="display:'+(solved?'block':'none')+'">'+(solved?'✓ Победа! +10 XP. Босс повержен.':'')+'</div>'
|
||||
+'</div>';
|
||||
});
|
||||
cont.innerHTML = html;
|
||||
BOSSES.forEach(b=>{
|
||||
const go = document.getElementById('f3-b'+b.n+'-go');
|
||||
const inp = document.getElementById('f3-b'+b.n+'-inp');
|
||||
const fb = document.getElementById('f3-b'+b.n+'-fb');
|
||||
const ht = document.getElementById('f3-b'+b.n+'-ht');
|
||||
const hintBtn = document.getElementById('f3-b'+b.n+'-hint');
|
||||
if(hintBtn) hintBtn.addEventListener('click', ()=>{ ht.style.display = ht.style.display==='block'?'none':'block'; });
|
||||
if(!go || go.disabled) return;
|
||||
go.addEventListener('click', ()=>{
|
||||
const v = parseFloat((inp.value || '').replace(',','.'));
|
||||
if(isNaN(v)){ fb.style.display='block'; fb.className='feedback fail'; fb.innerHTML='Введите число.'; return; }
|
||||
if(Math.abs(v - b.ans) < b.tol){
|
||||
fb.style.display='block'; fb.className='feedback ok'; fb.innerHTML='✓ Победа! +10 XP. '+b.hint;
|
||||
go.disabled = true; inp.disabled = true;
|
||||
document.getElementById('f3-boss-'+b.n).classList.add('solved');
|
||||
const s = loadState();
|
||||
if(!s[b.n]){ s[b.n]=true; saveState(s); addXp(10,'f3-boss-'+b.n); bumpProgress('final3', 14); }
|
||||
updateBar();
|
||||
renderMath(fb);
|
||||
} else {
|
||||
fb.style.display='block'; fb.className='feedback fail'; fb.innerHTML='✗ Не то. Перепроверь и попробуй снова.';
|
||||
}
|
||||
});
|
||||
inp.addEventListener('keydown', e=>{ if(e.key === 'Enter') go.click(); });
|
||||
});
|
||||
renderMath(cont);
|
||||
updateBar();
|
||||
}
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function init(){
|
||||
|
||||
Reference in New Issue
Block a user