feat(phys10 ch1 wave2): §3 «Идеальный газ» + §4 «Температура» + симуляции

This commit is contained in:
Maxim Dolgolyov
2026-05-29 16:25:24 +03:00
parent 3116f9d815
commit 7acc606cc2
+692 -12
View File
@@ -1290,34 +1290,714 @@ function build_p2(){
function build_p3(){
const box = document.getElementById('p3-body');
let html = '';
html += makeCard('theory', "Идеальный газ. Основное уравнение МКТ", "§3", `
<p><b>Идеальный газ. Основное уравнение МКТ</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
/* THEORY 1 — модель идеального газа */
html += makeCard('theory', "Модель идеального газа", "§3", `
<p><b>Идеальный газ</b> — упрощённая модель реального газа, в которой:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li><b>Размеры молекул пренебрежимо малы</b> по сравнению с расстояниями между ними.</li>
<li><b>Молекулы взаимодействуют только при столкновениях</b> (как упругие шары).</li>
<li><b>Между столкновениями молекулы движутся равномерно и прямолинейно</b>.</li>
</ol>
<p>Реальные газы при <b>низком давлении</b> и <b>не очень низкой температуре</b> близки к идеальному.</p>
<p style="margin-top:10px"><b>Параметры газа:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Макропараметры</b> (описывают газ в целом): давление $p$, объём $V$, температура $T$.</li>
<li><b>Микропараметры</b> (описывают отдельные молекулы): масса молекулы $m_0$, концентрация $n = N/V$, скорость $v$.</li>
</ul>
`);
/* THEORY 2 — основное уравнение МКТ */
html += makeCard('rule', "Основное уравнение МКТ", "§3", `
<p>Давление идеального газа создаётся <b>ударами молекул о стенки сосуда</b>. Связь между макро- и микропараметрами:</p>
<p style="text-align:center;margin:10px 0">$$p = \\dfrac{1}{3} n m_0 \\overline{v^2}$$</p>
<p>где:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$n$ — концентрация молекул, м$^{-3}$;</li>
<li>$m_0$ — масса одной молекулы, кг;</li>
<li>$\\overline{v^2}$ — средний квадрат скорости молекул, м²/с².</li>
</ul>
<p>Это <b>основное уравнение МКТ идеального газа</b>.</p>
<p style="margin-top:8px">Через среднюю кинетическую энергию поступательного движения $\\overline{E_k} = \\dfrac{1}{2} m_0 \\overline{v^2}$:</p>
<p style="text-align:center;margin:10px 0">$$p = \\dfrac{2}{3} n \\overline{E_k}$$</p>
`);
/* THEORY 3 — средняя квадратичная скорость */
html += makeCard('example', "Средняя квадратичная скорость", "§3", `
<p><b>Средняя квадратичная скорость молекул</b>:</p>
<p style="text-align:center;margin:10px 0">$$v_{ср.кв.} = \\sqrt{\\overline{v^2}}$$</p>
<p>Из основного уравнения МКТ:</p>
<p style="text-align:center;margin:10px 0">$$\\overline{v^2} = \\dfrac{3p}{n m_0}$$</p>
<p><b>Примеры скоростей</b> (при $T = 300$ К):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Молекулы водорода ($M = 2$ г/моль): $v_{ср.кв.} \\approx 1900$ м/с.</li>
<li>Молекулы азота ($M = 28$ г/моль): $v_{ср.кв.} \\approx 510$ м/с.</li>
<li>Молекулы кислорода ($M = 32$ г/моль): $v_{ср.кв.} \\approx 480$ м/с.</li>
<li>Молекулы воды (пар, $M = 18$ г/моль): $v_{ср.кв.} \\approx 645$ м/с.</li>
</ul>
<p>Чем легче молекула — тем быстрее она движется при той же температуре.</p>
`);
/* INTERACTIVE 1 — Симуляция давления газа */
html += `<div class="wg" id="p3-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симуляция давления газа</div></div>
<div class="wg-help">Меняй число молекул и их скорость — наблюдай удары о правую стенку. Давление пропорционально частоте ударов и квадрату скорости: $p \\sim n \\cdot v^2$.</div>
<div class="sliders">
<label>Концентрация $n$: <b id="p3-iv1-nL">50</b> молекул <input type="range" id="p3-iv1-n" min="10" max="100" value="50" step="1"></label>
<label>Скорость $v$: <b id="p3-iv1-vL">80</b> px/s <input type="range" id="p3-iv1-v" min="30" max="200" value="80" step="5"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p3-iv1-svg" viewBox="0 0 420 280" width="100%" style="max-width:420px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p3-iv1-pause">Пауза</button>
<button class="btn" id="p3-iv1-reset">Сброс</button>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.9rem;line-height:1.7">
<div>Ударов в правую стенку за последнюю секунду: <b id="p3-iv1-hits">0</b></div>
<div><b>Давление</b> (усл. ед., $\\propto n v^2$): <b id="p3-iv1-pres">—</b></div>
</div>
</div>`;
/* INTERACTIVE 2 — Калькулятор основного уравнения МКТ */
html += `<div class="wg" id="p3-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор уравнения МКТ</div></div>
<div class="wg-help">$p = \\dfrac{1}{3} n m_0 \\overline{v^2}$. Введи параметры и получи давление и среднюю кинетическую энергию молекулы.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">$n$, $10^{25}$ м$^{-3}$ <input type="number" id="p3-iv2-n" class="tinp" style="width:100%;margin-top:6px" value="2.7" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">$m_0$, $10^{-26}$ кг <input type="number" id="p3-iv2-m" class="tinp" style="width:100%;margin-top:6px" value="4.65" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">$\\overline{v^2}$, $10^{6}$ м²/с² <input type="number" id="p3-iv2-v" class="tinp" style="width:100%;margin-top:6px" value="0.24" step="any"></label>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p3-iv2-go">Вычислить $p$</button>
</div>
<div id="p3-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p3-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр Идеальный/Реальный */
html += `<div class="wg" id="p3-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Идеальный или реальный газ?</div></div>
<div class="wg-help">6 ситуаций. Газ близок к идеальному при низком $p$ и не очень низком $T$. Жми соответствующую кнопку.</div>
<div class="score-display"><span>Задача <b id="p3-iv3-i">1</b> / 6</span><span>Очки: <b id="p3-iv3-s">0</b> / 6</span></div>
<div id="p3-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p3-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p3-iv3-fb"></div>
<div class="actions"><button class="btn" id="p3-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр МКТ */
html += `<div class="wg" id="p3-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр МКТ</div></div>
<div class="wg-help">5 задач на $p = \\frac{1}{3} n m_0 \\overline{v^2}$. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p3-iv4-i">1</b> / 5</span><span>Очки: <b id="p3-iv4-s">0</b> / 5</span></div>
<div id="p3-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p3-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p3-iv4-go">Проверить</button>
<button class="btn" id="p3-iv4-start">Заново</button>
</div>
<div class="feedback" id="p3-iv4-fb"></div>
</div>`;
html += secNav('p2', 'p4');
html += readButton('p3');
box.innerHTML = html;
renderMath(box);
/* IV1 — Симуляция давления газа */
(function(){
const svg = document.getElementById('p3-iv1-svg');
const nInp = document.getElementById('p3-iv1-n');
const vInp = document.getElementById('p3-iv1-v');
const nLab = document.getElementById('p3-iv1-nL');
const vLab = document.getElementById('p3-iv1-vL');
const hitsEl = document.getElementById('p3-iv1-hits');
const presEl = document.getElementById('p3-iv1-pres');
const btnPause = document.getElementById('p3-iv1-pause');
const btnReset = document.getElementById('p3-iv1-reset');
const W = 400, H = 220, OX = 10, OY = 30;
let raf = null, lastT = 0, paused = false;
let sim = null;
let hitTimes = []; // timestamps ударов о правую стенку (сек)
let lastUpdate = 0;
const tempChanges = new Set();
let _xpDone = false;
function makeSim(){
const N = +nInp.value;
const v = +vInp.value;
sim = PHYS.createGasSim({W, H, N, speed: v, r: 3});
hitTimes = [];
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
// подсчёт ударов о правую стенку (до step) — отслеживаем переход через правую границу
const R = sim.r;
const beforeX = sim.particles.map(p => p.x);
sim.step(dt);
for(let i = 0; i < sim.particles.length; i++){
// если после step координата x = W - r и vx стало отрицательным — был удар
if(beforeX[i] < W - R - 0.1 && sim.particles[i].x >= W - R - 0.5 && sim.particles[i].vx < 0){
hitTimes.push(t / 1000);
}
// более надёжный признак: за последний кадр частица отскочила от правой стенки (vx < 0 после step), а до этого vx > 0
// мы упрощаем: считаем удар, если частица сейчас касается правой стенки
}
// удаляем старые удары (>1 сек назад)
const tSec = t / 1000;
while(hitTimes.length && hitTimes[0] < tSec - 1) hitTimes.shift();
if(tSec - lastUpdate > 0.3){
lastUpdate = tSec;
const hits = hitTimes.length;
hitsEl.textContent = hits;
const n = +nInp.value;
const v = +vInp.value;
// условное «давление» — пропорционально n*v^2 / 1000
const p = (n * v * v) / 1000;
presEl.textContent = p.toFixed(1);
}
render();
}
function render(){
let g = '';
// рамка сосуда
g += `<rect x="${OX}" y="${OY}" width="${W}" height="${H}" fill="none" stroke="#475569" stroke-width="1"/>`;
// правая стенка — подсвечена
g += `<line x1="${OX+W}" y1="${OY}" x2="${OX+W}" y2="${OY+H}" stroke="#f97316" stroke-width="3"/>`;
g += `<text x="${OX+W-4}" y="${OY-8}" text-anchor="end" fill="#f97316" font-family="Inter,sans-serif" font-size="11" font-weight="600">стенка</text>`;
// молекулы — сдвигаем в (OX, OY)
for(const p of sim.particles){
g += `<circle cx="${(p.x+OX).toFixed(1)}" cy="${(p.y+OY).toFixed(1)}" r="${sim.r}" fill="#60a5fa" stroke="#0f172a" stroke-width="0.5"/>`;
}
svg.innerHTML = g;
}
makeSim();
raf = requestAnimationFrame(frame);
nInp.addEventListener('input', () => {
nLab.textContent = nInp.value;
makeSim();
tempChanges.add('n'+nInp.value+'v'+vInp.value);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true; addXp(10, 'p3-iv1'); bumpProgress('p3', 15);
}
});
vInp.addEventListener('input', () => {
vLab.textContent = vInp.value;
// не пересоздаём — масштабируем скорость
const cur = +vInp.value;
let avg = 0;
for(const p of sim.particles) avg += Math.hypot(p.vx, p.vy);
avg /= sim.particles.length;
if(avg > 0.01) sim.setSpeed(cur / avg);
tempChanges.add('n'+nInp.value+'v'+vInp.value);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true; addXp(10, 'p3-iv1'); bumpProgress('p3', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => { makeSim(); });
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — Калькулятор основного уравнения МКТ */
(function(){
const nI = document.getElementById('p3-iv2-n');
const mI = document.getElementById('p3-iv2-m');
const vI = document.getElementById('p3-iv2-v');
const out = document.getElementById('p3-iv2-out');
const fb = document.getElementById('p3-iv2-fb');
const go = document.getElementById('p3-iv2-go');
const used = new Set();
let _done = false;
function calc(){
const n = parseFloat(nI.value);
const m0 = parseFloat(mI.value);
const v2 = parseFloat(vI.value);
if(!isFinite(n) || !isFinite(m0) || !isFinite(v2) || n <= 0 || m0 <= 0 || v2 <= 0){
feedback(fb, false, '&#10007; Введи положительные значения во все поля.');
return;
}
// n × 10^25, m0 × 10^-26, v2 × 10^6 → p = 1/3 · n·m0·v2 × 10^(25-26+6) = × 10^5
const pCoef = (1/3) * n * m0 * v2;
const pPa = pCoef * 1e5; // Па
// средняя кинетическая энергия: Ek = (1/2) m0 v2 = (1/2) m0(10^-26) · v2(10^6) = (1/2) m0 v2 × 10^-20 Дж
const EkCoef = 0.5 * m0 * v2; // в 10^-20 Дж
const EkJ = EkCoef * 1e-20;
const EkEv = EkJ / 1.6e-19;
out.innerHTML =
'<div style="margin-bottom:8px"><b>Подставляем:</b></div>'
+ '<div style="margin-bottom:8px">$p = \\dfrac{1}{3} \\cdot ' + (+n.toFixed(3)) + ' \\cdot 10^{25} \\cdot ' + (+m0.toFixed(3)) + ' \\cdot 10^{-26} \\cdot ' + (+v2.toFixed(3)) + ' \\cdot 10^{6}$</div>'
+ '<div style="margin-bottom:8px"><b>Давление:</b> $p \\approx ' + (+pCoef.toFixed(3)) + ' \\cdot 10^{5}$ Па $\\approx ' + (+(pPa/1e5).toFixed(3)) + '$ атм</div>'
+ '<div><b>Средняя кинетическая энергия молекулы:</b> $\\overline{E_k} = \\dfrac{1}{2} m_0 \\overline{v^2} \\approx ' + (+EkCoef.toFixed(3)) + ' \\cdot 10^{-20}$ Дж $\\approx ' + (+EkEv.toFixed(4)) + '$ эВ</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add((+n.toFixed(2))+':'+(+m0.toFixed(2))+':'+(+v2.toFixed(2)));
if(!_done && used.size >= 3){ _done = true; addXp(10, 'p3-iv2'); bumpProgress('p3', 15); }
}
go.addEventListener('click', calc);
[nI, mI, vI].forEach(el => el.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }));
})();
/* IV3 — квикфайр Идеальный/Реальный */
(function(){
const Q = [
{ q:'Воздух в комнате (комнатная температура, атмосферное давление)', ans:0, why:'При обычных условиях воздух близок к идеальному.' },
{ q:'Водяной пар у поверхности кипящей воды (готов конденсироваться)', ans:1, why:'Близок к конденсации — взаимодействия молекул нельзя пренебречь.' },
{ q:'Гелий при $T = 4$ К (на грани ожижения)', ans:1, why:'При очень низкой $T$ молекулы сильно взаимодействуют.' },
{ q:'Кислород в баллоне под давлением 200 атм', ans:1, why:'При высоком давлении расстояния между молекулами малы.' },
{ q:'Воздух в верхних слоях атмосферы (низкое давление)', ans:0, why:'Чем ниже $p$ — тем ближе к идеальному газу.' },
{ q:'Разреженный газ в плазме разряда (низкое $p$, очень высокая $T$)', ans:0, why:'Низкое давление и высокая $T$ — идеальные условия для модели.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p3-iv3-q');
const oEl = document.getElementById('p3-iv3-opts');
const fb = document.getElementById('p3-iv3-fb');
const iEl = document.getElementById('p3-iv3-i');
const sEl = document.getElementById('p3-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p3-iv3'); bumpProgress('p3', 25); }
else if(score >= 4){ addXp(8, 'p3-iv3'); bumpProgress('p3', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="0">Близко к идеальному</button><button class="btn primary" data-v="1" style="background:#ef4444;border-color:#ef4444">Сильно отличается</button>';
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p3-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр МКТ */
(function(){
const Q = [
{ q:'$n = 3 \\cdot 10^{25}$ м$^{-3}$, $m_0 = 5 \\cdot 10^{-26}$ кг, $\\overline{v^2} = 4 \\cdot 10^{5}$ м²/с². Введи коэффициент при $10^5$ Па.', ans:2, hint:'$p = \\frac{1}{3} \\cdot 3 \\cdot 5 \\cdot 4 \\cdot 10^{25-26+5} = 20 \\cdot 10^{4} = 2 \\cdot 10^{5}$' },
{ q:'$\\overline{E_k} = 6 \\cdot 10^{-21}$ Дж, $n = 5 \\cdot 10^{25}$ м$^{-3}$. Введи коэффициент при $10^5$ Па.', ans:2, hint:'$p = \\frac{2}{3} n \\overline{E_k} = \\frac{2}{3} \\cdot 5 \\cdot 10^{25} \\cdot 6 \\cdot 10^{-21} = 2 \\cdot 10^{5}$' },
{ q:'Если давление и концентрацию увеличить вдвое, во сколько раз изменится $\\overline{v^2}$?', ans:1, hint:'$p = \\frac{1}{3} n m_0 \\overline{v^2}$: если $p$ и $n$ ×2, то $\\overline{v^2}$ не меняется.' },
{ q:'Если массу молекулы $m_0$ увеличить в 4 раза при том же $p$ и $n$, во сколько раз изменится $\\overline{v^2}$?', ans:0.25, hint:'$\\overline{v^2} \\sim 1/m_0$ при фиксированных $p, n$ — уменьшится в 4 раза.' },
{ q:'Концентрация газа $n = 2{,}5 \\cdot 10^{25}$ м$^{-3}$. Сколько молекул в $1$ м³? Введи коэффициент при $10^{25}$.', ans:2.5, hint:'$N = n \\cdot V = 2{,}5 \\cdot 10^{25}$.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p3-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p3-iv4'); bumpProgress('p3', 25); }
else if(score >= 3){ addXp(8, 'p3-iv4'); bumpProgress('p3', 15); }
return;
}
document.getElementById('p3-iv4-i').textContent = (i+1);
document.getElementById('p3-iv4-s').textContent = score;
document.getElementById('p3-iv4-q').innerHTML = Q[i].q;
document.getElementById('p3-iv4-ans').value = '';
renderMath(document.getElementById('p3-iv4-q'));
document.getElementById('p3-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p3-iv4-fb');
const raw = document.getElementById('p3-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p3-iv4-s').textContent = score;
i++;
setTimeout(show, 1700);
}
document.getElementById('p3-iv4-go').addEventListener('click', go);
document.getElementById('p3-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p3-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p3');
}
function build_p4(){
const box = document.getElementById('p4-body');
let html = '';
html += makeCard('theory', "Температура. Тепловое равновесие", "§4", `
<p><b>Температура. Тепловое равновесие</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
/* THEORY 1 — тепловое равновесие и температура */
html += makeCard('theory', "Тепловое равновесие и температура", "§4", `
<p><b>Тепловое равновесие</b> — состояние термодинамической системы, в котором её макроскопические параметры (температура, давление, плотность) не меняются со временем.</p>
<p style="margin-top:8px"><b>Температура</b> — физическая величина, характеризующая состояние теплового равновесия. Если два тела находятся в тепловом равновесии — их температуры равны.</p>
<p style="margin-top:10px"><b>Шкалы температуры:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Цельсия</b> ($t$, °C): $$C — таяние льда, $100°$C — кипение воды (при $p = 101{,}3$ кПа).</li>
<li><b>Кельвина</b> ($T$, К): абсолютная шкала. $T = 0$ K — абсолютный нуль.</li>
</ul>
<p><b>Связь шкал:</b></p>
<p style="text-align:center;margin:10px 0">$$T = t + 273{,}15 \\approx t + 273$$</p>
<p>Абсолютный нуль ($0$ К $= -273{,}15°$C) — теоретический предел: невозможно охладить вещество ниже.</p>
`);
/* THEORY 2 — температура и средняя кинетическая энергия */
html += makeCard('rule', "Температура и средняя кинетическая энергия", "§4", `
<p><b>Постоянная Больцмана:</b> $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К.</p>
<p style="margin-top:8px"><b>Главная формула</b> — связь температуры и средней кинетической энергии молекул:</p>
<p style="text-align:center;margin:10px 0">$$\\overline{E_k} = \\dfrac{3}{2} k_B T$$</p>
<p>Из этой формулы и основного уравнения МКТ ($p = \\frac{2}{3} n \\overline{E_k}$) получаем уравнение состояния:</p>
<p style="text-align:center;margin:10px 0">$$p = n k_B T$$</p>
<p><b>Температура — мера средней кинетической энергии хаотического теплового движения молекул.</b></p>
`);
/* THEORY 3 — средняя квадратичная скорость */
html += makeCard('example', "Средняя квадратичная скорость молекул", "§4", `
<p>Из $\\overline{E_k} = \\dfrac{1}{2} m_0 \\overline{v^2} = \\dfrac{3}{2} k_B T$ выводим:</p>
<p style="text-align:center;margin:10px 0">$$\\overline{v^2} = \\dfrac{3 k_B T}{m_0}$$</p>
<p><b>Средняя квадратичная скорость:</b></p>
<p style="text-align:center;margin:10px 0">$$v_{ср.кв.} = \\sqrt{\\dfrac{3 k_B T}{m_0}} = \\sqrt{\\dfrac{3 R T}{M}}$$</p>
<p>где $R = N_A \\cdot k_B = 8{,}314$ Дж/(моль·К) — универсальная газовая постоянная.</p>
<p style="margin-top:8px"><b>Примеры</b> (при $T = 300$ К):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Водород ($M = 2$ г/моль): $v \\approx 1934$ м/с.</li>
<li>Азот ($M = 28$ г/моль): $v \\approx 517$ м/с.</li>
<li>Кислород ($M = 32$ г/моль): $v \\approx 484$ м/с.</li>
<li>Углекислый газ ($M = 44$ г/моль): $v \\approx 412$ м/с.</li>
</ul>
<p>Чем выше $T$ и легче молекула — тем больше скорость.</p>
`);
/* INTERACTIVE 1 — Температурный визуализатор */
html += `<div class="wg" id="p4-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Температурный визуализатор</div></div>
<div class="wg-help">Меняй $T$ — наблюдай, как меняется скорость молекул ($\\sim \\sqrt{T}$) и цвет (от холодного к горячему).</div>
<div class="sliders">
<label>Температура $T$: <b id="p4-iv1-tL">300</b> К <input type="range" id="p4-iv1-T" min="100" max="1000" value="300" step="10"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p4-iv1-svg" viewBox="0 0 420 280" width="100%" style="max-width:420px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p4-iv1-pause">Пауза</button>
<button class="btn" id="p4-iv1-reset">Сброс</button>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.9rem;line-height:1.75">
<div>$T = $ <b id="p4-iv1-T2">300</b> К ($t = $ <b id="p4-iv1-tc">27</b> °C)</div>
<div>$\\overline{E_k} = \\dfrac{3}{2} k_B T \\approx$ <b id="p4-iv1-Ek">6.21</b> $\\cdot 10^{-21}$ Дж</div>
<div>$v_{ср.кв.}$ (для азота, $m_0 = 4{,}65 \\cdot 10^{-26}$ кг) $\\approx$ <b id="p4-iv1-v">517</b> м/с</div>
</div>
</div>`;
/* INTERACTIVE 2 — Калькулятор температуры и скорости */
html += `<div class="wg" id="p4-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $T$, $\\overline{E_k}$ и $v_{ср.кв.}$</div></div>
<div class="wg-help">Введи $T$ и выбери газ — получи среднюю кинетическую энергию и среднюю квадратичную скорость.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Температура $T$, К <input type="number" id="p4-iv2-T" class="tinp" style="width:100%;margin-top:6px" value="300" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Газ ($M$, г/моль)
<select id="p4-iv2-M" class="tinp" style="width:100%;margin-top:6px">
<option value="2">Водород H₂ (M = 2)</option>
<option value="4">Гелий He (M = 4)</option>
<option value="28" selected>Азот N₂ (M = 28)</option>
<option value="32">Кислород O₂ (M = 32)</option>
<option value="44">Углекислый газ CO₂ (M = 44)</option>
</select>
</label>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p4-iv2-go">Вычислить</button>
</div>
<div id="p4-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p4-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Конвертер шкал */
html += `<div class="wg" id="p4-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Конвертер шкал и быстрая проверка</div></div>
<div class="wg-help">6 вопросов: переведи температуру по шкалам или узнай типичное значение. Выбери правильный ответ.</div>
<div class="score-display"><span>Задача <b id="p4-iv3-i">1</b> / 6</span><span>Очки: <b id="p4-iv3-s">0</b> / 6</span></div>
<div id="p4-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p4-iv3-opts" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px"></div>
<div class="feedback" id="p4-iv3-fb"></div>
<div class="actions"><button class="btn" id="p4-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр температуры */
html += `<div class="wg" id="p4-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр температуры</div></div>
<div class="wg-help">5 задач. Используй $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К, $R = 8{,}3$ Дж/(моль·К).</div>
<div class="score-display"><span>Задача <b id="p4-iv4-i">1</b> / 5</span><span>Очки: <b id="p4-iv4-s">0</b> / 5</span></div>
<div id="p4-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p4-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p4-iv4-go">Проверить</button>
<button class="btn" id="p4-iv4-start">Заново</button>
</div>
<div class="feedback" id="p4-iv4-fb"></div>
</div>`;
html += secNav('p3', 'p5');
html += readButton('p4');
box.innerHTML = html;
renderMath(box);
/* IV1 — Температурный визуализатор */
(function(){
const svg = document.getElementById('p4-iv1-svg');
const tInp = document.getElementById('p4-iv1-T');
const tLab = document.getElementById('p4-iv1-tL');
const T2 = document.getElementById('p4-iv1-T2');
const tc = document.getElementById('p4-iv1-tc');
const EkEl = document.getElementById('p4-iv1-Ek');
const vEl = document.getElementById('p4-iv1-v');
const btnPause = document.getElementById('p4-iv1-pause');
const btnReset = document.getElementById('p4-iv1-reset');
const W = 400, H = 220, OX = 10, OY = 30;
const baseSpeed = 80; // соответствует T0 = 300 К
let raf = null, lastT = 0, paused = false;
let sim = null;
const tempChanges = new Set();
let _xpDone = false;
function tempColor(T){
// 100 К → синий (#60a5fa), 300 К → бирюзовый, 600 К → жёлтый, 1000 К → красный
const t = Math.max(0, Math.min(1, (T - 100) / 900));
// интерполяция: синий (0.16, 0.5, 0.96) → красный (0.94, 0.27, 0.21)
const r = Math.round(255 * (0.16 + t * (0.94 - 0.16)));
const g = Math.round(255 * (0.5 + t * (0.27 - 0.5)));
const b = Math.round(255 * (0.96 + t * (0.21 - 0.96)));
return `rgb(${r},${g},${b})`;
}
function makeSim(){
sim = PHYS.createGasSim({W, H, N: 40, speed: baseSpeed, r: 4});
applyTemp(+tInp.value);
}
function applyTemp(T){
const targetScale = Math.sqrt(T/300);
let curAvg = 0;
for(const p of sim.particles) curAvg += Math.hypot(p.vx, p.vy);
curAvg /= sim.particles.length;
const targetAvg = baseSpeed * targetScale;
if(curAvg > 0.01) sim.setSpeed(targetAvg / curAvg);
updateLabels(T);
}
function updateLabels(T){
const kB = PHYS.CONST.kB;
const Ek = 1.5 * kB * T; // Дж
// m0 для азота: M = 0.028 кг/моль / NA = 4.65e-26 кг
const m0 = 0.028 / PHYS.CONST.NA;
const v = Math.sqrt(3 * kB * T / m0);
T2.textContent = T;
tc.textContent = (T - 273.15).toFixed(1);
EkEl.textContent = (Ek / 1e-21).toFixed(2);
vEl.textContent = Math.round(v);
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
sim.step(dt);
render();
}
function render(){
const T = +tInp.value;
const col = tempColor(T);
let g = '';
g += `<rect x="${OX}" y="${OY}" width="${W}" height="${H}" fill="none" stroke="#475569" stroke-width="1"/>`;
// лейбл температуры
g += `<text x="${OX+W/2}" y="${OY-10}" text-anchor="middle" fill="${col}" font-family="Inter,sans-serif" font-size="14" font-weight="700">T = ${T} K</text>`;
for(const p of sim.particles){
g += `<circle cx="${(p.x+OX).toFixed(1)}" cy="${(p.y+OY).toFixed(1)}" r="${sim.r}" fill="${col}" stroke="#0f172a" stroke-width="0.5"/>`;
}
svg.innerHTML = g;
}
makeSim();
raf = requestAnimationFrame(frame);
tInp.addEventListener('input', () => {
const T = +tInp.value;
tLab.textContent = T;
applyTemp(T);
// snap к ключевым точкам
tempChanges.add(T);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true; addXp(10, 'p4-iv1'); bumpProgress('p4', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => { makeSim(); });
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — Калькулятор T, Ek, v */
(function(){
const TI = document.getElementById('p4-iv2-T');
const MI = document.getElementById('p4-iv2-M');
const out = document.getElementById('p4-iv2-out');
const fb = document.getElementById('p4-iv2-fb');
const go = document.getElementById('p4-iv2-go');
const used = new Set();
let _done = false;
function calc(){
const T = parseFloat(TI.value);
const Mg = parseFloat(MI.value); // г/моль
if(!isFinite(T) || T <= 0){ feedback(fb, false, '&#10007; Введи положительную температуру (К).'); return; }
const kB = PHYS.CONST.kB;
const R = PHYS.CONST.R;
const M = Mg * 1e-3; // кг/моль
const Ek = 1.5 * kB * T; // Дж
const v = Math.sqrt(3 * R * T / M);
const m0 = M / PHYS.CONST.NA;
out.innerHTML =
'<div style="margin-bottom:8px"><b>Подставляем:</b></div>'
+ '<div style="margin-bottom:8px">$\\overline{E_k} = \\dfrac{3}{2} k_B T = \\dfrac{3}{2} \\cdot 1{,}38 \\cdot 10^{-23} \\cdot ' + (+T.toFixed(2)) + ' \\approx ' + (+(Ek/1e-21).toFixed(2)) + ' \\cdot 10^{-21}$ Дж</div>'
+ '<div style="margin-bottom:8px">$v_{ср.кв.} = \\sqrt{\\dfrac{3 R T}{M}} = \\sqrt{\\dfrac{3 \\cdot 8{,}314 \\cdot ' + (+T.toFixed(2)) + '}{' + Mg + ' \\cdot 10^{-3}}} \\approx ' + Math.round(v) + '$ м/с</div>'
+ '<div style="font-size:.88rem;color:var(--muted)">Масса одной молекулы: $m_0 = M/N_A \\approx ' + (+(m0/1e-26).toFixed(3)) + ' \\cdot 10^{-26}$ кг</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add(T+':'+Mg);
if(!_done && used.size >= 3){ _done = true; addXp(10, 'p4-iv2'); bumpProgress('p4', 15); }
}
go.addEventListener('click', calc);
TI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
})();
/* IV3 — Конвертер шкал */
(function(){
const opts = ['0 К', '273 К', '300 К', '373 К'];
const Q = [
{ q:'$t = 0°$C — это сколько К?', ans:1, why:'$T = 0 + 273 = 273$ К.' },
{ q:'$t = -273°$C — это сколько К? (округлим)', ans:0, why:'$T = -273 + 273 = 0$ К — абсолютный нуль.' },
{ q:'$t = 100°$C — это сколько К?', ans:3, why:'$T = 100 + 273 = 373$ К.' },
{ q:'$t = 27°$C (комнатная) — это сколько К?', ans:2, why:'$T = 27 + 273 = 300$ К.' },
{ q:'Абсолютный нуль — это какая температура?', ans:0, why:'Абсолютный нуль = $0$ К $= -273{,}15°$C.' },
{ q:'Тёплая комната ($t \\approx 27°$C) — это какая температура $T$?', ans:2, why:'Комнатная $T \\approx 300$ К.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p4-iv3-q');
const oEl = document.getElementById('p4-iv3-opts');
const fb = document.getElementById('p4-iv3-fb');
const iEl = document.getElementById('p4-iv3-i');
const sEl = document.getElementById('p4-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p4-iv3'); bumpProgress('p4', 25); }
else if(score >= 4){ addXp(8, 'p4-iv3'); bumpProgress('p4', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = opts.map((o, j) => '<button class="btn primary" data-v="'+j+'">'+o+'</button>').join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p4-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр температуры */
(function(){
const Q = [
{ q:'При $T = 300$ К средняя кинетическая энергия молекулы. Введи мантиссу для $a \\cdot 10^{-21}$ Дж.', ans:6.2, hint:'$E_k = \\frac{3}{2} k_B T = 1{,}5 \\cdot 1{,}38 \\cdot 10^{-23} \\cdot 300 \\approx 6{,}21 \\cdot 10^{-21}$ Дж' },
{ q:'$t = 27°$C — это сколько К?', ans:300, hint:'$T = t + 273 = 300$' },
{ q:'При какой температуре $\\overline{E_k}$ удвоится по сравнению с $T = 300$ К?', ans:600, hint:'$E_k \\sim T$, значит $T = 2 \\cdot 300 = 600$ К' },
{ q:'$v_{ср.кв.}$ молекул азота ($M = 28$ г/моль) при $T = 300$ К, в м/с (допуск $\\pm 20$).', ans:516, hint:'$v = \\sqrt{3 R T / M} = \\sqrt{3 \\cdot 8{,}3 \\cdot 300 / 0{,}028} \\approx 516$ м/с' },
{ q:'При $T = 0$ К средняя кинетическая энергия молекулы (Дж)?', ans:0, hint:'$E_k = \\frac{3}{2} k_B \\cdot 0 = 0$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p4-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p4-iv4'); bumpProgress('p4', 25); }
else if(score >= 3){ addXp(8, 'p4-iv4'); bumpProgress('p4', 15); }
return;
}
document.getElementById('p4-iv4-i').textContent = (i+1);
document.getElementById('p4-iv4-s').textContent = score;
document.getElementById('p4-iv4-q').innerHTML = Q[i].q;
document.getElementById('p4-iv4-ans').value = '';
renderMath(document.getElementById('p4-iv4-q'));
document.getElementById('p4-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p4-iv4-fb');
const raw = document.getElementById('p4-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
let tol;
if(Q[i].ans === 0) tol = 0.05;
else if(Q[i].ans === 516) tol = 20;
else tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p4-iv4-s').textContent = score;
i++;
setTimeout(show, 1700);
}
document.getElementById('p4-iv4-go').addEventListener('click', go);
document.getElementById('p4-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p4-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p4');
}