diff --git a/frontend/textbooks/physics_10_ch1.html b/frontend/textbooks/physics_10_ch1.html
index a915518..7e71d55 100644
--- a/frontend/textbooks/physics_10_ch1.html
+++ b/frontend/textbooks/physics_10_ch1.html
@@ -2004,34 +2004,799 @@ function build_p4(){
function build_p5(){
const box = document.getElementById('p5-body');
let html = '';
- html += makeCard('theory', "Уравнение состояния идеального газа", "§5", `
-
Уравнение состояния идеального газа — этот параграф в разработке (Phase 1+).
- Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.
-
- Phase 0: создан скелет учебника. Phase 1+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
-
+
+ /* THEORY 1 — Уравнение Менделеева–Клапейрона */
+ html += makeCard('theory', "Уравнение Менделеева–Клапейрона", "§5", `
+ Уравнение состояния идеального газа связывает три макропараметра — давление $p$, объём $V$ и температуру $T$. Для $\\nu$ молей газа:
+ $$pV = \\nu R T$$
+ где:
+
+ $p$ — давление (Па);
+ $V$ — объём (м³);
+ $T$ — абсолютная температура (К);
+ $\\nu$ — количество вещества (моль);
+ $R = 8{,}314$ Дж/(моль·К) — универсальная газовая постоянная.
+
+ Через массу газа ($\\nu = m/M$):
+ $$pV = \\dfrac{m}{M} R T$$
+ Через концентрацию ($n = N/V$, $\\nu = N/N_A$, $R = N_A k_B$):
+ $$p = n k_B T$$
+ Это разные формы одного и того же уравнения состояния.
`);
+
+ /* THEORY 2 — Объединённый газовый закон */
+ html += makeCard('rule', "Объединённый газовый закон", "§5", `
+ Для постоянной массы идеального газа при переходе из состояния 1 в состояние 2:
+ $$\\dfrac{p V}{T} = \\text{const} \\quad \\Rightarrow \\quad \\dfrac{p_1 V_1}{T_1} = \\dfrac{p_2 V_2}{T_2}$$
+ Это позволяет связать два состояния газа без знания $\\nu$ или $M$.
+ Пример. Газ при $T_1 = 300$ К, $p_1 = 1$ атм, $V_1 = 10$ л нагрели до $T_2 = 450$ К, объём стал $V_2 = 12$ л. Найти $p_2$.
+ $$p_2 = \\dfrac{p_1 V_1 T_2}{T_1 V_2} = \\dfrac{1 \\cdot 10 \\cdot 450}{300 \\cdot 12} = 1{,}25 \\text{ атм}$$
+ `);
+
+ /* THEORY 3 — Нормальные условия и молярный объём */
+ html += makeCard('example', "Нормальные условия и молярный объём", "§5", `
+ Нормальные условия (н.у.):
+
+ $T_0 = 273$ К ($0°$C);
+ $p_0 = 101{,}3$ кПа $\\approx 1$ атм.
+
+ При нормальных условиях молярный объём любого идеального газа одинаков:
+ $$V_M = \\dfrac{R T_0}{p_0} = \\dfrac{8{,}314 \\cdot 273}{101300} \\approx 22{,}4 \\text{ л/моль}$$
+ 1 моль любого идеального газа (водорода, азота, кислорода, углекислого газа…) при $0°$C и 1 атм занимает $22{,}4$ литра.
+ Закон Авогадро: в равных объёмах разных газов при одинаковых $p$ и $T$ содержится одинаковое число молекул.
+ `);
+
+ /* INTERACTIVE 1 — Визуализатор уравнения состояния */
+ html += `
+
+
Меняй $\\nu$, $T$, $V$ — получай давление по формуле $p = \\nu R T / V$. Цвет «контейнера» — по $T$, число точек — по $\\nu$.
+
+ Кол-во вещества $\\nu$: 1.0 моль
+ Температура $T$: 300 К
+ Объём $V$: 10 л
+
+
+
+
+
+
$pV = \\nu R T = $ — Дж
+
Давление: $p = \\dfrac{\\nu R T}{V} \\approx $ — Па $\\approx$ — атм
+
+
`;
+
+ /* INTERACTIVE 2 — Калькулятор Менделеева–Клапейрона */
+ html += `
+
+
Выбери искомую величину — введи остальные параметры. Используется $pV = (m/M) R T$.
+
+ Найти $p$
+ Найти $V$
+ Найти $T$
+ Найти $m$
+
+
+
+ Вычислить
+
+
+
+
`;
+
+ /* INTERACTIVE 3 — DnD: Какое отношение работает? */
+ html += `
+
+
Перетащи 6 формул в 3 ящика — какой раздел теории газа описывает каждая.
+
+
+
+
+
Объединённый газовый закон
+
Частный случай / константа
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 4 — Тренажёр уравнения состояния */
+ html += `
+
+
6 задач. Используй $R = 8{,}3$ Дж/(моль·К), $1$ атм $= 10^5$ Па, $V_M = 22{,}4$ л/моль.
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ ответ =
+
+ Проверить
+ Заново
+
+
+
`;
+
html += secNav('p4', 'p6');
html += readButton('p5');
+
box.innerHTML = html;
renderMath(box);
+
+ /* IV1 — Визуализатор уравнения состояния */
+ (function(){
+ const svg = document.getElementById('p5-iv1-svg');
+ const nInp = document.getElementById('p5-iv1-n');
+ const tInp = document.getElementById('p5-iv1-t');
+ const vInp = document.getElementById('p5-iv1-v');
+ const nLab = document.getElementById('p5-iv1-nL');
+ const tLab = document.getElementById('p5-iv1-tL');
+ const vLab = document.getElementById('p5-iv1-vL');
+ const rhsEl = document.getElementById('p5-iv1-rhs');
+ const pPaEl = document.getElementById('p5-iv1-pPa');
+ const pAtmEl = document.getElementById('p5-iv1-pAtm');
+ const R = 8.314;
+ const seen = new Set();
+ let _xpDone = false;
+
+ function tColor(T){
+ const t = Math.max(0, Math.min(1, (T - 100) / 700));
+ 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 fmtSci(x){
+ if(x === 0) return '0';
+ const a = Math.abs(x);
+ if(a >= 1e4 || a < 1e-2){
+ const exp = Math.floor(Math.log10(a));
+ const m = x / Math.pow(10, exp);
+ return m.toFixed(2) + '·10^{' + exp + '}';
+ }
+ return (+x.toFixed(2)).toString();
+ }
+ function render(){
+ const nu = +nInp.value;
+ const T = +tInp.value;
+ const Vl = +vInp.value;
+ nLab.textContent = nu.toFixed(1);
+ tLab.textContent = T;
+ vLab.textContent = Vl.toFixed(1);
+
+ const Vm3 = Vl * 1e-3;
+ const pPa = nu * R * T / Vm3;
+ const pAtm = pPa / 1e5;
+ const rhs = nu * R * T;
+
+ rhsEl.textContent = rhs.toFixed(1);
+ pPaEl.textContent = (pPa / 1e5).toFixed(2) + '·10⁵';
+ pAtmEl.textContent = pAtm.toFixed(2);
+
+ // SVG: прямоугольник, ширина по V (1..50 л → 60..360 px)
+ const W = 380, H = 200, pad = 10;
+ const boxW = 60 + (Vl - 1) * (300 / 49);
+ const boxH = 130;
+ const bx = (W - boxW) / 2, by = pad + 30;
+ const col = tColor(T);
+
+ let g = '';
+ g += ' ';
+ // молекулы — пропорционально nu (1..30 точек)
+ const Nshow = Math.max(3, Math.round(nu * 8));
+ for(let i = 0; i < Nshow; i++){
+ const px = bx + 6 + Math.random() * (boxW - 12);
+ const py = by + 6 + Math.random() * (boxH - 12);
+ g += ' ';
+ }
+ g += 'T = '+T+' K · V = '+Vl.toFixed(1)+' л · ν = '+nu.toFixed(1)+' моль ';
+ g += 'p ≈ '+pAtm.toFixed(2)+' атм ';
+ svg.innerHTML = g;
+
+ seen.add(Math.round(nu*10)+':'+T+':'+Math.round(Vl*2));
+ if(!_xpDone && seen.size >= 4){ _xpDone = true; addXp(10, 'p5-iv1'); bumpProgress('p5', 15); }
+ }
+ nInp.addEventListener('input', render);
+ tInp.addEventListener('input', render);
+ vInp.addEventListener('input', render);
+ render();
+ })();
+
+ /* IV2 — Калькулятор Менделеева–Клапейрона */
+ (function(){
+ const inpsBox = document.getElementById('p5-iv2-inputs');
+ const out = document.getElementById('p5-iv2-out');
+ const fb = document.getElementById('p5-iv2-fb');
+ const go = document.getElementById('p5-iv2-go');
+ const R = 8.314;
+ const calcCount = new Set();
+ let _done = false;
+
+ function fieldHTML(id, label, val){
+ return ''+label+' ';
+ }
+ function build(mode){
+ let h = '';
+ // Всегда показываем M (молярную массу)
+ h += fieldHTML('p5-iv2-M', '$M$, г/моль', '32');
+ if(mode !== 'p') h += fieldHTML('p5-iv2-p', '$p$, атм', '1');
+ if(mode !== 'V') h += fieldHTML('p5-iv2-V', '$V$, л', '22.4');
+ if(mode !== 'T') h += fieldHTML('p5-iv2-T', '$T$, К', '273');
+ if(mode !== 'm') h += fieldHTML('p5-iv2-m', '$m$, г', '32');
+ inpsBox.innerHTML = h;
+ renderMath(inpsBox);
+ }
+ function num(id){ const el = document.getElementById(id); return el ? parseFloat((el.value||'').replace(',','.')) : NaN; }
+ function getMode(){
+ const r = document.querySelector('input[name="p5-iv2-mode"]:checked');
+ return r ? r.value : 'p';
+ }
+ function calc(){
+ const mode = getMode();
+ const Mg = num('p5-iv2-M');
+ if(!isFinite(Mg) || Mg <= 0){ feedback(fb, false, '✗ Введи положительную $M$ (г/моль).'); return; }
+ const M = Mg * 1e-3;
+ let pPa, Vm3, T, mKg;
+ if(mode !== 'p'){ const pa = num('p5-iv2-p'); if(!isFinite(pa)||pa<=0){feedback(fb,false,'✗ Введи положительное $p$.');return;} pPa = pa * 1e5; }
+ if(mode !== 'V'){ const vl = num('p5-iv2-V'); if(!isFinite(vl)||vl<=0){feedback(fb,false,'✗ Введи положительный $V$.');return;} Vm3 = vl * 1e-3; }
+ if(mode !== 'T'){ T = num('p5-iv2-T'); if(!isFinite(T)||T<=0){feedback(fb,false,'✗ Введи положительную $T$.');return;} }
+ if(mode !== 'm'){ const mg = num('p5-iv2-m'); if(!isFinite(mg)||mg<=0){feedback(fb,false,'✗ Введи положительную $m$.');return;} mKg = mg * 1e-3; }
+
+ let res = '', resVal = 0, unit = '';
+ if(mode === 'p'){
+ // p = (m/M) R T / V
+ pPa = (mKg / M) * R * T / Vm3;
+ resVal = pPa / 1e5; unit = 'атм';
+ res = '$p = \\dfrac{m R T}{M V} \\approx '+(pPa/1e5).toFixed(3)+' \\cdot 10^5$ Па $\\approx '+resVal.toFixed(3)+'$ атм';
+ } else if(mode === 'V'){
+ Vm3 = (mKg / M) * R * T / pPa;
+ resVal = Vm3 * 1000; unit = 'л';
+ res = '$V = \\dfrac{m R T}{M p} \\approx '+(Vm3*1000).toFixed(3)+'$ л';
+ } else if(mode === 'T'){
+ T = pPa * Vm3 * M / (mKg * R);
+ resVal = T; unit = 'К';
+ res = '$T = \\dfrac{p V M}{m R} \\approx '+T.toFixed(1)+'$ К';
+ } else { // m
+ mKg = pPa * Vm3 * M / (R * T);
+ resVal = mKg * 1000; unit = 'г';
+ res = '$m = \\dfrac{p V M}{R T} \\approx '+(mKg*1000).toFixed(3)+'$ г';
+ }
+ out.innerHTML = 'Уравнение: $pV = \\dfrac{m}{M} R T$
'
+ + 'Решение: '+res+'
'
+ + 'Ответ: '+(+resVal.toFixed(3))+' '+unit+'
';
+ renderMath(out);
+ feedback(fb, true, '✓ Вычислено.');
+ calcCount.add(mode + ':' + Math.round(resVal*100));
+ if(!_done && calcCount.size >= 3){ _done = true; addXp(10, 'p5-iv2'); bumpProgress('p5', 15); }
+ }
+ document.querySelectorAll('input[name="p5-iv2-mode"]').forEach(r => r.addEventListener('change', () => build(getMode())));
+ go.addEventListener('click', calc);
+ build('p');
+ })();
+
+ /* IV3 — DnD сортер формул */
+ (function(){
+ const items = [
+ { id:'f1', cat:'eq', html:'$pV = \\nu R T$' },
+ { id:'f2', cat:'comb', html:'$\\dfrac{p_1 V_1}{T_1} = \\dfrac{p_2 V_2}{T_2}$' },
+ { id:'f3', cat:'eq', html:'$p = n k_B T$' },
+ { id:'f4', cat:'eq', html:'$pV = \\dfrac{m}{M} R T$' },
+ { id:'f5', cat:'sp', html:'$V_M = 22{,}4$ л/моль' },
+ { id:'f6', cat:'comb', html:'$\\dfrac{V_1}{T_1} = \\dfrac{V_2}{T_2}$ (при $p$=const)' },
+ ];
+ const sorter = setupSorter({
+ poolId:'p5-iv3-pool',
+ scopeSelector:'#p5-iv3',
+ items: items,
+ cats:['eq','comb','sp'],
+ columnLayout:false,
+ });
+ document.getElementById('p5-iv3-check').addEventListener('click', () => {
+ const fb = document.getElementById('p5-iv3-fb');
+ const placedCount = items.filter(it => sorter.placed[it.id]).length;
+ const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
+ if(placedCount < items.length){ feedback(fb, false, '✗ Размести все 6 формул.'); return; }
+ if(correct === items.length){ feedback(fb, true, '✓ Все 6 верно! +10 XP'); addXp(10,'p5-iv3'); bumpProgress('p5', 15); }
+ else feedback(fb, false, '✗ Правильно ' + correct + ' из 6. Попробуй ещё.');
+ });
+ document.getElementById('p5-iv3-reset').addEventListener('click', () => { sorter.reset(); document.getElementById('p5-iv3-fb').style.display = 'none'; });
+ })();
+
+ /* IV4 — Тренажёр уравнения состояния */
+ (function(){
+ const Q = [
+ { q:'1 моль газа при $T = 300$ К, $p = 1$ атм. Найти $V$ в литрах. ($R = 8{,}3$)', ans:24.9, hint:'$V = \\nu R T / p = 1 \\cdot 8{,}3 \\cdot 300 / 10^5 = 0{,}0249$ м³ = 24,9 л' },
+ { q:'2 моль газа при $T = 273$ К, $V = 44{,}8$ л. Найти $p$ в атм.', ans:1, hint:'$p = \\nu R T / V = 2 \\cdot 8{,}3 \\cdot 273 / 0{,}0448 \\approx 10^5$ Па = 1 атм' },
+ { q:'При нагревании от $T_1 = 300$ К до $T_2 = 600$ К при $V$ = const, во сколько раз изменится $p$?', ans:2, hint:'$p \\sim T$ при $V$=const, $p_2/p_1 = 600/300 = 2$' },
+ { q:'4 г водорода ($M = 2$ г/моль) при $T = 273$ К, $p = 1$ атм. $V$ в литрах?', ans:44.8, hint:'$\\nu = m/M = 4/2 = 2$ моль. $V = 2 \\cdot V_M = 2 \\cdot 22{,}4 = 44{,}8$ л' },
+ { q:'$V_1 = 10$ л при $T_1 = 300$ К, $p_1 = 1$ атм. Нагрев до $T_2 = 360$ К при $p$ = const. $V_2 = ?$ л', ans:12, hint:'$V_2 = V_1 T_2/T_1 = 10 \\cdot 360/300 = 12$' },
+ { q:'6 г кислорода ($M = 32$ г/моль). Сколько это моль? (до 0,01)', ans:0.19, hint:'$\\nu = m/M = 6/32 = 0{,}1875 \\approx 0{,}19$' },
+ ];
+ let i = 0, score = 0;
+ function show(){
+ if(i >= Q.length){
+ document.getElementById('p5-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ if(score === Q.length){ addXp(15, 'p5-iv4'); bumpProgress('p5', 25); }
+ else if(score >= 4){ addXp(8, 'p5-iv4'); bumpProgress('p5', 15); }
+ return;
+ }
+ document.getElementById('p5-iv4-i').textContent = (i+1);
+ document.getElementById('p5-iv4-s').textContent = score;
+ document.getElementById('p5-iv4-q').innerHTML = Q[i].q;
+ document.getElementById('p5-iv4-ans').value = '';
+ renderMath(document.getElementById('p5-iv4-q'));
+ document.getElementById('p5-iv4-fb').style.display = 'none';
+ }
+ function go(){
+ if(i >= Q.length) return;
+ const fb = document.getElementById('p5-iv4-fb');
+ const raw = document.getElementById('p5-iv4-ans').value.replace(',', '.');
+ const ans = parseFloat(raw);
+ if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; }
+ let tol;
+ if(Q[i].ans === 0.19) tol = 0.015;
+ else if(Q[i].ans === 24.9 || Q[i].ans === 44.8) tol = 0.6;
+ 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, '✓ Верно! '+Q[i].hint+'. Дальше ▶'); }
+ else feedback(fb, false, '✗ Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
+ document.getElementById('p5-iv4-s').textContent = score;
+ i++;
+ setTimeout(show, 1800);
+ }
+ document.getElementById('p5-iv4-go').addEventListener('click', go);
+ document.getElementById('p5-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
+ document.getElementById('p5-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
+ show();
+ })();
+
wireReadBtn('p5');
}
function build_p6(){
const box = document.getElementById('p6-body');
let html = '';
- html += makeCard('theory', "Изопроцессы", "§6", `
- Изопроцессы — этот параграф в разработке (Phase 1+).
- Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.
-
- Phase 0: создан скелет учебника. Phase 1+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
-
+
+ /* THEORY 1 — Три изопроцесса */
+ html += makeCard('theory', "Три изопроцесса", "§6", `
+ Изопроцесс — процесс, при котором один из параметров идеального газа остаётся постоянным (при постоянной массе газа).
+ 1) Изотермический ($T = \\text{const}$) — закон Бойля–Мариотта :
+ $$pV = \\text{const} \\quad \\Leftrightarrow \\quad p_1 V_1 = p_2 V_2$$
+ 2) Изобарный ($p = \\text{const}$) — закон Гей-Люссака :
+ $$\\dfrac{V}{T} = \\text{const} \\quad \\Leftrightarrow \\quad \\dfrac{V_1}{T_1} = \\dfrac{V_2}{T_2}$$
+ 3) Изохорный ($V = \\text{const}$) — закон Шарля :
+ $$\\dfrac{p}{T} = \\text{const} \\quad \\Leftrightarrow \\quad \\dfrac{p_1}{T_1} = \\dfrac{p_2}{T_2}$$
+ Все три закона — частные случаи объединённого газового закона $pV/T = \\text{const}$.
`);
+
+ /* THEORY 2 — Графики */
+ html += makeCard('rule', "Графики изопроцессов", "§6", `
+ В трёх системах координат изопроцессы выглядят так:
+
+
+
+ Процесс
+ $p$–$V$
+ $V$–$T$
+ $p$–$T$
+
+
+
+
+ Изотерма ($T$=const)
+ гипербола
+ прямая через 0
+ прямая через 0
+
+
+ Изобара ($p$=const)
+ горизонт. прямая
+ прямая через 0
+ горизонт. прямая
+
+
+ Изохора ($V$=const)
+ вертик. прямая
+ горизонт. прямая
+ прямая через 0
+
+
+
+ Главные характеристики:
+
+ Изотерма на $p$–$V$ — гипербола: $p = \\nu R T / V$.
+ Изобара и изохора на $V$–$T$ и $p$–$T$ — прямые, проходящие через начало координат .
+ Чем больше $T$ у изотермы, тем дальше гипербола от осей.
+
+ `);
+
+ /* THEORY 3 — Примеры */
+ html += makeCard('example', "Применение и примеры", "§6", `
+ Изотермический процесс: воздушный шарик медленно сжимают руками — температура почти не меняется (теплообмен со средой). $p_1 V_1 = p_2 V_2$.
+ Изобарный процесс: воздух в открытом цилиндре с поршнем нагревают — давление равно атмосферному (= const), объём растёт пропорционально $T$. $V_1 / T_1 = V_2 / T_2$.
+ Изохорный процесс: газ в герметичном сосуде нагревают — объём фиксирован, давление растёт пропорционально $T$. $p_1 / T_1 = p_2 / T_2$.
+ Пример. Газ в шине автомобиля изохорно нагревают летом от $T_1 = 290$ К до $T_2 = 320$ К. Если $p_1 = 2$ атм:
+ $$p_2 = p_1 \\dfrac{T_2}{T_1} = 2 \\cdot \\dfrac{320}{290} \\approx 2{,}21 \\text{ атм}$$
+ `);
+
+ /* INTERACTIVE 1 — Графики изопроцессов (главный визуал) */
+ html += ``;
+
+ /* INTERACTIVE 2 — Калькулятор изопроцессов */
+ html += `
+
+
Выбери тип процесса, введи $p_1, V_1, T_1$ и одну величину состояния 2 — получи недостающие.
+
+ Изотермический
+ Изобарный
+ Изохорный
+
+
+
+ Вычислить
+
+
+
+
`;
+
+ /* INTERACTIVE 3 — DnD: какой процесс */
+ html += `
+
+
Перетащи 6 ситуаций в нужный процесс.
+
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 4 — Тренажёр изопроцессов */
+ html += `
+
+
6 задач. Допуск $\\pm 5\\%$.
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ ответ =
+
+ Проверить
+ Заново
+
+
+
`;
+
html += secNav('p5', 'p7');
html += readButton('p6');
+
box.innerHTML = html;
renderMath(box);
+
+ /* IV1 — Графики изопроцессов */
+ (function(){
+ const R = 8.314;
+ const NU = 1; // 1 моль для расчётов
+ const pvSvg = document.getElementById('p6-iv1-pv');
+ const vtSvg = document.getElementById('p6-iv1-vt');
+ const ptSvg = document.getElementById('p6-iv1-pt');
+ let paramInpRef = document.getElementById('p6-iv1-param');
+ let paramLabRef = document.getElementById('p6-iv1-pL');
+ const paramLbl = document.getElementById('p6-iv1-paramLbl');
+ const info = document.getElementById('p6-iv1-info');
+ const COL = { iso:'#ea580c', bar:'#2563eb', hor:'#10b981' };
+ const seen = new Set();
+ let _xpDone = false;
+
+ function getMode(){ const r = document.querySelector('input[name="p6-iv1-proc"]:checked'); return r ? r.value : 'iso'; }
+
+ function paramConfig(mode){
+ if(mode === 'iso') return { label:'Температура $T$', unit:'К', min:200, max:600, step:10, val:300, set:[250, 400, 550] };
+ if(mode === 'bar') return { label:'Давление $p$', unit:'атм', min:0.5, max:3, step:0.1, val:1, set:[0.7, 1.5, 2.5] };
+ return { label:'Объём $V$', unit:'л', min:5, max:20, step:0.5, val:10, set:[6, 12, 18] };
+ }
+
+ function applyParam(mode){
+ const cfg = paramConfig(mode);
+ // Перестраиваем label целиком, чтобы корректно работал и до, и после KaTeX-рендера
+ paramLbl.innerHTML = cfg.label + ': '+cfg.val+' '+cfg.unit+' ';
+ renderMath(paramLbl);
+ // Обновляем ссылки на пересозданные элементы
+ paramInpRef = document.getElementById('p6-iv1-param');
+ paramLabRef = document.getElementById('p6-iv1-pL');
+ paramInpRef.addEventListener('input', render);
+ }
+
+
+ // Диаграмма p-V: x ∈ [1, 25] л, y ∈ [0, 4] атм
+ function plotPV(active, others, activeVal){
+ const W=380, H=260, pad=34;
+ const a = axes2D(W, H, pad, 0, 25, 0, 4);
+ let g = a.content;
+ g += 'V, л ';
+ g += 'p, атм ';
+
+ function drawIsotherm(T, color, opacity){
+ // p (атм) = ν R T / V(м³) / 10^5; для 1 моль: p = 8.314*T / (V_л * 10^-3 * 10^5) = 8.314*T/(V_л*100)
+ const f = V => (NU * R * T) / (V * 1e-3) / 1e5;
+ return ''+plotFunc(f, 0.5, 25, a.toX, a.toY, color, 220)+' ';
+ }
+ function drawIsobar(p, color, opacity){
+ // горизонтальная прямая p = const
+ return ' ';
+ }
+ function drawIsohor(V, color, opacity){
+ // вертикальная прямая V = const
+ return ' ';
+ }
+ others.forEach(v => {
+ if(active === 'iso') g += drawIsotherm(v, COL.iso, 0.25);
+ else if(active === 'bar') g += drawIsobar(v, COL.bar, 0.25);
+ else g += drawIsohor(v, COL.hor, 0.25);
+ });
+ if(active === 'iso') g += drawIsotherm(activeVal, COL.iso, 1);
+ else if(active === 'bar') g += drawIsobar(activeVal, COL.bar, 1);
+ else g += drawIsohor(activeVal, COL.hor, 1);
+ pvSvg.innerHTML = g;
+ }
+
+ // Диаграмма V-T: x ∈ [0, 600] К, y ∈ [0, 25] л
+ function plotVT(active, others, activeVal){
+ const W=380, H=260, pad=34;
+ const a = axes2D(W, H, pad, 0, 600, 0, 25);
+ let g = a.content;
+ g += 'T, К ';
+ g += 'V, л ';
+
+ function drawIsotherm(T, color, opacity){
+ // изотерма на V-T — вертикальная прямая T = const
+ return ' ';
+ }
+ function drawIsobar(p, color, opacity){
+ // V = (ν R / p) T, p в Па; V в л = ν R T / p_Па * 1000
+ const pPa = p * 1e5;
+ const k = NU * R / pPa * 1000; // V_л = k * T
+ return ''+plotFunc(T => k * T, 0, 600, a.toX, a.toY, color, 50)+' ';
+ }
+ function drawIsohor(V, color, opacity){
+ // изохора на V-T — горизонтальная прямая V = const
+ return ' ';
+ }
+ others.forEach(v => {
+ if(active === 'iso') g += drawIsotherm(v, COL.iso, 0.25);
+ else if(active === 'bar') g += drawIsobar(v, COL.bar, 0.25);
+ else g += drawIsohor(v, COL.hor, 0.25);
+ });
+ if(active === 'iso') g += drawIsotherm(activeVal, COL.iso, 1);
+ else if(active === 'bar') g += drawIsobar(activeVal, COL.bar, 1);
+ else g += drawIsohor(activeVal, COL.hor, 1);
+ vtSvg.innerHTML = g;
+ }
+
+ // Диаграмма p-T: x ∈ [0, 600] К, y ∈ [0, 4] атм
+ function plotPT(active, others, activeVal){
+ const W=380, H=260, pad=34;
+ const a = axes2D(W, H, pad, 0, 600, 0, 4);
+ let g = a.content;
+ g += 'T, К ';
+ g += 'p, атм ';
+
+ function drawIsotherm(T, color, opacity){
+ // на p-T — вертикальная прямая T = const
+ return ' ';
+ }
+ function drawIsobar(p, color, opacity){
+ // на p-T — горизонтальная прямая p = const
+ return ' ';
+ }
+ function drawIsohor(V, color, opacity){
+ // p = (ν R / V) T, V в м³; p в атм = ν R T / V_м³ / 10^5
+ const Vm3 = V * 1e-3;
+ const k = NU * R / Vm3 / 1e5; // p_атм = k * T
+ return ''+plotFunc(T => k * T, 0, 600, a.toX, a.toY, color, 50)+' ';
+ }
+ others.forEach(v => {
+ if(active === 'iso') g += drawIsotherm(v, COL.iso, 0.25);
+ else if(active === 'bar') g += drawIsobar(v, COL.bar, 0.25);
+ else g += drawIsohor(v, COL.hor, 0.25);
+ });
+ if(active === 'iso') g += drawIsotherm(activeVal, COL.iso, 1);
+ else if(active === 'bar') g += drawIsobar(activeVal, COL.bar, 1);
+ else g += drawIsohor(activeVal, COL.hor, 1);
+ ptSvg.innerHTML = g;
+ }
+
+ function render(){
+ const mode = getMode();
+ const cfg = paramConfig(mode);
+ const v = +paramInpRef.value;
+ paramLabRef.textContent = (cfg.step < 1) ? v.toFixed(1) : v.toString();
+
+ plotPV(mode, cfg.set, v);
+ plotVT(mode, cfg.set, v);
+ plotPT(mode, cfg.set, v);
+
+ let infoHtml = '';
+ if(mode === 'iso'){
+ infoHtml = 'Изотерма ($T = '+v+'$ К): $pV = \\nu R T = '+(NU*R*v).toFixed(1)+'$ Дж. На $p$–$V$ — гипербола.
';
+ } else if(mode === 'bar'){
+ infoHtml = 'Изобара ($p = '+v.toFixed(1)+'$ атм): $V/T = \\nu R / p = $ const. На $V$–$T$ — прямая через 0.
';
+ } else {
+ infoHtml = 'Изохора ($V = '+v.toFixed(1)+'$ л): $p/T = \\nu R / V = $ const. На $p$–$T$ — прямая через 0.
';
+ }
+ info.innerHTML = infoHtml;
+ renderMath(info);
+
+ seen.add(mode + ':' + Math.round(v*10));
+ if(!_xpDone && seen.size >= 5){ _xpDone = true; addXp(10, 'p6-iv1'); bumpProgress('p6', 15); }
+ }
+
+ document.querySelectorAll('input[name="p6-iv1-proc"]').forEach(r => r.addEventListener('change', () => { applyParam(getMode()); render(); }));
+ applyParam('iso');
+ render();
+ })();
+
+ /* IV2 — Калькулятор изопроцессов */
+ (function(){
+ const tabs = document.getElementById('p6-iv2-tabs');
+ const inpsBox = document.getElementById('p6-iv2-inputs');
+ const out = document.getElementById('p6-iv2-out');
+ const fb = document.getElementById('p6-iv2-fb');
+ const go = document.getElementById('p6-iv2-go');
+ const used = new Set();
+ let _done = false;
+ let mode = 'iso';
+
+ function fieldHTML(id, label, val){
+ return ''+label+' ';
+ }
+ function build(){
+ let h = '';
+ if(mode === 'iso'){
+ h += fieldHTML('p6-iv2-p1', '$p_1$, атм', '1');
+ h += fieldHTML('p6-iv2-V1', '$V_1$, л', '10');
+ h += fieldHTML('p6-iv2-p2', '$p_2$, атм', '2');
+ } else if(mode === 'bar'){
+ h += fieldHTML('p6-iv2-V1', '$V_1$, л', '5');
+ h += fieldHTML('p6-iv2-T1', '$T_1$, К', '250');
+ h += fieldHTML('p6-iv2-T2', '$T_2$, К', '400');
+ } else {
+ h += fieldHTML('p6-iv2-p1', '$p_1$, атм', '2');
+ h += fieldHTML('p6-iv2-T1', '$T_1$, К', '290');
+ h += fieldHTML('p6-iv2-T2', '$T_2$, К', '348');
+ }
+ inpsBox.innerHTML = h;
+ renderMath(inpsBox);
+ out.innerHTML = '';
+ fb.style.display = 'none';
+ }
+ function num(id){ const el = document.getElementById(id); return el ? parseFloat((el.value||'').replace(',','.')) : NaN; }
+ function calc(){
+ let res = '', val = 0, unit = '';
+ if(mode === 'iso'){
+ const p1 = num('p6-iv2-p1'), V1 = num('p6-iv2-V1'), p2 = num('p6-iv2-p2');
+ if(![p1,V1,p2].every(x => isFinite(x) && x>0)){ feedback(fb,false,'✗ Все значения должны быть положительными.'); return; }
+ val = p1 * V1 / p2; unit = 'л';
+ res = '$p_1 V_1 = p_2 V_2 \\Rightarrow V_2 = \\dfrac{p_1 V_1}{p_2} = \\dfrac{'+p1+' \\cdot '+V1+'}{'+p2+'} \\approx '+val.toFixed(3)+'$ л';
+ } else if(mode === 'bar'){
+ const V1 = num('p6-iv2-V1'), T1 = num('p6-iv2-T1'), T2 = num('p6-iv2-T2');
+ if(![V1,T1,T2].every(x => isFinite(x) && x>0)){ feedback(fb,false,'✗ Все значения должны быть положительными.'); return; }
+ val = V1 * T2 / T1; unit = 'л';
+ res = '$\\dfrac{V_1}{T_1} = \\dfrac{V_2}{T_2} \\Rightarrow V_2 = \\dfrac{V_1 T_2}{T_1} = \\dfrac{'+V1+' \\cdot '+T2+'}{'+T1+'} \\approx '+val.toFixed(3)+'$ л';
+ } else {
+ const p1 = num('p6-iv2-p1'), T1 = num('p6-iv2-T1'), T2 = num('p6-iv2-T2');
+ if(![p1,T1,T2].every(x => isFinite(x) && x>0)){ feedback(fb,false,'✗ Все значения должны быть положительными.'); return; }
+ val = p1 * T2 / T1; unit = 'атм';
+ res = '$\\dfrac{p_1}{T_1} = \\dfrac{p_2}{T_2} \\Rightarrow p_2 = \\dfrac{p_1 T_2}{T_1} = \\dfrac{'+p1+' \\cdot '+T2+'}{'+T1+'} \\approx '+val.toFixed(3)+'$ атм';
+ }
+ const lawName = mode==='iso' ? 'Бойля–Мариотта' : (mode==='bar' ? 'Гей-Люссака' : 'Шарля');
+ out.innerHTML = 'Закон '+lawName+':
'
+ + ''+res+'
'
+ + 'Ответ: '+(+val.toFixed(3))+' '+unit+'
';
+ renderMath(out);
+ feedback(fb, true, '✓ Вычислено.');
+ used.add(mode);
+ if(!_done && used.size === 3){ _done = true; addXp(10, 'p6-iv2'); bumpProgress('p6', 15); }
+ }
+ tabs.querySelectorAll('button').forEach(b => {
+ b.addEventListener('click', () => {
+ mode = b.dataset.mode;
+ tabs.querySelectorAll('button').forEach(x => { x.className = 'btn'; x.style.background=''; x.style.borderColor=''; });
+ b.className = 'btn primary';
+ const colMap = { iso:'#ea580c', bar:'#2563eb', hor:'#10b981' };
+ b.style.background = colMap[mode]; b.style.borderColor = colMap[mode];
+ build();
+ });
+ });
+ go.addEventListener('click', calc);
+ build();
+ })();
+
+ /* IV3 — DnD: какой процесс */
+ (function(){
+ const items = [
+ { id:'s1', cat:'iso', html:'Газ медленно сжимают в баллоне при контакте с термостатом' },
+ { id:'s2', cat:'bar', html:'Газ нагревают в открытом цилиндре с подвижным поршнем' },
+ { id:'s3', cat:'hor', html:'Газ в герметичном баллоне нагревают электронагревателем' },
+ { id:'s4', cat:'hor', html:'Шину автомобиля надули зимой — летом давление в ней растёт' },
+ { id:'s5', cat:'iso', html:'Воздушный шарик медленно сжимают рукой' },
+ { id:'s6', cat:'bar', html:'В цилиндре с поршнем над газом — атмосфера; газ нагревают, поршень выдвигается' },
+ ];
+ const sorter = setupSorter({
+ poolId:'p6-iv3-pool',
+ scopeSelector:'#p6-iv3',
+ items: items,
+ cats:['iso','bar','hor'],
+ columnLayout:false,
+ });
+ document.getElementById('p6-iv3-check').addEventListener('click', () => {
+ const fb = document.getElementById('p6-iv3-fb');
+ const placedCount = items.filter(it => sorter.placed[it.id]).length;
+ const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
+ if(placedCount < items.length){ feedback(fb, false, '✗ Размести все 6 ситуаций.'); return; }
+ if(correct === items.length){ feedback(fb, true, '✓ Все 6 верно! +10 XP'); addXp(10,'p6-iv3'); bumpProgress('p6', 15); }
+ else feedback(fb, false, '✗ Правильно ' + correct + ' из 6. Попробуй ещё.');
+ });
+ document.getElementById('p6-iv3-reset').addEventListener('click', () => { sorter.reset(); document.getElementById('p6-iv3-fb').style.display = 'none'; });
+ })();
+
+ /* IV4 — Тренажёр изопроцессов */
+ (function(){
+ const Q = [
+ { q:'Изотермически газ сжали от $V_1 = 10$ л до $V_2 = 4$ л. Во сколько раз изменилось давление?', ans:2.5, hint:'$p_2/p_1 = V_1/V_2 = 10/4 = 2{,}5$' },
+ { q:'Изобарно газ нагрели от $T_1 = 200$ К до $T_2 = 500$ К. Во сколько раз изменился объём?', ans:2.5, hint:'$V_2/V_1 = T_2/T_1 = 500/200 = 2{,}5$' },
+ { q:'Изохорно газ нагрели от $T_1 = 300$ К до $T_2 = 600$ К. Во сколько раз изменилось $p$?', ans:2, hint:'$p_2/p_1 = T_2/T_1 = 600/300 = 2$' },
+ { q:'В шине $p_1 = 2$ атм при $T_1 = 290$ К. Летом $T_2 = 348$ К. Найти $p_2$ в атм.', ans:2.4, hint:'$p_2 = p_1 T_2/T_1 = 2 \\cdot 348/290 \\approx 2{,}4$' },
+ { q:'В цилиндре с поршнем $V_1 = 5$ л при $T_1 = 250$ К. Изобарный нагрев до $T_2 = 400$ К. $V_2 = ?$ л', ans:8, hint:'$V_2 = V_1 T_2/T_1 = 5 \\cdot 400/250 = 8$' },
+ { q:'$p_1 = 4$ атм. Изотермически давление уменьшили до $p_2 = 1$ атм. Во сколько раз увеличился $V$?', ans:4, hint:'$V_2/V_1 = p_1/p_2 = 4/1 = 4$' },
+ ];
+ let i = 0, score = 0;
+ function show(){
+ if(i >= Q.length){
+ document.getElementById('p6-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ if(score === Q.length){ addXp(15, 'p6-iv4'); bumpProgress('p6', 25); }
+ else if(score >= 4){ addXp(8, 'p6-iv4'); bumpProgress('p6', 15); }
+ return;
+ }
+ document.getElementById('p6-iv4-i').textContent = (i+1);
+ document.getElementById('p6-iv4-s').textContent = score;
+ document.getElementById('p6-iv4-q').innerHTML = Q[i].q;
+ document.getElementById('p6-iv4-ans').value = '';
+ renderMath(document.getElementById('p6-iv4-q'));
+ document.getElementById('p6-iv4-fb').style.display = 'none';
+ }
+ function go(){
+ if(i >= Q.length) return;
+ const fb = document.getElementById('p6-iv4-fb');
+ const raw = document.getElementById('p6-iv4-ans').value.replace(',', '.');
+ const ans = parseFloat(raw);
+ if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; }
+ const 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, '✓ Верно! '+Q[i].hint+'. Дальше ▶'); }
+ else feedback(fb, false, '✗ Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
+ document.getElementById('p6-iv4-s').textContent = score;
+ i++;
+ setTimeout(show, 1800);
+ }
+ document.getElementById('p6-iv4-go').addEventListener('click', go);
+ document.getElementById('p6-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
+ document.getElementById('p6-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
+ show();
+ })();
+
wireReadBtn('p6');
}