From 5e9bafb20cb7f5de30e06c18ac56434dbf2a274b Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 16:51:43 +0300 Subject: [PATCH] =?UTF-8?q?feat(phys10=20ch1=20wave5=20+=20final):=20?= =?UTF-8?q?=C2=A79=20=C2=AB=D0=98=D1=81=D0=BF=D0=B0=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=C2=BB=20+=20=C2=A710=20=C2=AB=D0=92=D0=BB=D0=B0?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C=C2=BB=20+=20=D0=A4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20=D0=93=D0=BB=D0=B0=D0=B2=D1=8B=201=20(7=20?= =?UTF-8?q?=D0=B1=D0=BE=D1=81=D1=81=D0=BE=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/textbooks/physics_10_ch1.html | 996 ++++++++++++++++++++++++- 1 file changed, 975 insertions(+), 21 deletions(-) diff --git a/frontend/textbooks/physics_10_ch1.html b/frontend/textbooks/physics_10_ch1.html index cb01da6..3f8e8ae 100644 --- a/frontend/textbooks/physics_10_ch1.html +++ b/frontend/textbooks/physics_10_ch1.html @@ -117,6 +117,11 @@ a{color:inherit;text-decoration:none} .feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)} .feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)} +.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s;position:relative;overflow:hidden} +.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important} +@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}} +.boss-card.pulse{animation:bossPulse .8s ease-out} + .wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1} .wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px} .wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em} @@ -3584,55 +3589,1004 @@ function build_p8(){ wireReadBtn('p8'); } +/* ========== §9 ИСПАРЕНИЕ И КОНДЕНСАЦИЯ. НАСЫЩЕННЫЙ ПАР ========== */ + +/* Табличные значения давления насыщенного пара воды (кПа) при t в °C — используется в §9 и §10. */ +const P9_PNTABLE = [ + { t: 0, p: 0.61 }, + { t: 10, p: 1.23 }, + { t: 20, p: 2.34 }, + { t: 30, p: 4.24 }, + { t: 40, p: 7.38 }, + { t: 50, p: 12.3 }, + { t: 60, p: 19.9 }, + { t: 70, p: 31.2 }, + { t: 80, p: 47.4 }, + { t: 90, p: 70.1 }, + { t: 100, p: 101.3 }, +]; +function pSatWater(t){ + if(t <= P9_PNTABLE[0].t) return P9_PNTABLE[0].p; + if(t >= P9_PNTABLE[P9_PNTABLE.length-1].t) return P9_PNTABLE[P9_PNTABLE.length-1].p; + for(let i = 0; i < P9_PNTABLE.length - 1; i++){ + const a = P9_PNTABLE[i], b = P9_PNTABLE[i+1]; + if(t >= a.t && t <= b.t){ + const k = (t - a.t) / (b.t - a.t); + return a.p + k * (b.p - a.p); + } + } + return 0; +} +function dewPointWater(p){ + // обратная интерполяция: при каком t выполняется p_н(t) = p? + if(p <= P9_PNTABLE[0].p) return P9_PNTABLE[0].t; + if(p >= P9_PNTABLE[P9_PNTABLE.length-1].p) return P9_PNTABLE[P9_PNTABLE.length-1].t; + for(let i = 0; i < P9_PNTABLE.length - 1; i++){ + const a = P9_PNTABLE[i], b = P9_PNTABLE[i+1]; + if(p >= a.p && p <= b.p){ + const k = (p - a.p) / (b.p - a.p); + return a.t + k * (b.t - a.t); + } + } + return 0; +} + function build_p9(){ const box = document.getElementById('p9-body'); let html = ''; - html += makeCard('theory', "Испарение и конденсация. Насыщенный пар", "§9", ` -

Испарение и конденсация. Насыщенный пар — этот параграф в разработке (Phase 1+).

-

Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.

-

- Phase 0: создан скелет учебника. Phase 1+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019). -

