feat(phys8 ch3): Phase 5 Wave 1+2 — §32 источники + §33 тени + §34 отражение + §35 зеркало

§32 Источники света:
- 3 теории: естеств./искусств., тепловые/люминесц., точечные
- IV-1: 8 раундов «светит/отражает»
- IV-2: 6 раундов «тепловой/люминесцентный»
- IV-3: DnD 8 источников на 2 категории
- IV-4: 6 MCQ

§33 Скорость света и распространение:
- 3 теории: c, прямолинейность, тень/полутень
- IV-1: ГЛАВНЫЙ ВИЗУАЛ — динамическая тень/полутень: slider'ы
  размера источника (0=точечный → 40=протяжённый) и расстояния,
  рисуются зоны тени и полутени на экране
- IV-2: калькулятор времени пролёта света
- IV-3: DnD 8 утверждений правда/ложь
- IV-4: 5 числовых задач (Солнце, Луна, скорость vs звук)

§34 Отражение света:
- 3 теории: закон отражения, зеркальное/диффузное, примеры
- IV-1: динамическая визуализация через OPTICS.reflectRay,
  slider α 0-80°
- IV-2: 6 раундов «зеркало/диффузное»
- IV-3: DnD 8 поверхностей
- IV-4: 5 задач (включая поворот зеркала)

§35 Плоское зеркало:
- 3 теории: свойства изображения, построение, зеркальные надписи
- IV-1: построение мнимого изображения через OPTICS.mirrorPlane
  + OPTICS.lightObject, slider расстояния
- IV-2: 6 True/False
- IV-3: DnD свойств изображения
- IV-4: 5 задач (включая 2 зеркала под 90°)

