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$$

+

где:

+ +

Через массу газа ($\\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", ` +

Нормальные условия (н.у.):

+ +

При нормальных условиях молярный объём любого идеального газа одинаков:

+

$$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 += `
+
ИНТЕРАКТИВ 1
Визуализатор уравнения состояния
+
Меняй $\\nu$, $T$, $V$ — получай давление по формуле $p = \\nu R T / V$. Цвет «контейнера» — по $T$, число точек — по $\\nu$.
+
+ + + +
+
+ +
+
+
$pV = \\nu R T = $ Дж
+
Давление: $p = \\dfrac{\\nu R T}{V} \\approx $ Па $\\approx$ атм
+
+
`; + + /* INTERACTIVE 2 — Калькулятор Менделеева–Клапейрона */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор Менделеева–Клапейрона
+
Выбери искомую величину — введи остальные параметры. Используется $pV = (m/M) R T$.
+
+ + + + +
+
+
+ +
+
+
+
`; + + /* INTERACTIVE 3 — DnD: Какое отношение работает? */ + html += `
+
ИНТЕРАКТИВ 3
Какое отношение работает?
+
Перетащи 6 формул в 3 ящика — какой раздел теории газа описывает каждая.
+
6 формул — 3 категории
+
+
+
Уравнение состояния
+
Объединённый газовый закон
+
Частный случай / константа
+
+
+
+
`; + + /* INTERACTIVE 4 — Тренажёр уравнения состояния */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр уравнения состояния
+
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 ''; + } + 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
+

Главные характеристики:

+ + `); + + /* 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 += `
+
ИНТЕРАКТИВ 1
Графики изопроцессов: $p$–$V$, $V$–$T$, $p$–$T$
+
Переключай тип процесса и параметр — смотри, как меняются 3 диаграммы. Текущая кривая ярче, две альтернативные — бледнее.
+
+ + + +
+
+ +
+
+
$p$–$V$ диаграмма
+
$V$–$T$ диаграмма
+
$p$–$T$ диаграмма
+
+
+
`; + + /* INTERACTIVE 2 — Калькулятор изопроцессов */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор изопроцессов
+
Выбери тип процесса, введи $p_1, V_1, T_1$ и одну величину состояния 2 — получи недостающие.
+
+ + + +
+
+
+ +
+
+ +
`; + + /* INTERACTIVE 3 — DnD: какой процесс */ + html += `
+
ИНТЕРАКТИВ 3
Какой процесс?
+
Перетащи 6 ситуаций в нужный процесс.
+
6 ситуаций — 3 процесса
+
+
+
Изотермический
+
Изобарный
+
Изохорный
+
+
+ +
`; + + /* INTERACTIVE 4 — Тренажёр изопроцессов */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр изопроцессов
+
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 ''; + } + 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'); }