+ + /* THEORY 1 — Испарение и конденсация */ + html += makeCard('theory', "Испарение и конденсация", "§9", ` +

Испарение — переход жидкости в пар с её поверхности. Происходит при любой температуре (даже при $0°$C — мокрое бельё высыхает зимой).

+

Молекулы с наибольшей кинетической энергией вылетают с поверхности, преодолевая силы притяжения остальных. При этом средняя энергия оставшихся молекул уменьшается — жидкость охлаждается при испарении (поэтому мокрое полотенце холодит кожу).

+

Скорость испарения растёт при:

+ +

Конденсация — обратный процесс: пар → жидкость. При конденсации выделяется то же количество энергии, которое было затрачено на испарение.

`); + + /* THEORY 2 — Насыщенный пар */ + html += makeCard('rule', "Насыщенный пар", "§9", ` +

Насыщенный пар — пар, находящийся в динамическом равновесии с жидкостью: число молекул, испаряющихся в единицу времени, равно числу молекул, конденсирующихся обратно.

+

Главное свойство: давление насыщенного пара $p_н$ зависит только от температуры и не зависит от объёма!

+ +

Кипение наступает при такой температуре $T_{\\text{кип}}$, когда $p_н(T_{\\text{кип}}) = p_{\\text{атм}}$. Для воды при 1 атм ($\\approx 101{,}3$ кПа) это $T_{\\text{кип}} = 100°$C.

+

Значения $p_н$ для воды (кПа): при $20°$C — $2{,}34$; при $50°$C — $12{,}3$; при $80°$C — $47{,}4$; при $100°$C — $101{,}3$.

+ `); + + /* THEORY 3 — Виды пара */ + html += makeCard('example', "Три вида пара", "§9", ` +

Насыщенный пар. Пар в равновесии с жидкостью. $p = p_н(T)$. Подчиняется законам идеального газа только приближённо.

+

Ненасыщенный пар. $p < p_н$. «Обычный» пар, без жидкости поблизости. Хорошо описывается законами идеального газа и уравнением Менделеева–Клапейрона.

+

Перенасыщенный пар. $p > p_н$. Нестабильное состояние — пар спонтанно конденсируется. Возникает при резком охлаждении чистого пара (без центров конденсации).

+

Примеры в природе и технике:

+ + `); + + /* INTERACTIVE 1 — Симуляция испарения и конденсации */ + html += `
+
ИНТЕРАКТИВ 1
Симуляция: испарение и конденсация
+
Нижняя половина — жидкость (синие молекулы), верхняя — пар (голубые). Двигай ползунок $T$ — при росте температуры всё больше молекул переходит в пар.
+
+ +
+
+ +
+
+ + +
+
+
`; + + /* INTERACTIVE 2 — Калькулятор p_н(T) */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор давления насыщенного пара воды
+
Введи температуру $t$ от $0$ до $100°$C — получишь $p_н(t)$ по табличным данным (интерполяция между опорными точками).
+
+ + +
+
+ +
`; + + /* INTERACTIVE 3 — Какой пар? (квикфайр) */ + html += `
+
ИНТЕРАКТИВ 3
Какой это пар?
+
6 ситуаций. Определи: насыщенный, ненасыщенный или перенасыщенный.
+
Задача 1 / 6Очки: 0 / 6
+
+
+ +
+
`; + + /* INTERACTIVE 4 — Тренажёр пара */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр пара
+
5 задач. Числовые ответы, допуск ±5%.
+
Задача 1 / 5Очки: 0 / 5
+
+
+ ответ = + + + +
+ +
`; + html += secNav('p8', 'p10'); html += readButton('p9'); + box.innerHTML = html; renderMath(box); + + /* IV1 — Симуляция испарения */ + (function(){ + const svg = document.getElementById('p9-iv1-svg'); + const tInp = document.getElementById('p9-iv1-t'); + const tLab = document.getElementById('p9-iv1-tL'); + const btnPause = document.getElementById('p9-iv1-pause'); + const btnReset = document.getElementById('p9-iv1-reset'); + const info = document.getElementById('p9-iv1-info'); + const W = 380, H = 260; + const SURFACE_Y = 130; + const N_LIQUID = 80; // максимум молекул в жидкости + let parts = []; // {x,y,vx,vy,phase: 'liquid'|'vapor'} + let raf = null, lastT = 0, paused = false; + const tempChanges = new Set(); + let _xpDone = false; + + function init(){ + parts = []; + const cols = 10, rows = 8, sx = 30, sy = SURFACE_Y + 10, dx = 32, dy = 14; + for(let j = 0; j < rows; j++){ + for(let i = 0; i < cols; i++){ + parts.push({ + x0: sx + i*dx + (j%2)*dx/2, + y0: sy + j*dy, + x: sx + i*dx + (j%2)*dx/2, + y: sy + j*dy, + vx: 0, vy: 0, + phase: 'liquid' + }); + } + } + } + + 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; + + const T = +tInp.value; + const Tnorm = Math.max(0, (T - 273) / 127); // 0..1 + const amp = 1.5 + Tnorm * 5; + // вероятность испарения с поверхности (верхний ряд) + const escapeProb = Math.pow(Tnorm, 1.4) * 0.018; + // вероятность конденсации обратно (если касается поверхности) + const condenseProb = 0.5; + + for(const p of parts){ + if(p.phase === 'liquid'){ + p.vx += (Math.random() - 0.5) * amp * 30 * dt; + p.vy += (Math.random() - 0.5) * amp * 30 * dt; + p.vx += (p.x0 - p.x) * 9 * dt; + p.vy += (p.y0 - p.y) * 9 * dt; + p.vx *= 0.9; p.vy *= 0.9; + p.x += p.vx * dt; p.y += p.vy * dt; + // шанс испариться (только если близко к поверхности) + if(p.y < SURFACE_Y + 25 && Math.random() < escapeProb){ + p.phase = 'vapor'; + p.vx = (Math.random() - 0.5) * 80; + p.vy = -80 - Math.random() * 60; + } + } else { + // парная фаза — свободное движение + p.x += p.vx * dt; + p.y += p.vy * dt; + // отражение от стен + if(p.x < 8){ p.x = 8; p.vx = -p.vx; } + if(p.x > W-8){ p.x = W-8; p.vx = -p.vx; } + if(p.y < 8){ p.y = 8; p.vy = -p.vy; } + // касание поверхности — шанс сконденсироваться + if(p.y > SURFACE_Y - 2){ + if(Math.random() < condenseProb){ + p.phase = 'liquid'; + p.vx = 0; p.vy = 0; + p.x = p.x0; p.y = p.y0; + } else { + p.y = SURFACE_Y - 2; p.vy = -Math.abs(p.vy); + } + } + } + } + render(); + } + + function render(){ + const T = +tInp.value; + let g = ''; + g += ''; + // зона пара + g += ''; + // зона жидкости + g += ''; + // поверхность + g += ''; + g += 'пар'; + g += 'жидкость'; + let nLiq = 0, nVap = 0; + for(const p of parts){ + if(p.phase === 'liquid'){ + g += ''; + nLiq++; + } else { + g += ''; + nVap++; + } + } + // счётчики + g += 'пар: '+nVap+''; + g += 'жидкость: '+nLiq+''; + g += 'T = '+T+' К'; + svg.innerHTML = g; + } + + function updateInfo(T){ + let txt; + if(T < 290){ + txt = '$T \\approx '+T+'$ К. Низкая температура: испарение медленное, в паре мало молекул, равновесие достигается быстро.'; + } else if(T < 330){ + txt = '$T \\approx '+T+'$ К (комнатная). Появляется ощутимый поток молекул в пар. $p_н$ невелико, но не нулевое.'; + } else if(T < 370){ + txt = '$T \\approx '+T+'$ К. Сильное испарение, давление насыщенного пара $p_н$ растёт почти экспоненциально.'; + } else { + txt = '$T \\approx '+T+'$ К (около кипения). Пар почти заполняет весь объём, $p_н \\to p_{\\text{атм}}$.'; + } + info.innerHTML = txt; + renderMath(info); + } + + init(); + raf = requestAnimationFrame(frame); + updateInfo(+tInp.value); + + tInp.addEventListener('input', () => { + const T = +tInp.value; + tLab.textContent = T; + updateInfo(T); + tempChanges.add(Math.round(T/20)); + if(!_xpDone && tempChanges.size >= 4){ + _xpDone = true; + addXp(10, 'p9-iv1'); + bumpProgress('p9', 15); + } + }); + btnPause.addEventListener('click', () => { + paused = !paused; + btnPause.textContent = paused ? 'Продолжить' : 'Пауза'; + }); + btnReset.addEventListener('click', () => { init(); }); + document.addEventListener('visibilitychange', () => { + if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; } + else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); } + }); + })(); + + /* IV2 — Калькулятор p_н(T) */ + (function(){ + const tInp = document.getElementById('p9-iv2-t'); + const out = document.getElementById('p9-iv2-out'); + const fb = document.getElementById('p9-iv2-fb'); + const go = document.getElementById('p9-iv2-go'); + const used = new Set(); + let _done = false; + function calc(){ + const t = parseFloat((tInp.value||'').replace(',','.')); + if(!isFinite(t) || t < 0 || t > 100){ + feedback(fb, false, '✗ Введи $t$ от 0 до 100 °C.'); + return; + } + const pn = pSatWater(t); + const T = t + 273; + let comment; + if(pn >= 100){ + comment = 'Кипение! $p_н \\approx p_{\\text{атм}}$ — вода кипит при 1 атм.'; + } else if(pn >= 30){ + comment = 'При этой $t$ вода в открытом сосуде испаряется очень быстро.'; + } else if(pn >= 5){ + comment = 'Умеренное испарение; типично для тёплой комнаты или подогретой воды.'; + } else { + comment = 'Испарение медленное; типично для прохладного воздуха.'; + } + out.innerHTML = + '
$T$: $t = '+t+'$ °C $= '+T.toFixed(0)+'$ К
' + + '
Давление насыщенного пара: $p_н(t) \\approx '+(+pn.toFixed(2))+'$ кПа
' + + '
'+comment+'
'; + renderMath(out); + feedback(fb, true, '✓ Вычислено.'); + used.add(Math.round(t/15)); + if(!_done && used.size >= 3){ _done = true; addXp(10, 'p9-iv2'); bumpProgress('p9', 15); } + } + go.addEventListener('click', calc); + tInp.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }); + })(); + + /* IV3 — Какой пар? */ + (function(){ + // 0 = насыщенный, 1 = ненасыщенный, 2 = перенасыщенный + const Q = [ + { q:'Пар в закрытом сосуде над водой в равновесии при $T = 300$ К', ans:0, why:'Динамическое равновесие — это и есть насыщенный пар.' }, + { q:'Воздух комнаты при $\\varphi = 30\\%$, далеко от точки росы', ans:1, why:'$p < p_н$ — ненасыщенный пар.' }, + { q:'Чистый пар охладили в камере Вильсона ниже точки росы (без центров конденсации)', ans:2, why:'Перенасыщенный пар — нестабильное состояние, $p > p_н$.' }, + { q:'Пар над кипящим чайником, который только что закрыли крышкой', ans:0, why:'В замкнутом объёме над водой быстро устанавливается насыщенный пар.' }, + { q:'Утренний туман: пар охладился, образовались капельки', ans:0, why:'В тумане устанавливается насыщенный пар над поверхностью капель.' }, + { q:'Воздух в комнате с $\\varphi = 95\\%$ при $t = 20°$C', ans:1, why:'Пока $\\varphi < 100\\%$, пар остаётся ненасыщенным (хоть и близко к насыщению).' }, + ]; + const LABELS = ['Насыщенный', 'Ненасыщенный', 'Перенасыщенный']; + const COLORS = ['#10b981', '#0ea5e9', '#f59e0b']; + let i = 0, score = 0; + const qEl = document.getElementById('p9-iv3-q'); + const oEl = document.getElementById('p9-iv3-opts'); + const fb = document.getElementById('p9-iv3-fb'); + const iEl = document.getElementById('p9-iv3-i'); + const sEl = document.getElementById('p9-iv3-s'); + function show(){ + if(i >= Q.length){ + qEl.innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + oEl.innerHTML = ''; + if(score === Q.length){ addXp(15, 'p9-iv3'); bumpProgress('p9', 25); } + else if(score >= 4){ addXp(8, 'p9-iv3'); bumpProgress('p9', 15); } + return; + } + iEl.textContent = (i+1); sEl.textContent = score; + qEl.innerHTML = Q[i].q; + oEl.innerHTML = LABELS.map((l, k) => + '' + ).join(''); + fb.style.display = 'none'; + renderMath(qEl); + oEl.querySelectorAll('button').forEach(b => { + b.addEventListener('click', () => { + const v = +b.dataset.v; + if(v === Q[i].ans){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+' Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Это '+LABELS[Q[i].ans]+'. '+Q[i].why+' Дальше ▶'); + sEl.textContent = score; + oEl.querySelectorAll('button').forEach(x => x.disabled = true); + i++; + setTimeout(show, 1900); + }); + }); + } + document.getElementById('p9-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); }); + show(); + })(); + + /* IV4 — Тренажёр пара */ + (function(){ + const Q = [ + { q:'При $t = 100°$C давление насыщенного пара воды равно ... кПа?', ans:101.3, tol:1, hint:'$p_н(100°) = 101{,}3$ кПа — это и есть атмосферное.' }, + { q:'При какой температуре (в °C) начинается кипение воды при атмосферном давлении 1 атм?', ans:100, tol:1, hint:'Кипение наступает при $p_н = p_{\\text{атм}}$ — для 1 атм это $100°$C.' }, + { q:'В закрытом сосуде с водой нагрели содержимое с $20°$C до $50°$C. Во сколько раз вырастет $p_н$? ($p_н(20°) = 2{,}34$ кПа, $p_н(50°) = 12{,}3$ кПа)', ans:5.26, tol:0.5, hint:'$\\dfrac{12{,}3}{2{,}34} \\approx 5{,}26$.' }, + { q:'Зависит ли давление насыщенного пара от объёма сосуда? Введи 1 — да, 2 — нет.', ans:2, tol:0.1, hint:'$p_н$ зависит только от $T$.' }, + { q:'Что забирает больше энергии на 1 г: испарение воды ($r \\approx 2260$ кДж/кг) или плавление льда ($\\lambda \\approx 334$ кДж/кг)? Введи 1 — испарение, 2 — плавление.', ans:1, tol:0.1, hint:'$r \\gg \\lambda$ — испарение «дороже» в 6–7 раз.' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p9-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15, 'p9-iv4'); bumpProgress('p9', 25); } + else if(score >= 3){ addXp(8, 'p9-iv4'); bumpProgress('p9', 15); } + return; + } + document.getElementById('p9-iv4-i').textContent = (i+1); + document.getElementById('p9-iv4-s').textContent = score; + document.getElementById('p9-iv4-q').innerHTML = Q[i].q; + document.getElementById('p9-iv4-ans').value = ''; + renderMath(document.getElementById('p9-iv4-q')); + document.getElementById('p9-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p9-iv4-fb'); + const raw = document.getElementById('p9-iv4-ans').value.replace(',', '.'); + const ans = parseFloat(raw); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; } + const tol = Q[i].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('p9-iv4-s').textContent = score; + i++; + setTimeout(show, 1900); + } + document.getElementById('p9-iv4-go').addEventListener('click', go); + document.getElementById('p9-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); }); + document.getElementById('p9-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); }); + show(); + })(); + wireReadBtn('p9'); } +/* ========== §10 ВЛАЖНОСТЬ ВОЗДУХА ========== */ + function build_p10(){ const box = document.getElementById('p10-body'); let html = ''; - html += makeCard('theory', "Влажность воздуха", "§10", ` -

Влажность воздуха — этот параграф в разработке (Phase 1+).

-

Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.

-

- Phase 0: создан скелет учебника. Phase 1+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019). -

+ + /* THEORY 1 — Абсолютная и относительная влажность */ + html += makeCard('theory', "Абсолютная и относительная влажность", "§10", ` +

Абсолютная влажность $\\rho_a$ — масса водяного пара в $1$ м³ воздуха. Единица: г/м³.

+

Относительная влажность $\\varphi$ — характеризует, насколько воздух близок к насыщению:

+

$$\\varphi = \\dfrac{p}{p_н(T)} \\cdot 100\\% = \\dfrac{\\rho_a}{\\rho_н(T)} \\cdot 100\\%$$

+

где $p$, $\\rho_a$ — реальные значения, $p_н$, $\\rho_н$ — для насыщенного пара при той же $T$.

+
    +
  • $\\varphi = 100\\%$ — воздух полностью насыщен; начинается конденсация.
  • +
  • $\\varphi < 100\\%$ — пар ненасыщенный, испарение продолжается.
  • +
  • $\\varphi = 0\\%$ — абсолютно сухой воздух.
  • +
`); + + /* THEORY 2 — Точка росы */ + html += makeCard('rule', "Точка росы", "§10", ` +

Точка росы $T_{\\text{росы}}$ — температура, до которой нужно охладить воздух при неизменном давлении, чтобы пар стал насыщенным ($\\varphi = 100\\%$).

+

При охлаждении ниже точки росы начинается конденсация: образуются роса, туман, иней, капли на стекле.

+

Условие точки росы: $p_н(T_{\\text{росы}}) = p$ — то есть при $T = T_{\\text{росы}}$ табличное давление насыщенного пара равно реальному давлению пара $p$ в воздухе.

+

Пример. При $t = 25°$C и $\\varphi = 50\\%$:

+
    +
  • $p_н(25°) \\approx 3{,}17$ кПа, $p = 0{,}5 \\cdot 3{,}17 \\approx 1{,}58$ кПа;
  • +
  • ищем $t$, при котором $p_н(t) = 1{,}58$ → $T_{\\text{росы}} \\approx 14°$C.
  • +
+

Чем выше $\\varphi$, тем ближе точка росы к текущей температуре.

+ `); + + /* THEORY 3 — Психрометр и нормы */ + html += makeCard('example', "Психрометр и гигиенические нормы", "§10", ` +

Психрометр — прибор из двух термометров:

+
    +
  • Сухой показывает температуру воздуха $t$.
  • +
  • Влажный (обёрнут влажной тканью) — показывает $t_{\\text{вл}} < t$ из-за испарения.
  • +
+

По разности $\\Delta t = t - t_{\\text{вл}}$ и психрометрической таблице определяют $\\varphi$. Чем больше разность — тем суше воздух.

+

Гигиенические нормы влажности:

+
    +
  • Комфорт: $\\varphi = 40$–$60\\%$ — оптимально для дыхания и кожи.
  • +
  • Слишком сухо ($\\varphi < 30\\%$): пересыхают слизистые, кожа теряет влагу.
  • +
  • Слишком влажно ($\\varphi > 70\\%$): пот плохо испаряется, ощущение духоты.
  • +
+

Гигрометры применяются в музеях (защита картин), больницах, серверных и оранжереях.

+ `); + + /* INTERACTIVE 1 — Психрометрический визуализатор */ + html += `
+
ИНТЕРАКТИВ 1
Психрометрический визуализатор
+
Двигай $t$ и $p$ — увидишь $\\varphi$ на шкале комфорта и точку росы. Цветовые зоны: красная — сухо, зелёная — комфорт, синяя — влажно.
+
+ + +
+
+ +
+
+
`; + + /* INTERACTIVE 2 — Калькулятор влажности */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор относительной влажности
+
Введи температуру $t$ и реальное давление пара $p$ — получишь $\\varphi$ и точку росы $T_{\\text{росы}}$.
+
+ + +
+
+ +
+
+ +
`; + + /* INTERACTIVE 3 — Какая влажность? */ + html += `
+
ИНТЕРАКТИВ 3
Какая влажность: сухо / комфорт / влажно?
+
6 значений $\\varphi$. Определи зону комфорта по гигиеническим нормам.
+
Задача 1 / 6Очки: 0 / 6
+
+
+ +
+
`; + + /* INTERACTIVE 4 — Тренажёр влажности */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр влажности
+
5 задач. Допуск ±5% (для $\\varphi$ ±2%).
+
Задача 1 / 5Очки: 0 / 5
+
+
+ ответ = + + + +
+ +
`; + html += secNav('p9', 'final1'); html += readButton('p10'); + box.innerHTML = html; renderMath(box); + + /* IV1 — Психрометрический визуализатор */ + (function(){ + const svg = document.getElementById('p10-iv1-svg'); + const tInp = document.getElementById('p10-iv1-t'); + const pInp = document.getElementById('p10-iv1-p'); + const tLab = document.getElementById('p10-iv1-tL'); + const pLab = document.getElementById('p10-iv1-pL'); + const info = document.getElementById('p10-iv1-info'); + const W = 380, H = 280; + const configs = new Set(); + let _done = false; + + function render(){ + const t = +tInp.value; + let p = +pInp.value; + const pn = pSatWater(t); + // p не может превышать p_н(t) физически — обрежем + if(p > pn) p = pn; + pInp.value = p.toFixed(2); + const phi = (p / pn) * 100; + const dew = dewPointWater(p); + + tLab.textContent = t; + pLab.textContent = p.toFixed(2); + + // SVG: горизонтальная шкала комфорта 0..100% + const xL = 30, xR = W - 30, yBar = 80, hBar = 28; + let g = ''; + g += ''; + g += 'Шкала относительной влажности'; + // зоны + const xAt = (v) => xL + (xR - xL) * (v/100); + g += ''; + g += ''; + g += ''; + g += ''; + // подписи зон + g += 'Сухо'; + g += 'Комфорт'; + g += 'Влажно'; + // деления 0/30/70/100 + [0, 30, 70, 100].forEach(v => { + const x = xAt(v); + g += ''; + g += ''+v+'%'; + }); + // индикатор текущей phi + const xPhi = xAt(Math.min(100, Math.max(0, phi))); + g += ''; + g += 'φ = '+phi.toFixed(0)+'%'; + + // нижняя справка + g += ''; + g += 't = '+t+' °C · p = '+p.toFixed(2)+' кПа · pₙ('+t+'°) = '+pn.toFixed(2)+' кПа'; + g += 'Точка росы: T_росы ≈ '+dew.toFixed(1)+' °C'; + // вердикт + let verdict, vcol; + if(phi < 30){ verdict = 'Сухо: пересыхают слизистые.'; vcol = '#dc2626'; } + else if(phi < 70){ verdict = 'Комфорт: оптимальная влажность.'; vcol = '#059669'; } + else if(phi < 100){ verdict = 'Влажно: пот плохо испаряется.'; vcol = '#0369a1'; } + else { verdict = 'Насыщение! Возможна конденсация.'; vcol = '#7c3aed'; } + g += ''+verdict+''; + g += ''; + + svg.innerHTML = g; + info.innerHTML = '$\\varphi = '+phi.toFixed(1)+'\\%$, $p_н('+t+'°) \\approx '+pn.toFixed(2)+'$ кПа, точка росы $T_{\\text{росы}} \\approx '+dew.toFixed(1)+'°$C.'; + renderMath(info); + configs.add(Math.round(phi/20)+'_'+Math.round(t/10)); + if(!_done && configs.size >= 4){ + _done = true; + addXp(10, 'p10-iv1'); + bumpProgress('p10', 15); + } + } + tInp.addEventListener('input', render); + pInp.addEventListener('input', render); + render(); + })(); + + /* IV2 — Калькулятор влажности */ + (function(){ + const tInp = document.getElementById('p10-iv2-t'); + const pInp = document.getElementById('p10-iv2-p'); + const out = document.getElementById('p10-iv2-out'); + const fb = document.getElementById('p10-iv2-fb'); + const go = document.getElementById('p10-iv2-go'); + const used = new Set(); + let _done = false; + function calc(){ + const t = parseFloat((tInp.value||'').replace(',','.')); + const p = parseFloat((pInp.value||'').replace(',','.')); + if(!isFinite(t) || t < 0 || t > 100){ + feedback(fb, false, '✗ Введи $t$ от 0 до 100 °C.'); + return; + } + if(!isFinite(p) || p <= 0){ + feedback(fb, false, '✗ $p$ должно быть положительным.'); + return; + } + const pn = pSatWater(t); + if(p > pn + 0.01){ + feedback(fb, false, '✗ $p > p_н$ — невозможно: $p_н('+t+'°) = '+pn.toFixed(2)+'$ кПа.'); + return; + } + const phi = (p / pn) * 100; + const dew = dewPointWater(p); + out.innerHTML = + '
Дано: $t = '+t+'°$C, $p = '+p+'$ кПа.
' + + '
Из таблицы: $p_н('+t+'°) \\approx '+pn.toFixed(2)+'$ кПа.
' + + '
Относительная влажность: $\\varphi = \\dfrac{p}{p_н} \\cdot 100\\% = \\dfrac{'+p+'}{'+pn.toFixed(2)+'} \\cdot 100\\% \\approx '+phi.toFixed(1)+'\\%$
' + + '
Точка росы: $T_{\\text{росы}} \\approx '+dew.toFixed(1)+'°$C
'; + renderMath(out); + feedback(fb, true, '✓ Готово.'); + used.add(Math.round(phi/25)); + if(!_done && used.size >= 2){ _done = true; addXp(10, 'p10-iv2'); bumpProgress('p10', 15); } + } + go.addEventListener('click', calc); + [tInp, pInp].forEach(el => el.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); })); + })(); + + /* IV3 — Какая влажность? */ + (function(){ + // 0 = сухо, 1 = комфорт, 2 = влажно + const Q = [ + { q:'$\\varphi = 15\\%$', ans:0, why:'Меньше 30% — сухо.' }, + { q:'$\\varphi = 50\\%$', ans:1, why:'40–60% — зона комфорта.' }, + { q:'$\\varphi = 85\\%$', ans:2, why:'Больше 70% — влажно.' }, + { q:'$\\varphi = 30\\%$', ans:0, why:'На границе, но всё ещё сухо.' }, + { q:'$\\varphi = 60\\%$', ans:1, why:'Верхняя граница комфорта.' }, + { q:'$\\varphi = 95\\%$', ans:2, why:'Очень близко к точке росы — влажно.' }, + ]; + const LABELS = ['Сухо', 'Комфорт', 'Влажно']; + const COLORS = ['#ef4444', '#10b981', '#0ea5e9']; + let i = 0, score = 0; + const qEl = document.getElementById('p10-iv3-q'); + const oEl = document.getElementById('p10-iv3-opts'); + const fb = document.getElementById('p10-iv3-fb'); + const iEl = document.getElementById('p10-iv3-i'); + const sEl = document.getElementById('p10-iv3-s'); + function show(){ + if(i >= Q.length){ + qEl.innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + oEl.innerHTML = ''; + if(score === Q.length){ addXp(15, 'p10-iv3'); bumpProgress('p10', 25); } + else if(score >= 4){ addXp(8, 'p10-iv3'); bumpProgress('p10', 15); } + return; + } + iEl.textContent = (i+1); sEl.textContent = score; + qEl.innerHTML = Q[i].q; + oEl.innerHTML = LABELS.map((l, k) => + '' + ).join(''); + fb.style.display = 'none'; + renderMath(qEl); + oEl.querySelectorAll('button').forEach(b => { + b.addEventListener('click', () => { + const v = +b.dataset.v; + if(v === Q[i].ans){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+' Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Это '+LABELS[Q[i].ans]+'. '+Q[i].why+' Дальше ▶'); + sEl.textContent = score; + oEl.querySelectorAll('button').forEach(x => x.disabled = true); + i++; + setTimeout(show, 1800); + }); + }); + } + document.getElementById('p10-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); }); + show(); + })(); + + /* IV4 — Тренажёр влажности */ + (function(){ + const Q = [ + { q:'$t = 20°$C, $p = 1{,}17$ кПа, $p_н(20°) = 2{,}34$ кПа. Найди $\\varphi$ в %.', ans:50, tol:2, hint:'$\\varphi = 1{,}17/2{,}34 \\cdot 100\\% = 50\\%$.' }, + { q:'$\\varphi = 100\\%$, $t = 20°$C. Чему равно $p$ в кПа? (с точностью до десятых)', ans:2.34, tol:0.15, hint:'$p = p_н(20°) = 2{,}34$ кПа.' }, + { q:'$t = 50°$C, $p = 6{,}15$ кПа, $p_н(50°) = 12{,}3$ кПа. Найди $\\varphi$ в %.', ans:50, tol:2, hint:'$\\varphi = 6{,}15/12{,}3 \\cdot 100\\% = 50\\%$.' }, + { q:'$t = 25°$C, $\\varphi = 50\\%$. Чему равно $p$ в кПа? Используй $p_н(25°) \\approx 3{,}17$. Ответ в десятых.', ans:1.6, tol:0.15, hint:'$p = 0{,}5 \\cdot 3{,}17 \\approx 1{,}58$ кПа.' }, + { q:'Среднее значение комфортной влажности в комнате (в %)?', ans:50, tol:2, hint:'Норма $40$–$60\\%$, среднее — $50\\%$.' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p10-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15, 'p10-iv4'); bumpProgress('p10', 25); } + else if(score >= 3){ addXp(8, 'p10-iv4'); bumpProgress('p10', 15); } + return; + } + document.getElementById('p10-iv4-i').textContent = (i+1); + document.getElementById('p10-iv4-s').textContent = score; + document.getElementById('p10-iv4-q').innerHTML = Q[i].q; + document.getElementById('p10-iv4-ans').value = ''; + renderMath(document.getElementById('p10-iv4-q')); + document.getElementById('p10-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p10-iv4-fb'); + const raw = document.getElementById('p10-iv4-ans').value.replace(',', '.'); + const ans = parseFloat(raw); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; } + const tol = Q[i].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('p10-iv4-s').textContent = score; + i++; + setTimeout(show, 1900); + } + document.getElementById('p10-iv4-go').addEventListener('click', go); + document.getElementById('p10-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); }); + document.getElementById('p10-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); }); + show(); + })(); + wireReadBtn('p10'); } +/* ========== ФИНАЛ ГЛАВЫ 1 — «ОСНОВЫ МКТ» ========== */ + function build_final1(){ const box = document.getElementById('final1-body'); let html = ''; - html += makeCard('theory', "Финал главы 1", "★", ` -

Финал главы 1 — этот параграф в разработке (Phase 1+).

-

Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.

-

- Phase 0: создан скелет учебника. Phase 1+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019). -

- `); + + /* Часть А — Шпаргалка главы (10 mini-карточек) */ + const SHEET = [ + { t:'§ 1 · МКТ', icon:'', body:'3 положения: 1) тела состоят из частиц; 2) частицы непрерывно движутся; 3) частицы взаимодействуют (притягиваются и отталкиваются).' }, + { t:'§ 2 · Кол-во в-ва', icon:'', body:'$\\nu = \\dfrac{N}{N_A} = \\dfrac{m}{M}$, $N_A = 6 \\cdot 10^{23}$ моль⁻¹.' }, + { t:'§ 3 · Осн. ур. МКТ', icon:'', body:'$p = \\dfrac{1}{3} n m_0 \\overline{v^2} = \\dfrac{2}{3} n \\overline{E_k}$.' }, + { t:'§ 4 · Температура', icon:'', body:'$\\overline{E_k} = \\dfrac{3}{2} k_B T$, $T = t + 273$. $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К.' }, + { t:'§ 5 · Менделеев-Клапейрон', icon:'', body:'$pV = \\nu R T$, $R = 8{,}31$ Дж/(моль·К), $V_M = 22{,}4$ л/моль (н.у.).' }, + { t:'§ 6 · Изопроцессы', icon:'', body:'Бойль: $pV = $ const ($T$=const). Гей-Люссак: $V/T = $ const. Шарль: $p/T = $ const.' }, + { t:'§ 7 · Тв. тела', icon:'', body:'Кристалл (моно- и поликристаллы) — анизотропия. 4 типа решёток: ионная, атомная, молекулярная, металлическая. Аморфные тела изотропны.' }, + { t:'§ 8 · Жидкости', icon:'', body:'Ближний порядок. $F = \\sigma L$. Капилляр: $h = \\dfrac{2\\sigma\\cos\\theta}{\\rho g r}$.' }, + { t:'§ 9 · Насыщенный пар', icon:'', body:'Динамическое равновесие. $p_н$ зависит только от $T$. Кипение: $p_н(T_{\\text{кип}}) = p_{\\text{атм}}$.' }, + { t:'§ 10 · Влажность', icon:'', body:'$\\varphi = \\dfrac{p}{p_н(T)} \\cdot 100\\%$. Точка росы $T_{\\text{росы}}$: $p_н(T_{\\text{росы}}) = p$. Комфорт: $40$–$60\\%$.' }, + ]; + + html += `
+
+ ${ICONS.theory} + Шпаргалка главы 1 — Основы МКТ + Итог +
+
+

Ключевые формулы и идеи всех 10 параграфов в одном месте — просмотри перед битвой с боссами.

+
+ ${SHEET.map(s => `
+
+ ${s.icon} +
${s.t}
+
+
${s.body}
+
`).join('')} +
+
+
`; + + /* Часть Б — 7 боссов (intro) */ + html += `
+
+ ${ICONS.rule} + Боссы главы 1 + 7 +
+
+

7 интегрированных задач — каждая комбинирует ≥ 2 темы. За каждого побеждённого босса: +10 XP, +14% к прогрессу. Победишь всех — ачивка «Мастер МКТ» и +50 XP бонус.

+

Константы: $R = 8{,}3$ Дж/(моль·К), $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К, $N_A = 6 \\cdot 10^{23}$ моль⁻¹. Допуск ±5%.

+
+
`; + + html += '
'; + + html += `
+
Прогресс по боссам
+
0 / 7 боссов побеждено
+
+
+
+ +
`; + html += secNav('p10', null); - html += readButton('final1'); + box.innerHTML = html; renderMath(box); - wireReadBtn('final1'); + + /* Боссы */ + const BOSSES = [ + { + n:1, color:'#10b981', + title:'Капля Авогадро', + tag:'§ 2', + q:'Сколько молекул в $36$ г воды ($M = 18$ г/моль)? Введи коэффициент $a$ в записи $N = a \\cdot 10^{23}$.', + ans:12, + hint:'$\\nu = m/M = 36/18 = 2$ моль. $N = \\nu N_A = 2 \\cdot 6 \\cdot 10^{23} = 12 \\cdot 10^{23}$.' + }, + { + n:2, color:'#0891b2', + title:'Кинетический Голем', + tag:'§ 3 + § 4', + q:'При $T = 300$ К средняя кинетическая энергия одной молекулы. Введи $b$ в записи $\\overline{E_k} = b \\cdot 10^{-21}$ Дж.', + ans:6.2, + hint:'$\\overline{E_k} = \\dfrac{3}{2} k_B T = 1{,}5 \\cdot 1{,}38 \\cdot 10^{-23} \\cdot 300 = 6{,}21 \\cdot 10^{-21}$ Дж.' + }, + { + n:3, color:'#7c3aed', + title:'Дракон Клапейрона', + tag:'§ 5', + q:'В баллоне $\\nu = 2$ моль газа при $T = 300$ К, $V = 50$ л. Найди $p$ в кПа.', + ans:99.6, + hint:'$p = \\dfrac{\\nu R T}{V} = \\dfrac{2 \\cdot 8{,}3 \\cdot 300}{0{,}05} = 99\\,600$ Па $\\approx 99{,}6$ кПа.' + }, + { + n:4, color:'#dc2626', + title:'Изохорный Циклоп', + tag:'§ 6', + q:'Газ при $T_1 = 290$ К, $p_1 = 2$ атм нагрели изохорно до $T_2 = 348$ К. Найди $p_2$ в атм.', + ans:2.4, + hint:'Закон Шарля: $p_2 = p_1 \\dfrac{T_2}{T_1} = 2 \\cdot \\dfrac{348}{290} = 2{,}4$ атм.' + }, + { + n:5, color:'#0ea5e9', + title:'Капиллярный Спрут', + tag:'§ 8', + q:'$\\sigma = 0{,}073$ Н/м, $L = 0{,}5$ м. Найди силу поверхностного натяжения $F$ в мН.', + ans:36.5, + hint:'$F = \\sigma L = 0{,}073 \\cdot 0{,}5 = 0{,}0365$ Н $= 36{,}5$ мН.' + }, + { + n:6, color:'#06b6d4', + title:'Туман-Хранитель', + tag:'§ 9 + § 10', + q:'При $t = 50°$C давление пара $p = 8$ кПа, $p_н(50°) = 12{,}3$ кПа. Найди $\\varphi$ в %.', + ans:65, + hint:'$\\varphi = \\dfrac{p}{p_н} \\cdot 100\\% = \\dfrac{8}{12{,}3} \\cdot 100\\% \\approx 65\\%$.' + }, + { + n:7, color:'#f59e0b', + title:'Магистр МКТ', + tag:'§ 3 + § 4 + § 5', + q:'В сосуде $V = 1$ л идеальный газ при $T = 300$ К, $p = 100$ кПа. Найди число молекул $N$. Введи $c$ в записи $N = c \\cdot 10^{22}$.', + ans:2.4, + hint:'$pV = N k_B T$ → $N = \\dfrac{pV}{k_B T} = \\dfrac{10^5 \\cdot 10^{-3}}{1{,}38 \\cdot 10^{-23} \\cdot 300} \\approx 2{,}42 \\cdot 10^{22}$.' + }, + ]; + + const cont = document.getElementById('ch1-bosses-container'); + const STATE_KEY = 'physics10_ch1_bosses'; + const BOSS_STATE = (function(){ + try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }catch(e){} + return BOSSES.map(()=>({defeated:false})); + })(); + function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} } + + cont.innerHTML = BOSSES.map((b, idx)=>{ + return '
' + +'
' + +'' + +'
Босс '+b.n+': '+b.title+'
' + +'
'+b.tag+'
' + +'
' + +'
'+b.q+'
' + +'
' + +'ответ =' + +'' + +'' + +'' + +'
' + +'' + +'
'; + }).join(''); + renderMath(cont); + + function refreshOverall(){ + const won = BOSS_STATE.filter(s => s.defeated).length; + const txt = document.getElementById('ch1-boss-overall'); + const fill = document.getElementById('ch1-boss-overall-fill'); + if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено'; + if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%'; + if(won >= BOSSES.length){ + const reward = document.getElementById('ch1-final-reward'); + if(reward && reward.style.display === 'none'){ + reward.style.display = 'block'; + if(!STATE.achievements.has('ch1_done')){ + achievement('ch1_done','Мастер МКТ'); + addXp(50, 'ch1-bonus'); + bumpProgress('final1', 30); + if(window.confetti){ try{ confetti(); }catch(e){} } + } + } + } + } + + BOSSES.forEach((b, idx)=>{ + const card = document.getElementById('boss1-'+b.n+'-card'); + const goBtn = document.getElementById('boss1-'+b.n+'-go'); + const hintBtn = document.getElementById('boss1-'+b.n+'-hint'); + const ansInp = document.getElementById('boss1-'+b.n+'-ans'); + if(BOSS_STATE[idx].defeated){ + card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))'; + card.classList.add('glow'); + goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен'; + ansInp.disabled = true; + } + goBtn.addEventListener('click', ()=>{ + if(BOSS_STATE[idx].defeated) return; + const fb = document.getElementById('boss1-'+b.n+'-fb'); + const raw = ansInp.value.replace(',', '.'); + const val = parseFloat(raw); + if(isNaN(val)){ feedback(fb, false, '✗ Введи число.'); return; } + const tol = Math.max(0.05 * Math.abs(b.ans), 0.05); + if(Math.abs(val - b.ans) < tol + 0.001){ + BOSS_STATE[idx].defeated = true; saveBosses(); + feedback(fb, true, '✓ Босс '+b.n+' повержен! +10 XP. '+b.hint); + addXp(10, 'boss-ch1-'+b.n); + bumpProgress('final1', 14); + goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен'; + ansInp.disabled = true; + card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))'; + card.classList.add('glow','pulse'); + setTimeout(()=>card.classList.remove('pulse'), 900); + refreshOverall(); + } else { + feedback(fb, false, '✗ Промах. Попробуй ещё. Подсказка доступна.'); + } + }); + hintBtn.addEventListener('click', ()=>{ + const fb = document.getElementById('boss1-'+b.n+'-fb'); + fb.className = 'feedback ok'; + fb.innerHTML = 'Подсказка: '+b.hint; + fb.style.display = 'block'; + fb.style.background = 'var(--warn-bg)'; + fb.style.color = '#92400e'; + fb.style.borderLeftColor = 'var(--warn)'; + renderMath(fb); + }); + ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); }); + }); + + refreshOverall(); } /* ===== Search ===== */