§36-40 + финал — stub-заглушки, будут реализованы в Wave 3-4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 08:52:26 +03:00
parent 3727417810
commit 0c6618fb38
+670 -31
View File
@@ -259,46 +259,47 @@ const ACH_LABELS = {
p38_done:"Построение изображений в тонких линзах освоен!",
p39_done:"Глаз как оптическая система освоен!",
p40_done:"Дефекты зрения. Очки освоен!",
ch3_done:"Глава 3 пройдена!"
ch3_done:"Глава 3 пройдена!",
light_master:"Мастер света — все боссы главы 3 повержены!"
};
const SIDEBARS = {
p32:{title:"Шпаргалка § 32",rows:[["В разработке","Phase 5 Wave 1"]]},
p33:{title:"Шпаргалка § 33",rows:[["В разработке","Phase 5 Wave 1"]]},
p34:{title:"Шпаргалка § 34",rows:[["В разработке","Phase 5 Wave 2"]]},
p35:{title:"Шпаргалка § 35",rows:[["В разработке","Phase 5 Wave 2"]]},
p36:{title:"Шпаргалка § 36",rows:[["В разработке","Phase 5 Wave 3"]]},
p37:{title:"Шпаргалка § 37",rows:[["В разработке","Phase 5 Wave 3"]]},
p38:{title:"Шпаргалка § 38",rows:[["В разработке","Phase 5 Wave 4"]]},
p39:{title:"Шпаргалка § 39",rows:[["В разработке","Phase 5 Wave 4"]]},
p40:{title:"Шпаргалка § 40",rows:[["В разработке","Phase 5 Wave 4"]]},
final3:{title:"Шпаргалка ★",rows:[["В разработке","Phase 5 Wave 4"]]}
p32:{title:"Шпаргалка § 32",rows:[["Источники","естественные / искусствен."],["Тепловые","Солнце, лампа, костёр"],["Люминесцентные","экран, светодиод"],["Точечный","размер $\\ll$ расстояния"]]},
p33:{title:"Шпаргалка § 33",rows:[["$c$","$3 \\cdot 10^8$ м/с"],["В вакууме","максимальна"],["Прямолинейно","в однородн. среде"],["Тень","полное отсутствие света"],["Полутень","точечн. источник $\\to$ тень; протяжённый $\\to$ + полутень"]]},
p34:{title:"Шпаргалка § 34",rows:[["Закон","$\\alpha = \\beta$"],["От нормали","углы измеряют"],["Диффузное","шероховатая поверхность"],["Зеркальное","гладкая, отражает в одном направлении"]]},
p35:{title:"Шпаргалка § 35",rows:[["Изображение","мнимое, прямое, равное"],["Симметрия","относит. плоскости зеркала"],["Расстояние","предмет $=$ изобр. от зеркала"],["Размер","совпадает"]]},
p36:{title:"Шпаргалка § 36",rows:[["Закон Снеллиуса","$\\sin\\alpha/\\sin\\beta = n$"],["Из воздуха в воду","$\\alpha > \\beta$"],["Из воды в воздух","$\\alpha < \\beta$"],["$n$ воды","$1{,}33$"],["$n$ стекла","$1{,}5$"]]},
p37:{title:"Шпаргалка § 37",rows:[["Собирающая","выпукл., $F > 0$"],["Рассеивающая","вогн., $F < 0$"],["Оптическая сила","$D = 1/F$"],["[D]","дптр $=$ 1/м"],["Очки $+1$","$F = 1$ м"]]},
p38:{title:"Шпаргалка § 38",rows:[["Формула","$1/F = 1/d + 1/f$"],["3 «золотых» луча","через центр, парал. оси, через $F$"],["$d > 2F$","умен., перевёрн., действ."],["$d < F$","увел., прямое, мнимое (как лупа)"]]},
p39:{title:"Шпаргалка § 39",rows:[["Хрусталик","биол. линза"],["Аккомодация","изменение $F$ хрусталика"],["Сетчатка","экран"],["Расст. наилуч. зрения","25 см"]]},
p40:{title:"Шпаргалка § 40",rows:[["Близоруков.","изобр. перед сетч., $D &lt; 0$ (рассеив.)"],["Дальнозоркость","изобр. за сетч., $D &gt; 0$ (собир.)"]]},
final3:{title:"Финал главы 3",rows:[["§§32-40","свет"],["Награда","+50 XP + «Мастер света»"]]}
};
const TIPS=[
{sec:'p32',html:"Параграф § 32 будет реализован в Phase 5 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p33',html:"Параграф § 33 будет реализован в Phase 5 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p34',html:"Параграф § 34 будет реализован в Phase 5 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p35',html:"Параграф § 35 будет реализован в Phase 5 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p36',html:араграф § 36 будет реализован в Phase 5 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p37',html:"Параграф § 37 будет реализован в Phase 5 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p38',html:"Параграф § 38 будет реализован в Phase 5 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p39',html:"Параграф § 39 будет реализован в Phase 5 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p40',html:"Параграф § 40 будет реализован в Phase 5 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'final3',html:"Параграф ★ будет реализован в Phase 5 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."}
{sec:'p32',html:"Свет излучают <b>источники</b> — тепловые (Солнце, лампа накаливания) или люминесцентные (светодиоды, экраны). Источник, размер которого много меньше расстояния, называют <b>точечным</b>."},
{sec:'p33',html:"В вакууме свет летит со скоростью $c = 3 \\cdot 10^8$ м/с — это рекорд природы. От Солнца до Земли (150 млн км) свет идёт ~8 минут. В однородной среде свет распространяется <b>прямолинейно</b> — отсюда тени."},
{sec:'p34',html:"Закон отражения: угол падения $=$ угол отражения, оба от нормали. Гладкая поверхность даёт <b>зеркальное</b> отражение, шероховатая — <b>диффузное</b>. Луна светит отражённым светом Солнца — это диффузное отражение от её поверхности."},
{sec:'p35',html:"Зеркало даёт <b>мнимое</b> изображение: оно «за» зеркалом, на том же расстоянии, что и предмет, и тех же размеров. Симметричное относительно плоскости зеркала."},
{sec:'p36',html:ри переходе из одной среды в другую луч меняет направление — это <b>преломление</b>. $\\sin\\alpha/\\sin\\beta = n$. Из воздуха в воду $n = 1{,}33$ — угол $\\beta$ меньше угла $\\alpha$."},
{sec:'p37',html:"Линза — прозрачное тело, ограниченное двумя сферическими поверхностями. <b>Собирающая</b> (двусторонне выпуклая) фокусирует параллельные лучи в точку $F$. <b>Оптическая сила</b> $D = 1/F$, [D] = дптр. У очков $+2$ дптр $F = 0{,}5$ м."},
{sec:'p38',html:"Три «золотых» луча: 1) через центр линзы — без преломления; 2) параллельно оси — после линзы через $F$; 3) через ближний $F$ — после линзы параллельно оси. Точка их пересечения — изображение."},
{sec:'p39',html:"Глаз — оптическая система. Свет проходит через роговицу и <b>хрусталик</b> (биологическая собирающая линза) и фокусируется на <b>сетчатке</b>. При изменении расстояния мышцы меняют форму хрусталика — это <b>аккомодация</b>."},
{sec:'p40',html:"<b>Близорукость</b>: изображение фокусируется перед сетчаткой. Лечится <b>рассеивающими</b> линзами ($D &lt; 0$). <b>Дальнозоркость</b>: фокус за сетчаткой. Лечится <b>собирающими</b> линзами ($D &gt; 0$)."},
{sec:'final3',html:"Финал — 7 боссов по 9 параграфам оптики: отражение, преломление, линзы, очки. +50 XP и ачивка «Мастер света»."}
];
const BUILDERS = {
p32: ()=>{ const box=document.getElementById('p32-body'); box.innerHTML = buildStub('p32', 'Источники света', 'Phase 5 Wave 1') + secNavFor('p32') + readButton('p32'); renderMath(box); wireReadBtn('p32'); },
p33: ()=>{ const box=document.getElementById('p33-body'); box.innerHTML = buildStub('p33', 'Скорость света. Прямолинейное распространение света', 'Phase 5 Wave 1') + secNavFor('p33') + readButton('p33'); renderMath(box); wireReadBtn('p33'); },
p34: ()=>{ const box=document.getElementById('p34-body'); box.innerHTML = buildStub('p34', 'Отражение света', 'Phase 5 Wave 2') + secNavFor('p34') + readButton('p34'); renderMath(box); wireReadBtn('p34'); },
p35: ()=>{ const box=document.getElementById('p35-body'); box.innerHTML = buildStub('p35', 'Зеркала. Изображение в плоском зеркале', 'Phase 5 Wave 2') + secNavFor('p35') + readButton('p35'); renderMath(box); wireReadBtn('p35'); },
p36: ()=>{ const box=document.getElementById('p36-body'); box.innerHTML = buildStub('p36', 'Преломление света', 'Phase 5 Wave 3') + secNavFor('p36') + readButton('p36'); renderMath(box); wireReadBtn('p36'); },
p37: ()=>{ const box=document.getElementById('p37-body'); box.innerHTML = buildStub('p37', 'Линзы. Оптическая сила линзы', 'Phase 5 Wave 3') + secNavFor('p37') + readButton('p37'); renderMath(box); wireReadBtn('p37'); },
p38: ()=>{ const box=document.getElementById('p38-body'); box.innerHTML = buildStub('p38', 'Построение изображений в тонких линзах', 'Phase 5 Wave 4') + secNavFor('p38') + readButton('p38'); renderMath(box); wireReadBtn('p38'); },
p39: ()=>{ const box=document.getElementById('p39-body'); box.innerHTML = buildStub('p39', 'Глаз как оптическая система', 'Phase 5 Wave 4') + secNavFor('p39') + readButton('p39'); renderMath(box); wireReadBtn('p39'); },
p40: ()=>{ const box=document.getElementById('p40-body'); box.innerHTML = buildStub('p40', 'Дефекты зрения. Очки', 'Phase 5 Wave 4') + secNavFor('p40') + readButton('p40'); renderMath(box); wireReadBtn('p40'); },
final3: ()=>{ const box=document.getElementById('final3-body'); box.innerHTML = buildStub('final3', 'Финал главы', 'Phase 5 Wave 4') + secNavFor('final3') + readButton('final3'); renderMath(box); wireReadBtn('final3'); }
p32: ()=>{ build_p32(); },
p33: ()=>{ build_p33(); },
p34: ()=>{ build_p34(); },
p35: ()=>{ build_p35(); },
p36: ()=>{ build_p36(); },
p37: ()=>{ build_p37(); },
p38: ()=>{ build_p38(); },
p39: ()=>{ build_p39(); },
p40: ()=>{ build_p40(); },
final3: ()=>{ build_final3(); }
};
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
@@ -638,6 +639,644 @@ function initSidebarToggle(){
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
/* ======================================================================
PHASE 5 — Глава 3 «Световые явления» (§32-40 + Финал)
====================================================================== */
const _SIMS = {};
function _killSim(key){ if(_SIMS[key] && _SIMS[key].raf){ cancelAnimationFrame(_SIMS[key].raf); _SIMS[key].raf=0; } }
function _isVisible(secId){ const el=document.getElementById('sec-'+secId); return el && el.classList.contains('active'); }
/* ======== §32 — Источники света ======== */
function build_p32(){
const box = document.getElementById('p32-body'); let h = '';
h += makeCard('theory', 'Источники света', '§ 32.1',
'<p><b>Источник света</b> — тело, которое излучает свет. Их делят на 2 группы:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Естественные</b>: Солнце, звёзды, молния, светящиеся насекомые (светляки).</li>'
+'<li><b>Искусственные</b>: лампа, костёр, свеча, светодиод, экран.</li>'
+'</ul>'
+'<p>Сами по себе <b>не светят</b>: Луна (отражает свет Солнца), книги, стены — все они освещены.</p>'
);
h += makeCard('rule', 'Тепловые и люминесцентные', '§ 32.2',
'<p><b>Тепловые источники</b> излучают свет благодаря высокой температуре:</p>'
+'<ul style="padding-left:20px;margin:6px 0"><li>Солнце ($\\sim 6000$ &#176;C);</li><li>лампа накаливания ($\\sim 2500$ &#176;C);</li><li>пламя свечи ($\\sim 1000$ &#176;C).</li></ul>'
+'<p><b>Люминесцентные</b> — холодные, свет за счёт квантовых процессов:</p>'
+'<ul style="padding-left:20px;margin:6px 0"><li>светодиоды, светофоры;</li><li>светляки, гнилое дерево, медузы;</li><li>люминесцентные лампы.</li></ul>'
);
h += makeCard('example', 'Точечный и протяжённый', '§ 32.3',
'<p>Если размер источника много <b>меньше</b> расстояния до объекта — его называют <b>точечным</b>. Дальняя звезда — точечный источник.</p>'
+'<p><b>Протяжённый</b> — например, длинная лампа на потолке. Он даёт мягкий свет без чётких теней.</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 id="p32-quiz"></div>'
+'<div class="actions"><button class="btn" id="p32-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p32-quiz-r">1</b>/8</span><span>Правильно: <b id="p32-quiz-ok">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="p32-quiz2"></div>'
+'<div class="actions"><button class="btn" id="p32-q2-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p32-q2-r">1</b>/6</span><span>Правильно: <b id="p32-q2-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 class="wg-help">Распредели источники.</div>'
+'<div id="p32-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="nat"></div></div><div class="drop-box"><h5>Искусственный</h5><div class="drop-items" data-cat="art"></div></div></div>'
+'<div class="actions"><button class="btn primary" id="p32-dnd-check">Проверить</button><button class="btn" id="p32-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p32-dnd-fb"></div></div>';
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ — +15 XP.</div>'
+'<div id="p32-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p32-mcq-i">1</b>/6</span><span>Правильно: <b id="p32-mcq-ok">0</b></span></div></div>';
box.innerHTML = h + secNavFor('p32') + readButton('p32');
renderMath(box); wireReadBtn('p32');
_p32_quiz1(); _p32_quiz2(); _p32_dnd(); _p32_mcq();
}
function _p32_quiz1(){
const QS = [
{it:'Луна', ans:'O', why:'Луна отражает свет Солнца.'},
{it:'Солнце', ans:'S', why:'Сам излучает.'},
{it:'Светлячок', ans:'S', why:'Биолюминесценция.'},
{it:'Зеркало', ans:'O', why:'Только отражает.'},
{it:'Лампа', ans:'S', why:'Излучает.'},
{it:'Кошачьи глаза в темноте', ans:'O', why:'Отражают свет фонаря.'},
{it:'Молния', ans:'S', why:'Электр. разряд излучает свет.'},
{it:'Венера', ans:'O', why:'Отражает свет Солнца.'}
];
let i = 0, ok = 0;
function r(){
const q = QS[i]; const w = document.getElementById('p32-quiz');
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">'+q.it+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><button class="btn" data-p="S"><b>Светит сам</b></button><button class="btn" data-p="O"><b>Отражает</b></button></div>'
+'<div class="feedback" id="p32-q1-fb"></div>';
document.getElementById('p32-quiz-r').textContent = (i+1);
document.getElementById('p32-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('p32-q1-fb');
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; '+q.why; addXp(2,'p32-q1'); bumpProgress('p32',3); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; '+q.why; }
document.getElementById('p32-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p32-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
r();
}
function _p32_quiz2(){
const QS = [
{it:'Солнце', ans:'T', why:'Высокая температура.'},
{it:'Светодиод', ans:'L', why:'Квантовый эффект.'},
{it:'Костёр', ans:'T', why:'Горение, $T \\sim 1000$ &#176;C.'},
{it:'Светляк', ans:'L', why:'Холодная биолюминесценция.'},
{it:'Лампа накаливания', ans:'T', why:'Нить нагревается до 2500 &#176;C.'},
{it:'Экран смартфона', ans:'L', why:'Светодиоды излучают холодно.'}
];
let i = 0, ok = 0;
function r(){
const q = QS[i]; const w = document.getElementById('p32-quiz2');
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">'+q.it+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><button class="btn" data-p="T"><b>Тепловой</b></button><button class="btn" data-p="L"><b>Люминесцентный</b></button></div>'
+'<div class="feedback" id="p32-q2-fb"></div>';
document.getElementById('p32-q2-r').textContent = (i+1);
document.getElementById('p32-q2-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('p32-q2-fb');
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; '+q.why; addXp(2,'p32-q2'); bumpProgress('p32',3); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; '+q.why; }
document.getElementById('p32-q2-ok').textContent = ok;
});
});
}
document.getElementById('p32-q2-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
r();
}
function _p32_dnd(){
const items = [
{id:'a',cat:'nat',html:'Солнце'},{id:'b',cat:'nat',html:'молния'},{id:'c',cat:'nat',html:'светляк'},{id:'d',cat:'nat',html:'звезда'},
{id:'e',cat:'art',html:'лампа'},{id:'f',cat:'art',html:'светодиод'},{id:'g',cat:'art',html:'свеча'},{id:'h',cat:'art',html:'экран'}
];
const dnd = setupSorter({ poolId:'p32-dnd-pool', scopeSelector:'#sec-p32', cats:['nat','art'], items, columnLayout:false });
document.getElementById('p32-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p32-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='&#10003; +15 XP'; addXp(15,'p32-dnd'); bumpProgress('p32',20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wr+'.'; }
});
document.getElementById('p32-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p32-dnd-fb').style.display='none'; });
}
function _p32_mcq(){
const QS = [
{q:'Какое тело — источник света?',opts:['Луна','лампа','зеркало','страница'],ans:1,why:'Лампа излучает свет.'},
{q:'Что общего у Солнца и лампы накаливания?',opts:['обе люминесцентные','тепловые','холодные','одинаковая T'],ans:1,why:'Светят за счёт нагрева.'},
{q:'Светодиод — это …',opts:['тепловой','люминесцентный','зеркало','не источник'],ans:1,why:'Холодный квантовый источник.'},
{q:'Точечный источник — это …',opts:['любой','размер $\\ll$ расстояния','шар','точка'],ans:1,why:'Малый по сравнению с расстоянием.'},
{q:'Луна светит потому что …',opts:['горит','отражает свет Солнца','имеет атомную реакцию','остывает'],ans:1,why:'Отражает солнечный свет.'},
{q:'Какой источник самый «холодный»?',opts:['Солнце','свеча','светляк','лампа'],ans:2,why:'Биолюминесценция идёт при обычной T.'}
];
let i = 0, ok = 0, done = 0, aw = false;
function r(){
const q = QS[i]; const w = document.getElementById('p32-mcq');
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><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="p32-mcq-fb"></div><div class="actions"><button class="btn" id="p32-mcq-n">Следующий</button></div>';
w.innerHTML = h;
document.getElementById('p32-mcq-i').textContent = (i+1);
document.getElementById('p32-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('p32-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; '+q.why; addXp(2,'p32-mcq'); bumpProgress('p32',3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; '+q.why; }
document.getElementById('p32-mcq-ok').textContent = ok;
if(done >= QS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p32-mcq-fb'); f.className='feedback ok'; f.innerHTML='&#10003; +15 XP — тренажёр пройден.'; addXp(15,'p32-bonus'); bumpProgress('p32',15); }, 500); }
});
});
document.getElementById('p32-mcq-n').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
}
r();
}
/* ======== §33 — Скорость света. Прямолинейное распространение ======== */
function build_p33(){
const box = document.getElementById('p33-body'); let h = '';
h += makeCard('theory', 'Скорость света', '§ 33.1',
'<p>В вакууме свет распространяется со <b>скоростью</b>:</p>'
+'<p style="text-align:center;margin:8px 0">$$c = 3 \\cdot 10^8 \\text{ м/с} = 300\\,000 \\text{ км/с}$$</p>'
+'<p>Это <b>максимальная</b> скорость в природе. Никакое тело не может двигаться быстрее.</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>От Солнца до Земли (150 млн км): <b>$\\sim 8$ минут</b>.</li>'
+'<li>От Луны до Земли (384 тыс. км): <b>$\\sim 1{,}3$ с</b>.</li>'
+'<li>За 1 с свет пролетает в 7,5 раза вокруг Земли.</li>'
+'</ul>'
+'<p>В воде и стекле свет идёт медленнее: $v = c/n$.</p>'
);
h += makeCard('rule', 'Прямолинейное распространение', '§ 33.2',
'<p>В <b>однородной</b> среде свет распространяется по <b>прямой</b> линии. Это видно по лазерному лучу в пыльной комнате.</p>'
+'<p>Следствие: на пути луча возникает <b>тень</b> от непрозрачного предмета. Тень повторяет силуэт предмета.</p>'
);
h += makeCard('example', 'Тень и полутень', '§ 33.3',
'<p>Если источник <b>точечный</b>, то за предметом образуется только <b>тень</b> — резкая.</p>'
+'<p>Если источник <b>протяжённый</b>, то к тени добавляется <b>полутень</b> — переходная зона.</p>'
+'<p>Затмения Солнца и Луны — это «космические» тени:</p>'
+'<ul style="padding-left:20px;margin:6px 0"><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>Размер источника: <b id="p33-sv">точечный</b><input type="range" id="p33-s" min="0" max="40" step="2" value="0"></label>'
+'<label>Расстояние от объекта до источника: <b id="p33-dv">150</b><input type="range" id="p33-d" min="80" max="250" step="10" value="150"></label>'
+'</div>'
+'<svg id="p33-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 class="sliders" style="margin-bottom:10px"><label>Расстояние, км: <b id="p33-rv">150000000</b><input type="range" id="p33-r" min="1" max="1500000000" step="100" value="150000000"></label></div>'
+'<div class="score-display" style="margin-top:8px"><span>Время в пути: <b id="p33-tv">500</b> с</span><span><b id="p33-tm">8.3</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="p33-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="t"></div></div><div class="drop-box"><h5>Ложь</h5><div class="drop-items" data-cat="f"></div></div></div>'
+'<div class="actions"><button class="btn primary" id="p33-dnd-check">Проверить</button><button class="btn" id="p33-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p33-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">$c = 3 \\cdot 10^8$ м/с. 4+ — +15 XP.</div>'
+'<div id="p33-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p33-task-i">1</b>/5</span><span>Правильно: <b id="p33-task-ok">0</b></span></div></div>';
box.innerHTML = h + secNavFor('p33') + readButton('p33');
renderMath(box); wireReadBtn('p33');
_p33_shadow(); _p33_calc(); _p33_dnd(); _p33_tasks();
}
function _p33_shadow(){
const svg = document.getElementById('p33-sim'); if(!svg) return;
function draw(){
const sSize = +document.getElementById('p33-s').value;
const dist = +document.getElementById('p33-d').value;
document.getElementById('p33-sv').textContent = sSize === 0 ? 'точечный' : sSize < 20 ? 'малый' : 'большой';
document.getElementById('p33-dv').textContent = dist;
let s = '';
/* источник слева */
const srcX = 50, srcY = 110;
if(sSize === 0){ s += '<circle cx="'+srcX+'" cy="'+srcY+'" r="6" fill="#fbbf24" stroke="#0f172a" stroke-width="1.4"/>'; }
else { s += '<rect x="'+(srcX-sSize/4)+'" y="'+(srcY-sSize/2)+'" width="'+(sSize/2)+'" height="'+sSize+'" fill="#fbbf24" stroke="#0f172a" stroke-width="1.4"/>'; }
s += '<text x="'+srcX+'" y="40" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#475569">источник</text>';
/* непрозрачный объект */
const objX = srcX + dist*0.5, objY = 110;
s += '<rect x="'+objX+'" y="'+(objY-20)+'" width="14" height="40" fill="#1f2937" stroke="#0f172a" stroke-width="1.5"/>';
/* экран справа */
const screenX = 400;
s += '<line x1="'+screenX+'" y1="40" x2="'+screenX+'" y2="180" stroke="#475569" stroke-width="3"/>';
/* лучи от краёв источника к краям объекта и дальше на экран */
const sTop = srcY - sSize/2, sBot = srcY + sSize/2;
const oTop = objY - 20, oBot = objY + 20;
/* лучи без полутени (от центра источника к краям объекта): тень */
const k1 = (oTop - srcY) / (objX - srcX);
const yShTop = srcY + k1 * (screenX - srcX);
const k2 = (oBot - srcY) / (objX - srcX);
const yShBot = srcY + k2 * (screenX - srcX);
/* лучи полутени (от противоположного края источника к краям объекта) */
const kp1 = (oTop - sBot) / (objX - srcX);
const yPenTop = sBot + kp1 * (screenX - srcX);
const kp2 = (oBot - sTop) / (objX - srcX);
const yPenBot = sTop + kp2 * (screenX - srcX);
/* рисуем зоны */
if(sSize > 0){
/* полутень — между yPenTop и yShTop, и между yShBot и yPenBot */
s += '<rect x="'+(objX+14)+'" y="'+yPenTop+'" width="'+(screenX-objX-14)+'" height="'+(yShTop-yPenTop)+'" fill="rgba(0,0,0,.15)"/>';
s += '<rect x="'+(objX+14)+'" y="'+yShBot+'" width="'+(screenX-objX-14)+'" height="'+(yPenBot-yShBot)+'" fill="rgba(0,0,0,.15)"/>';
}
/* тень */
s += '<rect x="'+(objX+14)+'" y="'+yShTop+'" width="'+(screenX-objX-14)+'" height="'+(yShBot-yShTop)+'" fill="rgba(0,0,0,.6)"/>';
/* лучи света */
s += '<line x1="'+srcX+'" y1="'+srcY+'" x2="'+screenX+'" y2="'+(srcY-80)+'" stroke="#fbbf24" stroke-width="1" stroke-dasharray="3 3" opacity="0.6"/>';
s += '<line x1="'+srcX+'" y1="'+srcY+'" x2="'+screenX+'" y2="'+(srcY+80)+'" stroke="#fbbf24" stroke-width="1" stroke-dasharray="3 3" opacity="0.6"/>';
/* подписи */
s += '<text x="'+screenX+'" y="200" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">экран</text>';
if(sSize > 0) s += '<text x="320" y="'+(yPenBot+18)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">тень + полутень</text>';
else s += '<text x="320" y="200" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">только тень (источник точечный)</text>';
svg.innerHTML = s;
}
document.getElementById('p33-s').addEventListener('input', draw);
document.getElementById('p33-d').addEventListener('input', draw);
draw();
}
function _p33_calc(){
function u(){
const r = +document.getElementById('p33-r').value;
document.getElementById('p33-rv').textContent = r.toLocaleString('ru');
const t = r*1000 / 3e8;
document.getElementById('p33-tv').textContent = t.toFixed(2);
document.getElementById('p33-tm').textContent = (t/60).toFixed(2);
}
document.getElementById('p33-r').addEventListener('input', u); u();
}
function _p33_dnd(){
const items = [
{id:'a',cat:'t',html:'$c = 3 \\cdot 10^8$ м/с'},
{id:'b',cat:'t',html:'свет идёт прямолинейно в однород. среде'},
{id:'c',cat:'t',html:'свет огибает большой объект'},
{id:'d',cat:'t',html:'тень — там, куда свет не попал'},
{id:'e',cat:'f',html:'свет идёт зигзагом'},
{id:'f',cat:'f',html:'свет летит за 1 с до Луны'},
{id:'g',cat:'f',html:'тень светлее объекта'},
{id:'h',cat:'f',html:'$c$ в воде больше, чем в вакууме'}
];
/* исправлю c (cat) — путаница; правильно: «c» как факт — «не правда» */
items[2].cat = 'f'; /* свет НЕ огибает (он не дифрагирует значительно на больших объектах в школе) */
items[5].cat = 'f'; /* до Луны 1.3 с, не 1 с... но это близко, лучше: убираем спорный */
/* для безопасности — переписываем чище */
const items2 = [
{id:'a',cat:'t',html:'$c = 3 \\cdot 10^8$ м/с'},
{id:'b',cat:'t',html:'свет идёт прямолинейно в однород. среде'},
{id:'c',cat:'t',html:'тень — место, куда свет не попал'},
{id:'d',cat:'t',html:'свет от Солнца идёт ~8 мин'},
{id:'e',cat:'f',html:'свет идёт зигзагом'},
{id:'f',cat:'f',html:'свет — это поток жидкости'},
{id:'g',cat:'f',html:'$c$ в воде больше, чем в вакууме'},
{id:'h',cat:'f',html:'свет не отбрасывает тень'}
];
const dnd = setupSorter({ poolId:'p33-dnd-pool', scopeSelector:'#sec-p33', cats:['t','f'], items: items2, columnLayout:false });
document.getElementById('p33-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p33-dnd-fb'); let wr = 0;
items2.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wr++; });
if(wr===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; +15 XP'; addXp(15,'p33-dnd'); bumpProgress('p33',20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wr+'.'; }
});
document.getElementById('p33-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p33-dnd-fb').style.display='none'; });
}
function _p33_tasks(){
const TASKS = [
{q:'За какое время свет от Солнца дойдёт до Земли? Расстояние $1{,}5 \\cdot 10^{11}$ м. Ответ в минутах (целое).', ans:8, tol:0.5, why:'$t = r/c = 1{,}5\\cdot10^{11}/3\\cdot10^8 = 500$ с $= 8{,}3$ мин.'},
{q:'Свет от лампы до глаза: 3 м. Время в наносекундах (1 нс = $10^{-9}$ с)?', ans:10, tol:0.5, why:'$t = 3/3\\cdot10^8 = 10^{-8}$ с = $10$ нс.'},
{q:'Луна на расстоянии $384\\,000$ км. Время в секундах (одна цифра после запятой)?', ans:1.3, tol:0.1, why:'$t = 3{,}84\\cdot10^8/3\\cdot10^8 = 1{,}28$ с.'},
{q:'Сколько км свет проходит за 1 минуту? Введи в формате $a \\cdot 10^7$ км.', ans:1.8, tol:0.1, why:'$d = c \\cdot 60 = 1{,}8 \\cdot 10^{10}$ м = $1{,}8 \\cdot 10^7$ км.'},
{q:'Звук в воздухе $\\sim 340$ м/с. Во сколько раз свет быстрее (порядок: $10^a$, введи $a$)?', ans:6, tol:0.5, why:'$c/v_{зв} = 3\\cdot10^8/340 \\approx 9\\cdot10^5 \\approx 10^6$. Порядок $a = 6$.'}
];
let i = 0, ok = 0, done = 0, aw = false;
function r(){
const t = TASKS[i]; const w = document.getElementById('p33-task');
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>'+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p33-tinp" style="width:140px"><button class="btn primary" id="p33-tgo">Ответ</button><button class="btn" id="p33-thn">Подск.</button><button class="btn" id="p33-tn">След.</button></div>'
+'<div class="boss-hint-txt" id="p33-tht">'+t.why+'</div><div class="feedback" id="p33-tfb"></div>';
document.getElementById('p33-task-i').textContent = (i+1);
document.getElementById('p33-task-ok').textContent = ok;
document.getElementById('p33-tgo').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p33-tinp').value || '').replace(',','.'));
const fb = document.getElementById('p33-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='&#10003; '+t.why; addXp(4,'p33-task'); bumpProgress('p33',6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ответ: '+t.ans+'. '+t.why; }
document.getElementById('p33-task-ok').textContent = ok;
renderMath(w);
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p33-tfb'); f.className='feedback ok'; f.innerHTML='&#10003; +15 XP — сданы.'; addXp(15,'p33-bonus'); bumpProgress('p33',15); }, 500); }
});
document.getElementById('p33-thn').addEventListener('click', ()=>{ document.getElementById('p33-tht').classList.toggle('show'); });
document.getElementById('p33-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
renderMath(w);
}
r();
}
/* ======== §34 — Отражение света ======== */
function build_p34(){
const box = document.getElementById('p34-body'); let h = '';
h += makeCard('theory', 'Закон отражения', '§ 34.1',
'<p>Когда луч света падает на поверхность, часть его отражается. Угол падения $\\alpha$ — между падающим лучом и <b>нормалью</b> к поверхности (перпендикуляром). Угол отражения $\\beta$ — между нормалью и отражённым лучом.</p>'
+'<p><b>Закон отражения:</b></p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li>Падающий, отражённый луч и нормаль лежат в одной плоскости.</li>'
+'<li>$\\alpha = \\beta$ (угол падения = угол отражения).</li>'
+'</ol>'
);
h += makeCard('rule', 'Зеркальное и диффузное отражение', '§ 34.2',
'<p><b>Зеркальное</b> — от гладкой поверхности (зеркало, спокойная вода). Все лучи параллельны после отражения.</p>'
+'<p><b>Диффузное (рассеянное)</b> — от шероховатой поверхности (бумага, стена). Лучи разлетаются во все стороны — поэтому мы видим стену со всех сторон.</p>'
);
h += makeCard('example', 'В жизни', '§ 34.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<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>$\\alpha$, &#176;: <b id="p34-av">30</b><input type="range" id="p34-a" min="0" max="80" step="1" value="30"></label></div>'
+'<svg id="p34-sim" viewBox="0 0 460 220" 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>$\\alpha$ = <b id="p34-as">30&#176;</b></span><span>$\\beta$ = <b id="p34-bs">30&#176;</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="p34-quiz"></div>'
+'<div class="actions"><button class="btn" id="p34-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p34-quiz-r">1</b>/6</span><span>Правильно: <b id="p34-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="p34-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="d"></div></div></div>'
+'<div class="actions"><button class="btn primary" id="p34-dnd-check">Проверить</button><button class="btn" id="p34-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p34-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="p34-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p34-task-i">1</b>/5</span><span>Правильно: <b id="p34-task-ok">0</b></span></div></div>';
box.innerHTML = h + secNavFor('p34') + readButton('p34');
renderMath(box); wireReadBtn('p34');
_p34_ref(); _p34_quiz(); _p34_dnd(); _p34_tasks();
}
function _p34_ref(){
const svg = document.getElementById('p34-sim'); if(!svg) return;
function d(){
const a = +document.getElementById('p34-a').value;
document.getElementById('p34-av').textContent = a;
document.getElementById('p34-as').textContent = a+'&#176;';
document.getElementById('p34-bs').textContent = a+'&#176;';
svg.innerHTML = window.OPTICS.reflectRay(230, 150, a, 100);
}
document.getElementById('p34-a').addEventListener('input', d); d();
}
function _p34_quiz(){
const QS = [
{it:'Зеркало', ans:'M', why:'Гладкая поверхность.'},
{it:'Бумага', ans:'D', why:'Шероховатая.'},
{it:'Мокрый асфальт', ans:'M', why:'Тонкий слой воды делает поверхность гладкой.'},
{it:'Снег', ans:'D', why:'Кристаллики рассеивают.'},
{it:'Стекло окна', ans:'M', why:'Гладкое.'},
{it:'Стена побеленная', ans:'D', why:'Микрорельеф рассеивает.'}
];
let i = 0, ok = 0;
function r(){
const q = QS[i]; const w = document.getElementById('p34-quiz');
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0">'+q.it+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><button class="btn" data-p="M"><b>Зеркальное</b></button><button class="btn" data-p="D"><b>Диффузное</b></button></div>'
+'<div class="feedback" id="p34-q-fb"></div>';
document.getElementById('p34-quiz-r').textContent = (i+1);
document.getElementById('p34-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('p34-q-fb');
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; '+q.why; addXp(3,'p34-q'); bumpProgress('p34',4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; '+q.why; }
document.getElementById('p34-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p34-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
r();
}
function _p34_dnd(){
const items = [
{id:'a',cat:'m',html:'зеркало'},{id:'b',cat:'m',html:'спокойная вода'},{id:'c',cat:'m',html:'полировка металла'},{id:'d',cat:'m',html:'мокрый асфальт'},
{id:'e',cat:'d',html:'бумага'},{id:'f',cat:'d',html:'снег'},{id:'g',cat:'d',html:'ткань'},{id:'h',cat:'d',html:'штукатурка'}
];
const dnd = setupSorter({ poolId:'p34-dnd-pool', scopeSelector:'#sec-p34', cats:['m','d'], items, columnLayout:false });
document.getElementById('p34-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p34-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='&#10003; +15 XP'; addXp(15,'p34-dnd'); bumpProgress('p34',20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wr+'.'; }
});
document.getElementById('p34-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p34-dnd-fb').style.display='none'; });
}
function _p34_tasks(){
const TASKS = [
{q:'$\\alpha = 30$ &#176;. Найди $\\beta$.', ans:30, tol:0.5, why:'$\\alpha = \\beta$.'},
{q:'Угол между падающим и зеркалом 50&#176;. Найди $\\alpha$ (от нормали).', ans:40, tol:0.5, why:'Нормаль к зеркалу, $\\alpha = 90-50 = 40$&#176;.'},
{q:'Угол между падающим и отражённым лучами 60&#176;. Найди $\\alpha$.', ans:30, tol:0.5, why:'$2\\alpha = 60$, $\\alpha = 30$&#176;.'},
{q:'$\\alpha = 0$&#176;. Куда отражается?', ans:0, tol:0.5, why:'Назад по той же нормали, $\\beta = 0$.'},
{q:'Поверхность повернули на 10&#176; (зафиксировав луч). Как изменится $\\beta$?', ans:20, tol:0.5, why:'При повороте зеркала на $\\Delta$, отражённый поворачивается на $2\\Delta = 20$&#176;.'}
];
let i = 0, ok = 0, done = 0, aw = false;
function r(){
const t = TASKS[i]; const w = document.getElementById('p34-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.5" class="tinp" id="p34-tinp" style="width:140px"><button class="btn primary" id="p34-tgo">Ответ</button><button class="btn" id="p34-thn">Подск.</button><button class="btn" id="p34-tn">След.</button></div>'
+'<div class="boss-hint-txt" id="p34-tht">'+t.why+'</div><div class="feedback" id="p34-tfb"></div>';
document.getElementById('p34-task-i').textContent = (i+1);
document.getElementById('p34-task-ok').textContent = ok;
document.getElementById('p34-tgo').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p34-tinp').value || '').replace(',','.'));
const fb = document.getElementById('p34-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='&#10003; '+t.why; addXp(4,'p34-t'); bumpProgress('p34',6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; '+t.ans+'. '+t.why; }
document.getElementById('p34-task-ok').textContent = ok;
renderMath(w);
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p34-tfb'); f.className='feedback ok'; f.innerHTML='&#10003; +15 XP'; addXp(15,'p34-bonus'); bumpProgress('p34',15); }, 500); }
});
document.getElementById('p34-thn').addEventListener('click', ()=>{ document.getElementById('p34-tht').classList.toggle('show'); });
document.getElementById('p34-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
renderMath(w);
}
r();
}
/* ======== §35 — Плоское зеркало ======== */
function build_p35(){
const box = document.getElementById('p35-body'); let h = '';
h += makeCard('theory', 'Изображение в плоском зеркале', '§ 35.1',
'<p>Плоское зеркало даёт <b>мнимое</b> изображение предмета:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Изображение расположено <b>за</b> зеркалом, на том же расстоянии, что и предмет.</li>'
+'<li>Размеры предмета и изображения <b>равны</b>.</li>'
+'<li>Изображение <b>симметрично</b> предмету относительно плоскости зеркала.</li>'
+'<li>Изображение «прямое» (не перевёрнуто).</li>'
+'</ul>'
+'<p>«Мнимое» значит: лучи света не пересекаются в этой точке, а пересекаются их <b>продолжения</b>.</p>'
);
h += makeCard('rule', 'Как строится изображение', '§ 35.2',
'<p>От каждой точки предмета идут лучи во все стороны. Те, что попадают на зеркало, отражаются по закону отражения. Продолжения отражённых лучей собираются <b>за зеркалом</b> в точке изображения.</p>'
);
h += makeCard('example', 'Зеркало vs текст', '§ 35.3',
'<p>В зеркале правая рука выглядит как левая, а буквы переворачиваются по горизонтали. Это симметрия.</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="p35-dv">8</b><input type="range" id="p35-d" min="2" max="14" step="1" value="8"></label></div>'
+'<svg id="p35-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="p35-quiz"></div>'
+'<div class="actions"><button class="btn" id="p35-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p35-quiz-r">1</b>/6</span><span>Правильно: <b id="p35-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="p35-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="y"></div></div><div class="drop-box"><h5>Не свойственно</h5><div class="drop-items" data-cat="n"></div></div></div>'
+'<div class="actions"><button class="btn primary" id="p35-dnd-check">Проверить</button><button class="btn" id="p35-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p35-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="p35-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p35-task-i">1</b>/5</span><span>Правильно: <b id="p35-task-ok">0</b></span></div></div>';
box.innerHTML = h + secNavFor('p35') + readButton('p35');
renderMath(box); wireReadBtn('p35');
_p35_mir(); _p35_quiz(); _p35_dnd(); _p35_tasks();
}
function _p35_mir(){
const svg = document.getElementById('p35-sim'); if(!svg) return;
function d(){
const dist = +document.getElementById('p35-d').value;
document.getElementById('p35-dv').textContent = dist;
const cm = 12; /* px per cm */
const mirrorX = 230;
let s = '';
/* зеркало вертикальное */
s += window.OPTICS.mirrorPlane(mirrorX, 110, 160, 90);
/* предмет — стрелка слева */
const objX = mirrorX - dist*cm;
s += window.OPTICS.lightObject(objX, 150, 50, 'arrow');
s += '<text x="'+objX+'" y="180" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">предмет</text>';
/* мнимое изображение справа */
const imgX = mirrorX + dist*cm;
/* пунктирная стрелка-изображение */
s += '<line x1="'+imgX+'" y1="150" x2="'+imgX+'" y2="100" stroke="#7c3aed" stroke-width="2" stroke-dasharray="5 3"/>';
s += '<polygon points="'+imgX+',100 '+(imgX-4)+',107 '+(imgX+4)+',107" fill="#7c3aed" opacity="0.6"/>';
s += '<text x="'+imgX+'" y="180" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#7c3aed">мнимое изобр.</text>';
/* линии-лучи */
/* луч от вершины предмета к зеркалу и обратно */
s += '<line x1="'+objX+'" y1="100" x2="'+mirrorX+'" y2="130" stroke="#fbbf24" stroke-width="1.4"/>';
s += '<line x1="'+mirrorX+'" y1="130" x2="'+(mirrorX-50)+'" y2="170" stroke="#fbbf24" stroke-width="1.4"/>';
/* продолжение в изображение */
s += '<line x1="'+mirrorX+'" y1="130" x2="'+imgX+'" y2="100" stroke="#fbbf24" stroke-width="1.4" stroke-dasharray="3 3" opacity="0.6"/>';
svg.innerHTML = s;
}
document.getElementById('p35-d').addEventListener('input', d); d();
}
function _p35_quiz(){
const QS = [
{st:'Изображение в плоском зеркале мнимое.', ans:'T', why:'Лучи не пересекаются за зеркалом — только их продолжения.'},
{st:'Размер изображения больше предмета.', ans:'F', why:'Размеры равны.'},
{st:'Изображение находится на том же расстоянии от зеркала, что и предмет.', ans:'T', why:'Симметрия.'},
{st:'Изображение перевёрнуто вверх ногами.', ans:'F', why:'Прямое (не перевёрнуто).'},
{st:'Зеркальное изображение нельзя проецировать на экран.', ans:'T', why:'Мнимое — невозможно поймать на экран.'},
{st:'Если предмет приближается к зеркалу, изображение тоже приближается.', ans:'T', why:'Симметрия сохраняется.'}
];
let i = 0, ok = 0;
function r(){
const q = QS[i]; const w = document.getElementById('p35-quiz');
w.innerHTML = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0">"'+q.st+'"</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><button class="btn" data-p="T"><b>Правда</b></button><button class="btn" data-p="F"><b>Ложь</b></button></div>'
+'<div class="feedback" id="p35-q-fb"></div>';
document.getElementById('p35-quiz-r').textContent = (i+1);
document.getElementById('p35-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('p35-q-fb');
if(b.dataset.p === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; '+q.why; addXp(3,'p35-q'); bumpProgress('p35',4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; '+q.why; }
document.getElementById('p35-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p35-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; r(); });
r();
}
function _p35_dnd(){
const items = [
{id:'a',cat:'y',html:'мнимое'},{id:'b',cat:'y',html:'прямое'},{id:'c',cat:'y',html:'равное по размеру'},{id:'d',cat:'y',html:'симметричное'},
{id:'e',cat:'n',html:'действительное'},{id:'f',cat:'n',html:'перевёрнутое'},{id:'g',cat:'n',html:'увеличенное'},{id:'h',cat:'n',html:'на экране'}
];
const dnd = setupSorter({ poolId:'p35-dnd-pool', scopeSelector:'#sec-p35', cats:['y','n'], items, columnLayout:false });
document.getElementById('p35-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p35-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='&#10003; +15 XP'; addXp(15,'p35-dnd'); bumpProgress('p35',20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wr+'.'; }
});
document.getElementById('p35-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); document.getElementById('p35-dnd-fb').style.display='none'; });
}
function _p35_tasks(){
const TASKS = [
{q:'Предмет на расстоянии 1,5 м от зеркала. На каком расстоянии (м) изображение от предмета?', ans:3, tol:0.05, why:'Изображение в 1,5 м за зеркалом, расстояние предмет-изобр = 3 м.'},
{q:'Высота предмета 30 см. Высота изображения (см)?', ans:30, tol:0.5, why:'Равны.'},
{q:'Человек двигается к зеркалу со скоростью 1 м/с. С какой скоростью он сближается со своим изображением?', ans:2, tol:0.1, why:'Оба двигаются по 1 м/с к плоскости — сближение 2 м/с.'},
{q:'Изображение в плоском зеркале мнимое? (1 = да, 0 = нет)', ans:1, tol:0.1, why:'Да, мнимое.'},
{q:'Угол между двумя плоскими зеркалами 90&#176;. Сколько изображений предмета возникнет?', ans:3, tol:0.1, why:'Два прямых + одно «двойное» = 3.'}
];
let i = 0, ok = 0, done = 0, aw = false;
function r(){
const t = TASKS[i]; const w = document.getElementById('p35-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="p35-tinp" style="width:140px"><button class="btn primary" id="p35-tgo">Ответ</button><button class="btn" id="p35-thn">Подск.</button><button class="btn" id="p35-tn">След.</button></div>'
+'<div class="boss-hint-txt" id="p35-tht">'+t.why+'</div><div class="feedback" id="p35-tfb"></div>';
document.getElementById('p35-task-i').textContent = (i+1);
document.getElementById('p35-task-ok').textContent = ok;
document.getElementById('p35-tgo').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p35-tinp').value || '').replace(',','.'));
const fb = document.getElementById('p35-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='&#10003; '+t.why; addXp(4,'p35-t'); bumpProgress('p35',6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; '+t.ans+'. '+t.why; }
document.getElementById('p35-task-ok').textContent = ok;
renderMath(w);
if(done >= TASKS.length && !aw && ok >= 4){ aw = true; setTimeout(()=>{ const f=document.getElementById('p35-tfb'); f.className='feedback ok'; f.innerHTML='&#10003; +15 XP'; addXp(15,'p35-bonus'); bumpProgress('p35',15); }, 500); }
});
document.getElementById('p35-thn').addEventListener('click', ()=>{ document.getElementById('p35-tht').classList.toggle('show'); });
document.getElementById('p35-tn').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; r(); });
renderMath(w);
}
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);
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);