feat(phys8 ch1): Phase 1 Wave 1 — §1 «Внутренняя энергия» + §2 «Способы изменения U»

§1 — Внутренняя энергия:
- 3 теории: определение U, факторы зависимости, сравнение состояний
- IV-1: симуляция «холодный vs горячий газ» — 2 сосуда с молекулами,
  скорость ∝ √T_K, цвет по tempColor
- IV-2: викторина из 6 раундов «У какого тела U больше?»
- IV-3: DnD на 8 факторов «Зависит / Не зависит»
- IV-4: MCQ-тренажёр на 6 вопросов с XP-наградой

§2 — Способы изменения внутренней энергии:
- 3 теории: 2 способа, 3 вида теплопередачи, примеры из жизни
- IV-1: двойная анимация «работа (брусок-трение) vs теплопередача
  (контакт горячее+холодное)» с термометром и стрелками потока тепла
- IV-2: викторина из 8 ситуаций «работа или теплопередача?»
- IV-3: DnD-сортировка 8 ситуаций по 2 категориям
- IV-4: MCQ-тренажёр с XP-бонусом

Инфраструктура: _SIMS, _killSim, _isVisible — управление RAF для
паузы симуляций при переключении секций.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 22:50:01 +03:00
parent 33a91900a8
commit 244a063363
+651 -6
View File
@@ -271,8 +271,18 @@ const ACH_LABELS = {
};
const SIDEBARS = {
p1:{title:"Шпаргалка § 1",rows:[["В разработке","Phase 1 Wave 1"]]},
p2:{title:"Шпаргалка § 2",rows:[["В разработке","Phase 1 Wave 1"]]},
p1:{title:"Шпаргалка § 1",rows:[
["$U$","сумма $E_k$ хаотич. движения молекул и $E_p$ их взаимодействия"],
["Зависит от","$T$, $m$, агрегатного состояния, рода вещества"],
["Не зависит от","скорости тела как целого, высоты, формы, цвета"],
["При нагреве","молекулы быстрее $\\Rightarrow$ $U$ растёт"]
]},
p2:{title:"Шпаргалка § 2",rows:[
["Способа 2","совершение работы и теплопередача"],
["Работа","трение, удар, сжатие газа, деформация"],
["Теплопередача","проводность, конвекция, излучение"],
["Знак","нагрев $\\Rightarrow$ $\\Delta U > 0$; охлаждение $\\Rightarrow$ $\\Delta U < 0$"]
]},
p3:{title:"Шпаргалка § 3",rows:[["В разработке","Phase 1 Wave 2"]]},
p4:{title:"Шпаргалка § 4",rows:[["В разработке","Phase 1 Wave 2"]]},
p5:{title:"Шпаргалка § 5",rows:[["В разработке","Phase 1 Wave 2"]]},
@@ -286,8 +296,8 @@ const SIDEBARS = {
};
const TIPS=[
{sec:'p1',html:"Параграф § 1 будет реализован в Phase 1 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p2',html:"Параграф § 2 будет реализован в Phase 1 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p1',html:"Тело состоит из молекул. Они движутся (есть $E_k$) и взаимодействуют (есть $E_p$). Их сумма — внутренняя энергия $U$. Главное: $U$ не зависит от того, движется ли тело и на какой высоте оно лежит."},
{sec:'p2',html:"Изменить $U$ можно двумя способами: совершить работу (трение, сжатие, удар) или передать тепло без работы (контакт, поток, излучение). Чай в стакане остывает — это теплопередача. Спички в коробке нагреваются от тряски — это работа."},
{sec:'p3',html:"Параграф § 3 будет реализован в Phase 1 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p4',html:"Параграф § 4 будет реализован в Phase 1 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p5',html:"Параграф § 5 будет реализован в Phase 1 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
@@ -301,8 +311,8 @@ const TIPS=[
];
const BUILDERS = {
p1: ()=>{ const box=document.getElementById('p1-body'); box.innerHTML = buildStub('p1', 'Внутренняя энергия', 'Phase 1 Wave 1') + secNavFor('p1') + readButton('p1'); renderMath(box); wireReadBtn('p1'); },
p2: ()=>{ const box=document.getElementById('p2-body'); box.innerHTML = buildStub('p2', 'Способы изменения внутренней энергии', 'Phase 1 Wave 1') + secNavFor('p2') + readButton('p2'); renderMath(box); wireReadBtn('p2'); },
p1: ()=>{ build_p1(); },
p2: ()=>{ build_p2(); },
p3: ()=>{ const box=document.getElementById('p3-body'); box.innerHTML = buildStub('p3', 'Теплопроводность', 'Phase 1 Wave 2') + secNavFor('p3') + readButton('p3'); renderMath(box); wireReadBtn('p3'); },
p4: ()=>{ const box=document.getElementById('p4-body'); box.innerHTML = buildStub('p4', 'Конвекция', 'Phase 1 Wave 2') + secNavFor('p4') + readButton('p4'); renderMath(box); wireReadBtn('p4'); },
p5: ()=>{ const box=document.getElementById('p5-body'); box.innerHTML = buildStub('p5', 'Излучение', 'Phase 1 Wave 2') + secNavFor('p5') + readButton('p5'); renderMath(box); wireReadBtn('p5'); },
@@ -652,6 +662,641 @@ function initSidebarToggle(){
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
/* ======================================================================
PHASE 1 · WAVE 1 — §1, §2
====================================================================== */
/* Глобальное состояние симуляций — чтобы можно было остановить при unmount */
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'); }
/* ======== §1 — Внутренняя энергия ======== */
function build_p1(){
const box = document.getElementById('p1-body');
let h = '';
/* 3 теоретические карточки */
h += makeCard('theory', 'Что такое внутренняя энергия', '§ 1.1',
'<p>Любое тело состоит из <b>молекул</b>. Молекулы непрерывно движутся (имеют кинетическую энергию $E_k$) и взаимодействуют друг с другом (имеют потенциальную энергию $E_p$).</p>'
+'<p><b>Внутренняя энергия</b> $U$ тела — это сумма $E_k$ хаотического движения всех молекул и $E_p$ их взаимодействия:</p>'
+'<p style="text-align:center;margin:8px 0">$U = \\sum E_k\\,(\\text{молекул}) + \\sum E_p\\,(\\text{взаимодействия})$</p>'
+'<p>В отличие от механической энергии, $U$ <b>не зависит</b> от того, движется ли тело и на какой оно высоте.</p>'
);
h += makeCard('rule', 'От чего зависит $U$', '§ 1.2',
'<p>Внутренняя энергия тела <b>растёт</b>, если:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>повышается температура $T$ (молекулы движутся быстрее);</li>'
+'<li>увеличивается масса $m$ (становится больше молекул);</li>'
+'<li>меняется агрегатное состояние: лёд $\\to$ вода $\\to$ пар (рвутся связи между молекулами).</li>'
+'</ul>'
+'<p style="margin-top:6px">$U$ <b>не зависит</b> от того, движется ли тело как целое, и от его высоты над поверхностью земли.</p>'
);
h += makeCard('example', 'Сравнение', '§ 1.3',
'<p>В стакане 200 г воды при $t = 20$ &#176;C. Сравним $U$ в трёх ситуациях:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li>стакан стоит на столе;</li>'
+'<li>стакан подняли на полку высотой 1,5 м;</li>'
+'<li>стакан несут в поезде со скоростью 60 км/ч.</li>'
+'</ol>'
+'<p>Во всех трёх случаях $U$ <b>одинакова</b>, потому что $T$, $m$ и агрегатное состояние воды не изменились.</p>'
);
/* IV1 — Симуляция «Холодный vs горячий газ» */
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">Двигайте slider\'ы $T_1$ и $T_2$ — наблюдайте, как меняется скорость молекул. Чем выше $T$, тем быстрее они движутся и тем больше внутренняя энергия $U$.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$T_1$, &#176;C: <b id="p1-t1v">20</b><input type="range" id="p1-t1" min="-100" max="500" step="5" value="20"></label>'
+'<label>$T_2$, &#176;C: <b id="p1-t2v">300</b><input type="range" id="p1-t2" min="-100" max="500" step="5" value="300"></label>'
+'</div>'
+'<svg id="p1-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:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Левый: $T_1 = $ <b id="p1-t1k">293</b> К,&nbsp; ср. скорость молекул <b id="p1-v1">100</b>%</span>'
+'<span>Правый: $T_2 = $ <b id="p1-t2k">573</b> К,&nbsp; ср. скорость молекул <b id="p1-v2">140</b>%</span>'
+'<span style="font-size:.84rem;color:var(--muted)">У какого тела больше $U$? &mdash; у того, где молекулы быстрее.</span>'
+'</div>'
+'</div>';
/* IV2 — Викторина «Где больше U?» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">У какого тела больше $U$?</div></div>'
+'<div class="wg-help">Сравните две ситуации и выберите ту, у которой <b>больше</b> внутренняя энергия, или «одинаково», если они равны.</div>'
+'<div id="p1-quiz"></div>'
+'<div class="actions"><button class="btn" id="p1-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p1-quiz-r">1</b> / 6</span><span>Правильно: <b id="p1-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD «Зависит / Не зависит» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">От чего зависит $U$?</div></div>'
+'<div class="wg-help">Перетащите чипы в нужную колонку. <b>Подсказка:</b> $U$ — это о молекулах, а не о теле как целом.</div>'
+'<div id="p1-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="dep"></div></div>'
+'<div class="drop-box"><h5>Не зависит</h5><div class="drop-items" data-cat="indep"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p1-dnd-check">Проверить</button><button class="btn" id="p1-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p1-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ тренажёр */
h += '<div class="wg" id="p1-mcq-wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">Ответьте на 6 вопросов о внутренней энергии. Достаточно 4 правильных, чтобы получить +15 XP.</div>'
+'<div id="p1-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p1-mcq-i">1</b> / 6</span><span>Правильно: <b id="p1-mcq-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p1') + readButton('p1');
renderMath(box);
wireReadBtn('p1');
_initP1_sim();
_initP1_quiz();
_initP1_dnd();
_initP1_mcq();
}
/* === §1 IV-1: газ-симуляция === */
function _initP1_sim(){
_killSim('p1sim');
const svg = document.getElementById('p1-sim'); if(!svg) return;
const W=460, H=200, boxW=200, boxH=160, gap=20;
const L = {x:gap, y:(H-boxH)/2, w:boxW, h:boxH};
const R = {x:W-gap-boxW, y:(H-boxH)/2, w:boxW, h:boxH};
/* частицы */
const Nl = 22, Nr = 22;
function makeParticles(N, box){
const arr = [];
for(let i=0;i<N;i++) arr.push({
x: box.x + 6 + Math.random()*(box.w-12),
y: box.y + 6 + Math.random()*(box.h-12),
vx: (Math.random()-0.5)*2,
vy: (Math.random()-0.5)*2
});
return arr;
}
const pL = makeParticles(Nl, L);
const pR = makeParticles(Nr, R);
let T1 = 20, T2 = 300;
function getSpeed(tC){
/* скорость пропорциональна sqrt(T_K). T_K = 273+t. */
const tK = Math.max(1, 273 + tC);
/* масштаб: при 20°C (293К) — 1.0 */
return Math.sqrt(tK / 293);
}
function step(parts, box, k){
for(const p of parts){
p.x += p.vx * k * 1.6;
p.y += p.vy * k * 1.6;
if(p.x < box.x+4) { p.x = box.x+4; p.vx = -p.vx; }
if(p.x > box.x+box.w-4) { p.x = box.x+box.w-4; p.vx = -p.vx; }
if(p.y < box.y+4) { p.y = box.y+4; p.vy = -p.vy; }
if(p.y > box.y+box.h-4) { p.y = box.y+box.h-4; p.vy = -p.vy; }
}
}
function render(){
if(!_isVisible('p1')) { _SIMS.p1sim.raf = requestAnimationFrame(render); return; }
const k1 = getSpeed(T1), k2 = getSpeed(T2);
step(pL, L, k1); step(pR, R, k2);
/* HSL цвет — холодный → синий, горячий → красный */
const cL = window.PHYS.tempColor(T1, -100, 500);
const cR = window.PHYS.tempColor(T2, -100, 500);
let s = '';
s += '<rect x="'+L.x+'" y="'+L.y+'" width="'+L.w+'" height="'+L.h+'" fill="white" stroke="#0f172a" stroke-width="2" rx="6"/>';
s += '<rect x="'+R.x+'" y="'+R.y+'" width="'+R.w+'" height="'+R.h+'" fill="white" stroke="#0f172a" stroke-width="2" rx="6"/>';
for(const p of pL) s += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="4" fill="'+cL+'" stroke="#0f172a" stroke-width="0.6"/>';
for(const p of pR) s += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="4" fill="'+cR+'" stroke="#0f172a" stroke-width="0.6"/>';
s += '<text x="'+(L.x+L.w/2)+'" y="'+(L.y-6)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+cL+'">T = '+T1+' &#176;C</text>';
s += '<text x="'+(R.x+R.w/2)+'" y="'+(R.y-6)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+cR+'">T = '+T2+' &#176;C</text>';
svg.innerHTML = s;
_SIMS.p1sim.raf = requestAnimationFrame(render);
}
_SIMS.p1sim = { raf: 0 };
_SIMS.p1sim.raf = requestAnimationFrame(render);
function update(){
T1 = +document.getElementById('p1-t1').value;
T2 = +document.getElementById('p1-t2').value;
document.getElementById('p1-t1v').textContent = T1;
document.getElementById('p1-t2v').textContent = T2;
document.getElementById('p1-t1k').textContent = (273 + T1);
document.getElementById('p1-t2k').textContent = (273 + T2);
const v1 = Math.round(getSpeed(T1)*100);
const v2 = Math.round(getSpeed(T2)*100);
document.getElementById('p1-v1').textContent = v1;
document.getElementById('p1-v2').textContent = v2;
}
document.getElementById('p1-t1').addEventListener('input', update);
document.getElementById('p1-t2').addEventListener('input', update);
update();
}
/* === §1 IV-2: викторина «Где больше U?» === */
function _initP1_quiz(){
const QS = [
{A:'1 кг воды при $t = 20$ &#176;C', B:'1 кг воды при $t = 80$ &#176;C', ans:'B', why:'При большей $T$ молекулы движутся быстрее $\\Rightarrow$ больше $U$.'},
{A:'1 кг льда при $0$ &#176;C', B:'1 кг воды при $0$ &#176;C', ans:'B', why:'При плавлении (0 &#176;C, лёд$\\to$вода) поглощается теплота, поэтому у воды $U$ больше.'},
{A:'2 кг железа при $100$ &#176;C', B:'1 кг железа при $100$ &#176;C', ans:'A', why:'У большей массы — больше молекул, значит больше $U$.'},
{A:'Камень на полу', B:'Тот же камень на полке (1 м выше)', ans:'C', why:'$U$ не зависит от высоты. Это потенциальная механическая энергия, а не внутренняя.'},
{A:'Покоящийся теннисный мяч', B:'Тот же мяч, летящий со скоростью 20 м/с', ans:'C', why:'$U$ не зависит от скорости тела как целого.'},
{A:'1 кг воды при $100$ &#176;C', B:'1 кг водяного пара при $100$ &#176;C', ans:'B', why:'При кипении (вода$\\to$пар) поглощается большая теплота. У пара $U$ больше.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i];
const wrap = document.getElementById('p1-quiz');
if(!wrap) return;
wrap.innerHTML =
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">'
+'<button class="btn" data-pick="A" style="text-align:left;padding:14px"><b>A.</b> '+q.A+'</button>'
+'<button class="btn" data-pick="B" style="text-align:left;padding:14px"><b>B.</b> '+q.B+'</button>'
+'</div>'
+'<div style="display:flex;justify-content:center;margin-top:8px"><button class="btn" data-pick="C" style="padding:10px 20px">$U$ одинакова</button></div>'
+'<div class="feedback" id="p1-quiz-fb"></div>';
document.getElementById('p1-quiz-r').textContent = (i+1);
document.getElementById('p1-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
const pick = btn.dataset.pick;
wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p1-quiz-fb');
if(pick === q.ans){
ok++;
fb.className = 'feedback ok'; fb.innerHTML = '&#10003; Верно. '+q.why;
addXp(3, 'p1-quiz'); bumpProgress('p1', 4);
} else {
fb.className = 'feedback fail'; fb.innerHTML = '&#10007; Не то. '+q.why;
}
document.getElementById('p1-quiz-ok').textContent = ok;
renderMath(wrap);
});
});
renderMath(wrap);
}
document.getElementById('p1-quiz-next').addEventListener('click', ()=>{
i = (i+1) % QS.length; render();
});
render();
}
/* === §1 IV-3: DnD сортировка === */
function _initP1_dnd(){
const items = [
{id:'t', cat:'dep', html:'температура $T$'},
{id:'m', cat:'dep', html:'масса $m$'},
{id:'ag', cat:'dep', html:'агрегатное состояние'},
{id:'rd', cat:'dep', html:'род вещества'},
{id:'sp', cat:'indep', html:'скорость тела как целого'},
{id:'h', cat:'indep', html:'высота над землёй'},
{id:'fm', cat:'indep', html:'форма тела'},
{id:'cl', cat:'indep', html:'цвет тела'}
];
const wg = document.querySelector('#sec-p1 .wg:nth-of-type(7)'); /* IV-3 — 7-й (3 теории + IV1+IV2+IV3) */
const dnd = setupSorter({
poolId:'p1-dnd-pool',
scopeSelector:'#sec-p1',
cats:['dep','indep'],
items: items,
columnLayout:false
});
document.getElementById('p1-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p1-dnd-fb');
let wrong = 0, total = items.length;
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong === 0){
fb.className = 'feedback ok'; fb.innerHTML = '&#10003; Идеально! +15 XP. $U$ — это про молекулы, а не про движение тела или его положение.';
addXp(15, 'p1-dnd'); bumpProgress('p1', 20);
} else {
fb.className = 'feedback fail'; fb.innerHTML = '&#10007; Ошибок: '+wrong+' из '+total+'. Помните: $U$ зависит от $T$, $m$, агрегатного состояния и рода вещества.';
}
renderMath(fb);
});
document.getElementById('p1-dnd-reset').addEventListener('click', ()=>{
dnd.reset();
const fb = document.getElementById('p1-dnd-fb'); fb.style.display='none';
});
}
/* === §1 IV-4: MCQ-тренажёр === */
function _initP1_mcq(){
const QS = [
{q:'Из чего складывается внутренняя энергия?', opts:['Только из $E_k$ молекул','$E_k$ + $E_p$ всех молекул','Из массы и объёма','Из температуры'], ans:1, why:'$U$ — сумма кинетических энергий хаотического движения и потенциальных энергий взаимодействия молекул.'},
{q:'При нагревании тела его внутренняя энергия…', opts:['уменьшается','растёт','не меняется','становится отрицательной'], ans:1, why:'Молекулы движутся быстрее $\\Rightarrow$ $U$ растёт.'},
{q:'Зависит ли $U$ от скорости движения тела как целого?', opts:['да','нет','только для газов','только при $v &gt; 100$ м/с'], ans:1, why:'$U$ зависит только от <i>хаотического</i> движения молекул внутри тела.'},
{q:'У какого тела $U$ больше: у стакана воды на столе или у того же стакана, поднятого на 1 м?', opts:['на полу','на полке','одинакова','зависит от формы стакана'], ans:2, why:'Высота не влияет на $U$ — это про механическую $E_p$, а не про внутреннюю.'},
{q:'Изменится ли $U$, если вещество переходит из твёрдого в жидкое состояние при той же температуре?', opts:['нет','да, растёт','да, уменьшается','зависит от формы тела'], ans:1, why:'При плавлении рвутся связи между молекулами, поглощается теплота $\\Rightarrow$ $U$ растёт.'},
{q:'Какое из утверждений верно?', opts:['$U$ — это $mv^2/2$ тела','$U$ — это $mgh$ тела','$U$ — это $E_k+E_p$ молекул внутри','$U$ — это объём $\\times$ давление'], ans:2, why:'$U$ — энергия молекул, не имеет отношения к движению или положению тела как целого.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i];
const wrap = document.getElementById('p1-mcq');
if(!wrap) return;
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>';
h += '<div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt, k)=>{
h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+(String.fromCharCode(65+k))+'. '+opt+'</button>';
});
h += '</div><div class="feedback" id="p1-mcq-fb"></div>';
h += '<div class="actions"><button class="btn" id="p1-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p1-mcq-i').textContent = (i+1);
document.getElementById('p1-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k;
const fb = document.getElementById('p1-mcq-fb');
if(k === q.ans){
ok++; done++;
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why;
addXp(2,'p1-mcq'); bumpProgress('p1', 3);
} else {
done++;
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why;
}
document.getElementById('p1-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){
awarded = true;
setTimeout(()=>{
const wgFb = document.getElementById('p1-mcq-fb');
wgFb.className='feedback ok';
wgFb.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').';
addXp(15,'p1-mcq-bonus'); bumpProgress('p1', 15);
}, 600);
}
});
});
const nextBtn = document.getElementById('p1-mcq-next');
if(nextBtn) nextBtn.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §2 — Способы изменения внутренней энергии ======== */
function build_p2(){
const box = document.getElementById('p2-body');
let h = '';
h += makeCard('theory', 'Два способа', '§ 2.1',
'<p>Внутреннюю энергию тела $U$ можно изменить двумя принципиально разными способами:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Совершение работы.</b> Над телом или само тело совершает работу (трение, сжатие газа, удар, деформация). $U$ растёт.</li>'
+'<li><b>Теплопередача.</b> Энергия передаётся от более горячего тела к более холодному без совершения работы.</li>'
+'</ul>'
+'<p>Изменение внутренней энергии обозначают $\\Delta U$. Если $T$ растёт — $\\Delta U > 0$, если падает — $\\Delta U < 0$.</p>'
);
h += makeCard('rule', 'Три вида теплопередачи', '§ 2.2',
'<p>Теплопередача бывает трёх видов:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li><b>Теплопроводность</b> (§ 3) — через прямой контакт частиц. Металлическая ложка в чае нагревается.</li>'
+'<li><b>Конвекция</b> (§ 4) — потоками жидкости или газа. Радиатор греет комнату.</li>'
+'<li><b>Излучение</b> (§ 5) — электромагнитными волнами, даже через вакуум. Солнце греет Землю.</li>'
+'</ol>'
);
h += makeCard('example', 'Примеры из жизни', '§ 2.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Трёшь ладони — нагреваются. <b>Работа</b> сил трения.</li>'
+'<li>Спички в коробке встряхиваешь — нагреваются. <b>Работа</b>.</li>'
+'<li>Чай в стакане остывает. <b>Теплопередача</b> (излучение + конвекция).</li>'
+'<li>Ложка в горячем чае нагревается. <b>Теплопередача</b> (теплопроводность).</li>'
+'<li>Велосипедный насос нагревается при работе. <b>Работа</b> (сжатие газа).</li>'
+'</ul>'
);
/* IV1 — анимация: трение vs контакт */
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">Слева — <b>работа</b>: брусок скользит по доске, трение нагревает его. Справа — <b>теплопередача</b>: горячее тело отдаёт энергию холодному при контакте, температуры выравниваются.</div>'
+'<svg id="p2-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="actions" style="margin-top:10px"><button class="btn primary" id="p2-sim-start">Запустить</button><button class="btn" id="p2-sim-reset">Сброс</button></div>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Брусок (трение): $T = $ <b id="p2-tA">20</b> &#176;C, $\\Delta U$ <b id="p2-uA">растёт</b></span>'
+'<span>Контакт: $T_{гор} = $ <b id="p2-tH">90</b> &#176;C, $T_{хол} = $ <b id="p2-tC">10</b> &#176;C</span>'
+'</div>'
+'</div>';
/* IV2 — викторина «работа или теплопередача» */
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="p2-quiz"></div>'
+'<div class="actions"><button class="btn" id="p2-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p2-quiz-r">1</b> / 8</span><span>Правильно: <b id="p2-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD «работа vs теплопередача» */
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">Перетащите чипы в нужную колонку. <b>Работа</b> — когда силы перемещают что-то (трение, сжатие, удар). <b>Теплопередача</b> — без работы, просто через контакт, поток или излучение.</div>'
+'<div id="p2-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="work"></div></div>'
+'<div class="drop-box"><h5>Теплопередача</h5><div class="drop-items" data-cat="heat"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p2-dnd-check">Проверить</button><button class="btn" id="p2-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p2-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ тренажёр */
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="p2-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p2-mcq-i">1</b> / 6</span><span>Правильно: <b id="p2-mcq-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p2') + readButton('p2');
renderMath(box);
wireReadBtn('p2');
_initP2_sim();
_initP2_quiz();
_initP2_dnd();
_initP2_mcq();
}
/* === §2 IV-1: симуляция «работа vs теплопередача» === */
function _initP2_sim(){
_killSim('p2sim');
const svg = document.getElementById('p2-sim'); if(!svg) return;
const W=460, H=200;
/* левая сцена: брусок скользит по доске */
/* правая сцена: два тела касаются, температуры → среднее */
let running = false;
let tA = 20; /* температура бруска */
let tH = 90, tC = 10; /* контакт горячий/холодный */
let blockX = 60;
let t = 0;
function reset(){
tA = 20; tH = 90; tC = 10; blockX = 60; t = 0;
document.getElementById('p2-tA').textContent = '20';
document.getElementById('p2-uA').textContent = '0';
document.getElementById('p2-tH').textContent = '90';
document.getElementById('p2-tC').textContent = '10';
paint();
}
function paint(){
let s = '';
/* === ЛЕВАЯ СЦЕНА: трение === */
/* доска */
s += '<rect x="20" y="120" width="200" height="14" fill="#a16207" stroke="#0f172a" stroke-width="1.5" rx="2"/>';
/* штриховка под доской */
for(let i=22;i<218;i+=10) s += '<line x1="'+i+'" y1="134" x2="'+(i+4)+'" y2="140" stroke="#92400e" stroke-width="1"/>';
/* брусок (цвет по температуре) */
const cA = window.PHYS.tempColor(tA, 20, 80);
s += '<rect x="'+blockX+'" y="92" width="50" height="28" fill="'+cA+'" stroke="#0f172a" stroke-width="1.6" rx="3"/>';
/* стрелка-сила F (рука толкает) */
s += window.PHYS.drawArrow ? window.PHYS.drawArrow(blockX-30, 106, blockX-5, 106, '#10b981', 2.2, 9) : '';
s += '<text x="'+(blockX-32)+'" y="100" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#10b981">F</text>';
/* трение (внизу бруска) */
s += '<text x="'+(blockX+25)+'" y="80" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#dc2626">трение нагревает</text>';
/* термометр для бруска */
s += window.PHYS.thermometer(220, 50, 80, 0, 100, tA);
/* подпись */
s += '<text x="120" y="170" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">РАБОТА (трение)</text>';
/* === разделитель === */
s += '<line x1="240" y1="20" x2="240" y2="180" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="4 4"/>';
/* === ПРАВАЯ СЦЕНА: контакт === */
const cH = window.PHYS.tempColor(tH, 0, 100);
const cCo = window.PHYS.tempColor(tC, 0, 100);
s += '<rect x="270" y="80" width="60" height="60" fill="'+cH+'" stroke="#0f172a" stroke-width="1.6" rx="4"/>';
s += '<rect x="335" y="80" width="60" height="60" fill="'+cCo+'" stroke="#0f172a" stroke-width="1.6" rx="4"/>';
/* стрелки потока тепла */
s += window.PHYS.drawArrow ? window.PHYS.drawArrow(326, 100, 344, 100, '#dc2626', 2.2, 8) : '';
s += window.PHYS.drawArrow ? window.PHYS.drawArrow(326, 120, 344, 120, '#dc2626', 2.2, 8) : '';
s += '<text x="335" y="72" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626">Q</text>';
/* подписи температур */
s += '<text x="300" y="160" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">'+tH.toFixed(0)+' &#176;C</text>';
s += '<text x="365" y="160" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">'+tC.toFixed(0)+' &#176;C</text>';
s += '<text x="332" y="174" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">ТЕПЛОПЕРЕДАЧА</text>';
svg.innerHTML = s;
}
function tick(){
if(!running){ _SIMS.p2sim.raf = requestAnimationFrame(tick); return; }
if(!_isVisible('p2')){ _SIMS.p2sim.raf = requestAnimationFrame(tick); return; }
t += 1;
/* брусок двигается слева направо и нагревается */
blockX += 0.8;
if(blockX > 170){ blockX = 60; }
if(tA < 80) tA += 0.18;
/* контакт: tH и tC сходятся к среднему */
const avg = (tH + tC) / 2;
if(Math.abs(tH - avg) > 0.05){
tH -= (tH - avg) * 0.012;
tC += (avg - tC) * 0.012;
}
document.getElementById('p2-tA').textContent = tA.toFixed(0);
document.getElementById('p2-uA').textContent = (tA - 20).toFixed(0) === '0' ? '0' : '+'+(tA - 20).toFixed(0);
document.getElementById('p2-tH').textContent = tH.toFixed(0);
document.getElementById('p2-tC').textContent = tC.toFixed(0);
paint();
_SIMS.p2sim.raf = requestAnimationFrame(tick);
}
document.getElementById('p2-sim-start').addEventListener('click', ()=>{
running = !running;
document.getElementById('p2-sim-start').textContent = running ? 'Пауза' : 'Запустить';
});
document.getElementById('p2-sim-reset').addEventListener('click', ()=>{
running = false;
document.getElementById('p2-sim-start').textContent = 'Запустить';
reset();
});
_SIMS.p2sim = { raf: 0 };
reset();
_SIMS.p2sim.raf = requestAnimationFrame(tick);
}
/* === §2 IV-2: викторина работа/теплопередача === */
function _initP2_quiz(){
const QS = [
{sit:'Долго трёшь руки одна о другую — они нагреваются.', ans:'W', why:'Работа сил трения переходит во внутреннюю энергию рук.'},
{sit:'Чай в стакане остывает на столе.', ans:'H', why:'Теплопередача от чая в воздух и стенки стакана — без совершения работы.'},
{sit:'Велосипедный насос нагревается при накачивании колеса.', ans:'W', why:'Воздух в насосе сжимается — над ним совершается работа.'},
{sit:'Металлическая ложка в горячем чае нагревается.', ans:'H', why:'Теплопередача (теплопроводность) от чая к ложке.'},
{sit:'Гвоздь забивают молотком — шляпка гвоздя нагревается.', ans:'W', why:'Кинетическая энергия молотка переходит в работу деформации/трения.'},
{sit:'Лёд в холодильнике плавится, если открыть дверцу.', ans:'H', why:'Тёплый воздух комнаты передаёт энергию льду.'},
{sit:'Греется проволока в фене — горячий воздух дует на руки.', ans:'H', why:'Конвекция: поток горячего воздуха переносит энергию.'},
{sit:'Спички в коробке от долгой тряски нагрелись.', ans:'W', why:'Работа сил трения внутри коробки.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i];
const wrap = document.getElementById('p2-quiz');
if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">'+q.sit+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<button class="btn" data-pick="W" style="padding:14px"><b>Работа</b></button>'
+'<button class="btn" data-pick="H" style="padding:14px"><b>Теплопередача</b></button>'
+'</div>'
+'<div class="feedback" id="p2-quiz-fb"></div>';
document.getElementById('p2-quiz-r').textContent = (i+1);
document.getElementById('p2-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
const pick = btn.dataset.pick;
wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p2-quiz-fb');
if(pick === q.ans){
ok++;
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why;
addXp(3,'p2-quiz'); bumpProgress('p2', 4);
} else {
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why;
}
document.getElementById('p2-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p2-quiz-next').addEventListener('click', ()=>{
i = (i+1) % QS.length; render();
});
render();
}
/* === §2 IV-3: DnD сортировка === */
function _initP2_dnd(){
const items = [
{id:'a', cat:'work', html:'трение ладонями'},
{id:'b', cat:'work', html:'сжатие воздуха в насосе'},
{id:'c', cat:'work', html:'удар молотком по гвоздю'},
{id:'d', cat:'work', html:'строгание доски рубанком'},
{id:'e', cat:'heat', html:'остывание чая в стакане'},
{id:'f', cat:'heat', html:'нагрев ложки в супе'},
{id:'g', cat:'heat', html:'Солнце греет асфальт'},
{id:'h', cat:'heat', html:'батарея греет комнату'}
];
const dnd = setupSorter({
poolId:'p2-dnd-pool',
scopeSelector:'#sec-p2',
cats:['work','heat'],
items: items,
columnLayout:false
});
document.getElementById('p2-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p2-dnd-fb');
let wrong = 0, total = items.length;
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong === 0){
fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Работа связана с движением макротел, теплопередача — с разностью температур.';
addXp(15,'p2-dnd'); bumpProgress('p2', 20);
} else {
fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+' из '+total+'. Подсказка: трение, сжатие, удар, строгание — это работа.';
}
});
document.getElementById('p2-dnd-reset').addEventListener('click', ()=>{
dnd.reset();
const fb = document.getElementById('p2-dnd-fb'); fb.style.display='none';
});
}
/* === §2 IV-4: MCQ тренажёр === */
function _initP2_mcq(){
const QS = [
{q:'Какими способами можно изменить $U$ тела?', opts:['Только работой','Только теплопередачей','Работой и теплопередачей','Только нагревом'], ans:2, why:'Это два равноправных способа.'},
{q:'Что нагревает наковальню при ковке?', opts:['Теплопередача','Работа удара','Излучение','Конвекция'], ans:1, why:'Кинетическая энергия молотка совершает работу деформации.'},
{q:'Какой вид теплопередачи греет руку от костра, если не подходить близко?', opts:['Теплопроводность','Конвекция','Излучение','Работа'], ans:2, why:'Через воздух (плохой проводник) и без контакта — это излучение.'},
{q:'Чай в термосе долго не остывает, потому что…', opts:['у термоса большая масса','стенки термоса тонкие','двойные стенки и вакуум между ними подавляют все виды теплопередачи','чай совершает работу'], ans:2, why:'Вакуум — нет конвекции и проводности; зеркальные стенки уменьшают излучение.'},
{q:'Какой пример НЕ относится к теплопередаче?', opts:['Ложка нагрелась в супе','Воздух у радиатора поднимается вверх','Снег тает на солнце','Гвоздь нагрелся, когда его забили молотком'], ans:3, why:'Гвоздь нагрелся работой удара.'},
{q:'Знак $\\Delta U$ для остывающего тела?', opts:['$\\Delta U > 0$','$\\Delta U < 0$','$\\Delta U = 0$','зависит от массы'], ans:1, why:'$T$ падает $\\Rightarrow$ $U$ уменьшается $\\Rightarrow$ $\\Delta U < 0$.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i];
const wrap = document.getElementById('p2-mcq');
if(!wrap) return;
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>';
h += '<div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt, k)=>{
h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+(String.fromCharCode(65+k))+'. '+opt+'</button>';
});
h += '</div><div class="feedback" id="p2-mcq-fb"></div>';
h += '<div class="actions"><button class="btn" id="p2-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p2-mcq-i').textContent = (i+1);
document.getElementById('p2-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k;
const fb = document.getElementById('p2-mcq-fb');
if(k === q.ans){
ok++; done++;
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why;
addXp(2,'p2-mcq'); bumpProgress('p2', 3);
} else {
done++;
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why;
}
document.getElementById('p2-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){
awarded = true;
setTimeout(()=>{
const wgFb = document.getElementById('p2-mcq-fb');
wgFb.className='feedback ok';
wgFb.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').';
addXp(15,'p2-mcq-bonus'); bumpProgress('p2', 15);
}, 600);
}
});
});
const nextBtn = document.getElementById('p2-mcq-next');
if(nextBtn) nextBtn.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);