Files
Learn_System/frontend/textbooks/physics_8_ch1.html
T
Maxim Dolgolyov eaee79dc8a feat(phys8 ch1): Phase 1.2 — IV-6 интерактивы §3, §6, §8
Заменены stub'ы 'coming soon' на полноценные drag-and-drop виджеты:

§3 Тепловая лавочка (Heat Conductor Bench):
- SVG-sandbox 560×300 с горелкой (drop zone) и 4 стержнями
  (медь λ=400, серебро λ=430, стекло λ=0.8, дерево λ=0.15).
- P8Drag.attach на каждый стержень → drop на горелку.
- При drop'е sim запускается: P8Anim.raf обновляет цвет
  каждого сегмента стержня через P8Helpers.thermal.tempColor()
  по log-нормализованной λ. Тепловая волна идёт по стержню.
- Readouts: материал, λ, T дальнего конца.

§6 Heat Mixer (Q=cmΔT):
- 2 ёмкости (m₁, T₁), (m₂, T₂) — рисуются с цветом по T.
- 4 scrubber'a (m₁, T₁, m₂, T₂) с live update SVG.
- Кнопка 'Смешать' → tween анимация в 1.2 с → итоговая T
  через формулу теплового баланса (m₁T₁+m₂T₂)/(m₁+m₂).
- Readout T_итог, кнопка 'Сброс'.

§8 График плавления (Phase Diagram T(t)):
- T-t график 560×280 с осями (-20 до 120°C, 0 до 300 с).
- Фазовые области: лёд (синий), вода (голубой), пар (жёлтый).
- Реальная симуляция: c_льда=2100, c_воды=4200, λ=330000,
  r=2300000. P8Anim.raf вычисляет накопление энергии и
  фазовые переходы — плато на 0°C (плавление) и 100°C
  (кипение).
- Scrubber мощности 100-2000 Вт. Кнопки Старт/Сброс.
- Readouts: фаза, T.

+10 XP за каждое успешное взаимодействие.
2026-05-30 10:03:55 +03:00

3241 lines
234 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Физика 8 · Глава 1 · «Тепловые явления»</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<link rel="stylesheet" href="/css/phys8-interactives.css">
<link rel="stylesheet" href="/css/phys8-design-system.css">
<link rel="stylesheet" href="/css/phys-textbook-widgets.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/g3d.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/phys8-helpers.js" defer></script>
<script src="/js/phys8-drag.js" defer></script>
<script src="/js/phys8-anim.js" defer></script>
<script src="/js/optics.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fef2f2; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#7c3aed; --pri2:#5b21b6; --pri-soft:#ede9fe;
--acc:#a78bfa; --acc2:#7c3aed; --acc-soft:#ede9fe;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
function _initP8_iv6(){
const sb = document.getElementById('p8-iv6-sandbox');
if (!sb || !window.P8Helpers || !window.P8Anim) return;
const W = 560, H = 280;
const svg = P8Helpers.svg.create(W, H);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
/* Sim state */
const m = 0.5; // kg ice
const c_ice = 2100;
const c_water = 4200;
const lambda = 330000;
const r_vap = 2300000;
let power = 500;
let energyAccumulated = 0;
let running = false;
let raf = null;
let points = [{ t: 0, T: -20 }];
/* Axes */
const pad = { l: 50, r: 18, t: 22, b: 32 };
const plotW = W - pad.l - pad.r;
const plotH = H - pad.t - pad.b;
/* Background */
svg.appendChild(P8Helpers.svg.el('rect', { x: pad.l, y: pad.t, width: plotW, height: plotH, fill: '#fafafa', stroke: '#e5e7eb' }));
/* Y axis: -20 to 120 °C */
const yMin = -20, yMax = 120;
function yToPx(T) { return pad.t + plotH * (1 - (T - yMin) / (yMax - yMin)); }
function tToPx(t) { return pad.l + plotW * Math.min(1, t / 300); } // 300 s scale
/* Y ticks */
[-20, 0, 20, 40, 60, 80, 100, 120].forEach(t => {
const y = yToPx(t);
svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: y, x2: pad.l + plotW, y2: y, stroke: '#e5e7eb' }));
svg.appendChild(P8Helpers.svg.el('text', { x: pad.l - 6, y: y + 3, 'font-family':"'JetBrains Mono',monospace", 'font-size': 10, fill: 'var(--p8-muted,#64748b)', 'text-anchor':'end', text: t+'°' }));
});
/* Phase regions overlays (transparent) */
const phaseRegions = [
{ from: -20, to: 0, fill: '#bfdbfe', name: 'лёд' },
{ from: 0, to: 100, fill: '#7dd3fc', name: 'вода' },
{ from: 100, to: 120, fill: '#fde68a', name: 'пар' }
];
phaseRegions.forEach(r => {
const y1 = yToPx(r.from), y2 = yToPx(r.to);
svg.appendChild(P8Helpers.svg.el('rect', { x: pad.l, y: y2, width: plotW, height: y1 - y2, fill: r.fill, opacity: 0.18 }));
svg.appendChild(P8Helpers.svg.el('text', { x: pad.l + plotW - 6, y: (y1 + y2) / 2 + 3, 'font-family':"'Inter',sans-serif", 'font-size': 10, 'font-weight': 700, fill: 'var(--p8-text)', 'text-anchor': 'end', text: r.name }));
});
/* Phase lines (0 and 100) */
svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: yToPx(0), x2: pad.l + plotW, y2: yToPx(0), stroke: '#0f172a', 'stroke-width': 1, 'stroke-dasharray': '3 3' }));
svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: yToPx(100), x2: pad.l + plotW, y2: yToPx(100), stroke: '#0f172a', 'stroke-width': 1, 'stroke-dasharray': '3 3' }));
/* X axis */
svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: pad.t + plotH, x2: pad.l + plotW, y2: pad.t + plotH, stroke: '#0f172a' }));
svg.appendChild(P8Helpers.svg.el('text', { x: pad.l + plotW / 2, y: H - 6, 'font-family':"'Inter',sans-serif", 'font-size': 11, 'font-weight': 700, fill: 'var(--p8-text)', 'text-anchor': 'middle', text: 'Время, с' }));
/* Curve path (will be updated) */
const path = P8Helpers.svg.el('path', { d: '', fill: 'none', stroke: 'var(--th-mid, #f97316)', 'stroke-width': 3, 'stroke-linejoin': 'round', 'stroke-linecap': 'round' });
svg.appendChild(path);
function updatePath(){
if (!points.length) return;
const d = points.map((p, i) => (i === 0 ? 'M' : 'L') + tToPx(p.t).toFixed(1) + ',' + yToPx(p.T).toFixed(1)).join(' ');
path.setAttribute('d', d);
}
function currentT(){ return points[points.length-1].T; }
function currentPhase(T){
if (T < 0) return 'лёд';
if (T < 100) return T === 0 ? 'плавление' : 'вода';
if (T === 100) return 'кипение';
return 'пар';
}
function tick(dt){
if (!running) return;
const energy = power * dt; // J
let T = currentT();
let newT = T;
if (T < 0) {
/* heating ice */
const dT = energy / (c_ice * m);
newT = T + dT;
if (newT > 0) newT = 0;
} else if (T < 0.001 && energyAccumulated < lambda * m) {
/* phase transition (melting) */
energyAccumulated += energy;
newT = 0;
if (energyAccumulated >= lambda * m) {
newT = 0.001;
}
} else if (T < 100) {
/* heating water */
const dT = energy / (c_water * m);
newT = T + dT;
if (newT > 100) newT = 100;
} else if (T < 100.001 && energyAccumulated < (lambda + r_vap) * m) {
/* phase transition (boiling) */
energyAccumulated += energy;
newT = 100;
if (energyAccumulated >= (lambda + r_vap) * m) {
newT = 100.001;
}
} else if (T < 120) {
const dT = energy / (c_water * m); // simplified for steam
newT = T + dT;
if (newT > 120) newT = 120;
} else {
running = false;
}
const lastP = points[points.length-1];
points.push({ t: lastP.t + dt, T: newT });
if (points.length > 600) points.shift();
updatePath();
document.getElementById('p8-iv6-temp').textContent = Math.round(newT);
document.getElementById('p8-iv6-phase').textContent = currentPhase(newT);
if (lastP.t > 300) running = false;
}
raf = P8Anim.raf(dt => tick(Math.min(dt * 4, 0.5))); // accelerate 4x for demo
/* Bind controls */
const pwrInp = document.getElementById('p8-iv6-pwr');
const pwrLab = document.getElementById('p8-iv6-pwr-val');
pwrInp.oninput = () => { power = +pwrInp.value; pwrLab.textContent = power; };
document.getElementById('p8-iv6-play').onclick = () => {
if (!running) { running = true; raf.start(); if (window.addXp) addXp(10, 'p8-iv6-melt'); }
};
document.getElementById('p8-iv6-reset').onclick = () => {
running = false; raf.stop();
energyAccumulated = 0;
points = [{ t: 0, T: -20 }];
updatePath();
document.getElementById('p8-iv6-temp').textContent = '-20';
document.getElementById('p8-iv6-phase').textContent = 'лёд';
};
updatePath();
}
function _initP6_iv6(){
const sb = document.getElementById('p6-iv6-sandbox');
if (!sb || !window.P8Helpers || !window.P8Anim) return;
const svg = P8Helpers.svg.create(560, 240);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
/* Vessel positions (will animate to centre on mix) */
const v1 = { x: 140, y: 130, m: 0.5, T: 80, color: '#fb923c' };
const v2 = { x: 420, y: 130, m: 1.0, T: 20, color: '#7dd3fc' };
const finalState = { active: false, T: 50, fillFraction: 0.5 };
function drawVessel(x, y, m, T, color){
const g = P8Helpers.svg.el('g', { transform: 'translate('+x+','+y+')' });
const h = 30 + m * 50;
const w = 70;
/* Glass */
g.appendChild(P8Helpers.svg.el('rect', { x:-w/2, y:-h, width:w, height:h, rx:6, fill:'rgba(255,255,255,.6)', stroke:'#0f172a', 'stroke-width':1.5 }));
/* Liquid */
g.appendChild(P8Helpers.svg.el('rect', { x:-w/2+3, y:-h+5, width:w-6, height:h-8, rx:4, fill: P8Helpers.thermal.tempColor(T/100) }));
/* Label */
g.appendChild(P8Helpers.svg.el('text', { x:0, y:18, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'm='+m.toFixed(1)+' кг' }));
g.appendChild(P8Helpers.svg.el('text', { x:0, y:32, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'T='+Math.round(T)+'°C' }));
return g;
}
let v1G = drawVessel(v1.x, v1.y, v1.m, v1.T, v1.color);
let v2G = drawVessel(v2.x, v2.y, v2.m, v2.T, v2.color);
svg.appendChild(v1G); svg.appendChild(v2G);
function redraw(){
svg.innerHTML = '';
if (!finalState.active) {
v1G = drawVessel(v1.x, v1.y, v1.m, v1.T, v1.color);
v2G = drawVessel(v2.x, v2.y, v2.m, v2.T, v2.color);
svg.appendChild(v1G); svg.appendChild(v2G);
} else {
/* Single combined vessel */
const cv = drawVessel(280, 130, v1.m + v2.m, finalState.T, P8Helpers.thermal.tempColor(finalState.T/100));
svg.appendChild(cv);
/* Result label */
svg.appendChild(P8Helpers.svg.el('text', { x:280, y:60, 'font-family':"'Unbounded',sans-serif", 'font-size':14, 'font-weight':800, fill:'var(--th-mid,#f97316)', 'text-anchor':'middle', text: 'T_итог = '+Math.round(finalState.T)+' °C' }));
}
}
/* Hook up scrubbers */
function bindScrub(inputId, valId, obj, prop){
const input = document.getElementById(inputId);
const lab = document.getElementById(valId);
if (!input || !lab) return;
input.addEventListener('input', () => {
const v = parseFloat(input.value);
obj[prop] = v;
lab.textContent = v.toFixed(prop === 'm' ? 1 : 0);
if (finalState.active) {
finalState.active = false;
document.getElementById('p6-iv6-tf').textContent = '—';
}
redraw();
});
}
bindScrub('p6-iv6-m1', 'p6-iv6-m1-val', v1, 'm');
bindScrub('p6-iv6-t1', 'p6-iv6-t1-val', v1, 'T');
bindScrub('p6-iv6-m2', 'p6-iv6-m2-val', v2, 'm');
bindScrub('p6-iv6-t2', 'p6-iv6-t2-val', v2, 'T');
/* Mix button */
document.getElementById('p6-iv6-mix').onclick = () => {
const T = (v1.m * v1.T + v2.m * v2.T) / (v1.m + v2.m);
finalState.active = true;
finalState.T = T;
P8Anim.tween({
from: v1.T, to: T, duration: 1200, easing: 'cubicInOut',
onUpdate: t => {
finalState.T = t;
redraw();
document.getElementById('p6-iv6-tf').textContent = Math.round(t);
}
});
if (window.addXp) addXp(10, 'p6-iv6-mix');
};
document.getElementById('p6-iv6-reset').onclick = () => {
finalState.active = false;
document.getElementById('p6-iv6-tf').textContent = '—';
redraw();
};
redraw();
}
function _initP3_iv6(){
const sb = document.getElementById('p3-iv6-sandbox');
if (!sb || !window.P8Helpers || !window.P8Drag || !window.P8Anim) return;
const svg = P8Helpers.svg.create(560, 300);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
/* Горелка (drop zone) */
const burner = P8Helpers.svg.el('g', { transform: 'translate(80, 240)' });
burner.appendChild(P8Helpers.svg.el('rect', { x:-32, y:-8, width:64, height:32, rx:4, fill:'#475569' }));
burner.appendChild(P8Helpers.svg.el('rect', { x:-26, y:-22, width:52, height:14, rx:7, fill:'#dc2626' }));
burner.appendChild(P8Helpers.svg.el('text', { x:0, y:48, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text:'Горелка (drop)' }));
svg.appendChild(burner);
/* Палитра 4 стержней */
const rods = [
{ name:'Медь', lam:400, color:'#b45309', x:200, y:50 },
{ name:'Серебро', lam:430, color:'#9ca3af', x:300, y:50 },
{ name:'Стекло', lam:0.8, color:'#bae6fd', x:400, y:50 },
{ name:'Дерево', lam:0.15,color:'#a16207', x:500, y:50 }
];
const rodEls = [];
rods.forEach((rod, i) => {
const g = P8Helpers.svg.el('g', { transform: 'translate('+rod.x+','+rod.y+')' });
/* Sections of rod, each will be colored by T gradient when active */
const segments = 12;
const segs = [];
for (let s = 0; s < segments; s++) {
const r = P8Helpers.svg.el('rect', {
x: -55 + s * (110/segments), y: -10, width: 110/segments, height: 20,
fill: rod.color, stroke: 'none'
});
g.appendChild(r);
segs.push(r);
}
/* Frame */
g.appendChild(P8Helpers.svg.el('rect', { x:-55, y:-10, width:110, height:20, rx:3, fill:'none', stroke:'#0f172a', 'stroke-width':1.5 }));
g.appendChild(P8Helpers.svg.el('text', { x:0, y:-18, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: rod.name }));
g.appendChild(P8Helpers.svg.el('text', { x:0, y:30, 'font-family':"'JetBrains Mono',monospace", 'font-size':9, 'font-weight':600, fill:'var(--p8-muted, #64748b)', 'text-anchor':'middle', text: 'λ='+rod.lam }));
svg.appendChild(g);
rodEls.push({ rod, g, segs, x: rod.x, y: rod.y });
});
/* Active sim state */
let activeIdx = -1;
let simLoop = null;
let simTime = 0;
const matEl = document.getElementById('p3-iv6-mat');
const lamEl = document.getElementById('p3-iv6-lam');
const tendEl = document.getElementById('p3-iv6-tend');
function resetColors(rodObj){
rodObj.segs.forEach(s => s.setAttribute('fill', rodObj.rod.color));
}
function startSim(rodObj){
if (simLoop) simLoop.stop();
simTime = 0;
/* λ нормализованный 0..1: log scale (15 -> 430) */
const lamNorm = Math.min(1, Math.log10(rodObj.rod.lam + 1) / Math.log10(500));
simLoop = P8Anim.raf((dt, t) => {
simTime += dt;
/* Diffusion-like: каждый сегмент i прогревается со скоростью lamNorm */
const speed = lamNorm * 0.8 + 0.04;
rodObj.segs.forEach((seg, i) => {
const pos = i / (rodObj.segs.length - 1);
const wave = speed * simTime;
const heat = Math.max(0, Math.min(1, wave - pos));
seg.setAttribute('fill', P8Helpers.thermal.tempColor(heat * 0.85 + 0.1));
});
/* T-end value */
const endHeat = Math.max(0, Math.min(1, speed * simTime - 0.95));
const tEnd = Math.round(20 + endHeat * 80);
if (tendEl) tendEl.textContent = tEnd;
if (simTime > 30) simLoop.stop();
});
simLoop.start();
if (matEl) matEl.textContent = rodObj.rod.name;
if (lamEl) lamEl.textContent = rodObj.rod.lam;
}
/* Attach drag to each rod */
rodEls.forEach((rodObj, i) => {
P8Drag.attach(rodObj.g, {
container: svg,
onMove: (ev, pos) => {
rodObj.x = pos.x;
rodObj.y = pos.y;
rodObj.g.setAttribute('transform', 'translate('+rodObj.x+','+rodObj.y+')');
},
onEnd: (ev, pos) => {
/* Check if dropped near burner */
if (Math.abs(pos.x - 80) < 70 && Math.abs(pos.y - 240) < 50) {
/* Snap to position above burner */
P8Anim.tween({
from: 0, to: 1, duration: 320, easing: 'cubicOut',
onUpdate: k => {
rodObj.x = pos.x + (80 + 55 - pos.x) * k;
rodObj.y = pos.y + (220 - pos.y) * k;
rodObj.g.setAttribute('transform', 'translate('+rodObj.x+','+rodObj.y+')');
}
});
/* Reset other rods to original */
rodEls.forEach((other, j) => {
if (j === i) return;
resetColors(other);
});
activeIdx = i;
startSim(rodObj);
if (window.addXp) addXp(10, 'p3-iv6-conduct');
}
}
});
});
/* Help text */
svg.appendChild(P8Helpers.svg.el('text', {
x: 280, y: 290,
'font-family': "'Inter', sans-serif", 'font-size': 10,
fill: 'var(--p8-muted, #64748b)', 'text-anchor': 'middle',
text: 'Перетащи стержень на горелку • Чем выше λ — тем быстрее цвет дойдёт до конца'
}));
}
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --ink:#fef9e7; --muted:#a39070; --border:#2a2512}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#7f1d1d 0%,#dc2626 55%,#fca5a5 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft))}
.psel-card.final .psel-num{color:var(--warn)}
.sec[id="sec-p1"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p2"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p3"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p4"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p5"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p6"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p7"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p8"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p9"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p10"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p11"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec[id="sec-final1"]{ --sec-acc:#dc2626; --sec-acc-d:#991b1b; --sec-acc-soft:#fee2e2; }
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display: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)}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--acc));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(min-width:981px){#sidebar-btn{display:none}.col-side-backdrop.show{display:none}}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.search-modal.show{display:flex}
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
.search-results{flex:1;overflow-y:auto;padding:6px 0}
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
.sec{transition:opacity .25s}
</style>
</head>
<body class="p8-theme-thermal">
<header class="p8-hero">
<div class="p8-hero-wm"><svg viewBox="0 0 100 100" aria-hidden="true">
<path d="M50 8 C 52 22 65 30 64 46 C 63 56 56 60 55 48 C 53 56 48 60 42 58 C 36 56 32 50 34 42 C 30 52 22 60 24 72 C 26 84 36 92 50 92 C 64 92 76 84 76 70 C 76 50 60 40 56 22 C 54 14 52 10 50 8 Z"/>
</svg></div>
<div class="p8-hero-meter" id="p8-meter-ch1"><span id="p8-meter-val">37</span>°C</div>
<div class="p8-hero-inner">
<div class="p8-hero-eyebrow">Глава 1 · 11 параграфов</div>
<h1 class="p8-hero-title">Тепловые явления</h1>
<div class="p8-hero-sub">Внутренняя энергия, способы теплопередачи, плавление и кипение. Перетаскивайте термометры, нагреватели и материалы — наблюдайте поведение тепла в реальном времени.</div>
<div class="hdr-side" style="margin-top:18px;display:flex;gap:8px;flex-wrap:wrap;position:relative;z-index:1">
<a href="/textbook/physics-8" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 8</a>
<button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Тепловые явления — как энергия переходит между телами</h2>
<p>Внутренняя энергия зависит от температуры тела. Тепло передаётся теплопроводностью, конвекцией и излучением. При нагревании, плавлении и кипении нужно разное количество теплоты.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 1</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p1" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="28" stroke="currentColor" stroke-width="6" fill="none"/><path d="M50 22 v56 M22 50 h56" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 1</span><h2 class="sec-h">Внутренняя энергия</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p2" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M50 12 v76 M50 12 l-14 16 M50 12 l14 16 M50 88 l-14-16 M50 88 l14-16" stroke="currentColor" stroke-width="4" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 2</span><h2 class="sec-h">Способы изменения внутренней энергии</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p3" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M14 50 h72 M86 50 l-14-14 M86 50 l-14 14" stroke="currentColor" stroke-width="5" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 3</span><h2 class="sec-h">Теплопроводность</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p4" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M30 80 C 30 50, 70 50, 70 30 M30 30 C 30 60, 70 60, 70 80" stroke="currentColor" stroke-width="4" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 4</span><h2 class="sec-h">Конвекция</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p5" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="14" fill="currentColor"/><g stroke="currentColor" stroke-width="4" fill="none"><line x1="50" y1="6" x2="50" y2="22"/><line x1="50" y1="78" x2="50" y2="94"/><line x1="6" y1="50" x2="22" y2="50"/><line x1="78" y1="50" x2="94" y2="50"/><line x1="18" y1="18" x2="30" y2="30"/><line x1="70" y1="70" x2="82" y2="82"/><line x1="82" y1="18" x2="70" y2="30"/><line x1="30" y1="70" x2="18" y2="82"/></g></svg></div><div class="sec-header"><span class="sec-num">&sect; 5</span><h2 class="sec-h">Излучение</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p6" aria-hidden="true"><svg viewBox="0 0 100 100"><rect x="20" y="35" width="60" height="35" rx="4" stroke="currentColor" stroke-width="4" fill="none"/><path d="M28 35 v-8 M50 35 v-8 M72 35 v-8" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 6</span><h2 class="sec-h">Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p7" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M28 78 L50 22 L72 78 Z" stroke="currentColor" stroke-width="4" fill="none"/><path d="M40 60 L60 60" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 7</span><h2 class="sec-h">Горение. Удельная теплота сгорания топлива</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p8" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M30 30 L70 30 L70 70 L30 70 Z" stroke="currentColor" stroke-width="4" fill="none"/><path d="M30 50 L70 50" stroke="currentColor" stroke-width="3" stroke-dasharray="4 3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 8</span><h2 class="sec-h">Плавление и кристаллизация</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p9" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M50 14 L70 50 L50 86 L30 50 Z" stroke="currentColor" stroke-width="4" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 9</span><h2 class="sec-h">Удельная теплота плавления и кристаллизации</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p10" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M20 70 Q 35 50, 50 65 T 80 60" stroke="currentColor" stroke-width="4" fill="none"/><circle cx="78" cy="32" r="6" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 10</span><h2 class="sec-h">Испарение жидкостей. Факторы, влияющие на скорость испарения</h2></div><div id="p10-body"></div></section>
<section id="sec-p11" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p11" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="35" cy="55" r="6" fill="currentColor"/><circle cx="55" cy="45" r="8" fill="currentColor"/><circle cx="65" cy="65" r="5" fill="currentColor"/><circle cx="50" cy="75" r="4" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 11</span><h2 class="sec-h">Кипение жидкостей. Удельная теплота парообразования</h2></div><div id="p11-body"></div></section>
<section id="sec-final1" class="sec"><div class="sec-header"><span class="sec-num">&#9733;</span><h2 class="sec-h">Финал главы</h2></div><div id="final1-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Физика 8» · Глава 1 · «Тепловые явления» · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<div id="search-modal" class="search-modal" role="dialog">
<div class="search-box">
<input type="text" id="search-input" class="search-input" placeholder="Поиск…" autocomplete="off">
<div id="search-results" class="search-results"></div>
<div class="search-foot"><span><kbd>↑↓</kbd> навигация</span><span><kbd>Enter</kbd> открыть</span><span><kbd>Esc</kbd> закрыть</span></div>
</div>
</div>
<script>
'use strict';
const STATE = { current:'p1', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 12;
const _TB_SLUG = 'physics-8-ch1';
const LS_PREFIX = 'physics8_ch1';
const LS_XP = 'physics8_xp';
const PARAS = [
{ id:'p1', num:'\u00a7 1', name:'Внутренняя энергия', sub:'$U$ зависит от $T$' },
{ id:'p2', num:'\u00a7 2', name:'Способы изменения внутренней энергии', sub:'Работа и теплопередача' },
{ id:'p3', num:'\u00a7 3', name:'Теплопроводность', sub:'Передача без переноса в-ва' },
{ id:'p4', num:'\u00a7 4', name:'Конвекция', sub:'Перенос потоками' },
{ id:'p5', num:'\u00a7 5', name:'Излучение', sub:'Тепловое излучение' },
{ id:'p6', num:'\u00a7 6', name:'Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость', sub:'$Q = cm\\Delta T$' },
{ id:'p7', num:'\u00a7 7', name:'Горение. Удельная теплота сгорания топлива', sub:'$Q = qm$' },
{ id:'p8', num:'\u00a7 8', name:'Плавление и кристаллизация', sub:'$T_{пл}$, графики $T(t)$' },
{ id:'p9', num:'\u00a7 9', name:'Удельная теплота плавления и кристаллизации', sub:'$Q = \\lambda m$' },
{ id:'p10', num:'\u00a7 10', name:'Испарение жидкостей. Факторы, влияющие на скорость испарения', sub:'Зависит от $T$, $S$' },
{ id:'p11', num:'\u00a7 11', name:'Кипение жидкостей. Удельная теплота парообразования', sub:'$Q = Lm$' },
{ id:'final1', num:'\u2605', name:'Финал главы', sub:'Итоги · 7 боссов', final:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
const ACH_LABELS = {
start:"Начало главы 1!",
p1_done:"Внутренняя энергия освоен!",
p2_done:"Способы изменения внутренней энергии освоен!",
p3_done:"Теплопроводность освоен!",
p4_done:"Конвекция освоен!",
p5_done:"Излучение освоен!",
p6_done:"Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость освоен!",
p7_done:"Горение. Удельная теплота сгорания топлива освоен!",
p8_done:"Плавление и кристаллизация освоен!",
p9_done:"Удельная теплота плавления и кристаллизации освоен!",
p10_done:"Испарение жидкостей. Факторы, влияющие на скорость испарения освоен!",
p11_done:"Кипение жидкостей. Удельная теплота парообразования освоен!",
ch1_done:"Глава 1 пройдена!",
thermal_master:"Мастер теплоты — все боссы главы 1 повержены!"
};
const SIDEBARS = {
p1:{title:"Шпаргалка § 1",rows:[
["$U$","сумма $E_k$ хаотич. движения молекул и $E_p$ их взаимодействия"],
["Зависит от","$T$, $m$, агрегатного состояния, рода вещества"],
["Не зависит от","скорости тела как целого, высоты, формы, цвета"],
["При нагреве","молекулы быстрее $\\Rightarrow$ $U$ растёт"]
]},
p2:{title:"Шпаргалка § 2",rows:[
["Способа 2","совершение работы и теплопередача"],
["Работа","трение, удар, сжатие газа, деформация"],
["Теплопередача","проводность, конвекция, излучение"],
["Знак","нагрев $\\Rightarrow$ $\\Delta U > 0$; охлаждение $\\Rightarrow$ $\\Delta U < 0$"]
]},
p3:{title:"Шпаргалка § 3",rows:[
["Теплопроводность","передача $U$ без переноса вещества"],
["Хорошие","металлы (медь, серебро, алюминий)"],
["Плохие","дерево, пластик, воздух, шерсть, вакуум"],
["В газах и жидкостях","низкая (исключение — ртуть)"]
]},
p4:{title:"Шпаргалка § 4",rows:[
["Конвекция","перенос $U$ потоками жидкости / газа"],
["Механизм","тёплое $\\uparrow$, холодное $\\downarrow$"],
["В твёрдых телах","невозможна"],
["Примеры","батарея, ветер, кипение"]
]},
p5:{title:"Шпаргалка § 5",rows:[
["Излучение","перенос $U$ электромагнитными волнами"],
["Среда","не нужна — идёт через вакуум"],
["Излучают всё","чем горячее, тем сильнее"],
["Тёмные тела","поглощают и излучают сильнее светлых"]
]},
p6:{title:"Шпаргалка § 6",rows:[
["Закон","$Q = c m \\Delta T$"],
["$c$ — уд. теплоёмкость","Дж/(кг·К)"],
["вода","$c = 4200$"],
["лёд","$c = 2100$"],
["алюминий","$c = 920$"],
["медь","$c = 380$"],
["железо","$c = 460$"],
["Баланс","$Q_{отд} = Q_{пол}$"]
]},
p7:{title:"Шпаргалка § 7",rows:[
["Закон","$Q = q m$"],
["$q$ — уд. теплота сгорания","Дж/кг"],
["дрова","$q = 10^7$"],
["уголь","$q = 3 \\cdot 10^7$"],
["бензин","$q = 4{,}6 \\cdot 10^7$"],
["природ. газ","$q = 4{,}4 \\cdot 10^7$"],
["КПД котла","$\\eta = Q_{пол}/(q m)$"]
]},
p8:{title:"Шпаргалка § 8",rows:[
["Плавление","$T = T_{пл} = $ const"],
["Кристаллизация","$T = T_{кр} = T_{пл}$"],
["На графике","горизонт. плато"],
["Энергия идёт на","разрушение / построение решётки"],
["лёд","$T_{пл} = 0$ &#176;C"],
["алюминий","$T_{пл} = 660$ &#176;C"],
["железо","$T_{пл} = 1539$ &#176;C"]
]},
p9:{title:"Шпаргалка § 9",rows:[
["Формула","$Q = \\lambda m$"],
["$\\lambda$","удельная теплота плавления, Дж/кг"],
["лёд","$\\lambda = 3{,}34 \\cdot 10^5$"],
["свинец","$\\lambda = 2{,}5 \\cdot 10^4$"],
["железо","$\\lambda = 2{,}7 \\cdot 10^5$"],
["Баланс","$Q_{нагр} + Q_{пл} + Q_{нагр.ж}$"]
]},
p10:{title:"Шпаргалка § 10",rows:[
["Испарение","с поверхности при любой $T$"],
["Скорость зависит от","$T$, $S$, ветра, рода жидкости"],
["Жидкость","охлаждается (уходят быстрые)"],
["Конденсация","пар $\\to$ жидкость, тепло выделяется"]
]},
p11:{title:"Шпаргалка § 11",rows:[
["Кипение","по всему объёму при $T = T_{кип}$"],
["Формула","$Q = Lm$"],
["$L$ для воды","$2{,}26 \\cdot 10^6$ Дж/кг"],
["$T_{кип}$ воды","100 &#176;C (при 1 атм)"],
["Давление $\\downarrow$","$T_{кип} \\downarrow$ (на горе)"]
]},
final1:{title:"Финал главы 1",rows:[
["Формулы","$Q = cm\\Delta T$, $Q = qm$, $Q = \\lambda m$, $Q = Lm$"],
["3 вида теплопередачи","проводность, конвекция, излучение"],
["Награда","+50 XP + «Мастер теплоты»"]
]}
};
const TIPS=[
{sec:'p1',html:"Тело состоит из молекул. Они движутся (есть $E_k$) и взаимодействуют (есть $E_p$). Их сумма — внутренняя энергия $U$. Главное: $U$ не зависит от того, движется ли тело и на какой высоте оно лежит."},
{sec:'p2',html:"Изменить $U$ можно двумя способами: совершить работу (трение, сжатие, удар) или передать тепло без работы (контакт, поток, излучение). Чай в стакане остывает — это теплопередача. Спички в коробке нагреваются от тряски — это работа."},
{sec:'p3',html:"Один конец металлического стержня в огне — другой нагревается, хотя ничто не движется. Это <b>теплопроводность</b>: молекулы передают энергию соседям. У металлов это работает быстро, у дерева — медленно."},
{sec:'p4',html:"Тёплый воздух легче холодного и поднимается вверх. Так батарея греет всю комнату — это <b>конвекция</b>. В твёрдых телах конвекции нет, потому что молекулы не могут свободно двигаться."},
{sec:'p5',html:"Солнце греет Землю через космический вакуум — теплопроводность и конвекция тут невозможны. Это <b>излучение</b> электромагнитными волнами. Чёрная футболка в жаркий день нагревается сильнее белой."},
{sec:'p6',html:"Чтобы нагреть тело массой $m$ на $\\Delta T$ градусов, нужно $Q = c m \\Delta T$. Здесь $c$ — это «сколько энергии съедает 1 кг этого вещества на 1 градус». У воды $c$ самое большое — поэтому вода долго греется и долго остывает."},
{sec:'p7',html:"При сгорании 1 кг топлива выделяется $q$ Дж энергии. Полное выделение: $Q = q m$. У бензина $q$ в 4,5 раза больше, чем у дров, — поэтому литр бензина греет дольше, чем литр дров."},
{sec:'p8',html:"Пока лёд плавится, температура смеси «лёд + вода» сидит на 0 &#176;C — даже если плита продолжает греть. На графике $T(t)$ это видно как горизонтальная площадка (плато)."},
{sec:'p9',html:"Чтобы расплавить 1 кг льда (без нагрева!), нужно $\\lambda = 334$ кДж. Это столько же, сколько на нагрев той же массы воды от 0 до 80 &#176;C. Поэтому лёд — хороший «холодильник»."},
{sec:'p10',html:"Лужа высыхает даже зимой — это испарение. Чем теплее и ветренее, тем быстрее. При этом сама жидкость <b>остывает</b>, потому что её покидают самые быстрые молекулы — вот почему мокрая рука зябнет."},
{sec:'p11',html:"При кипении пузырьки пара образуются <b>внутри</b> жидкости. Чтобы перевести 1 кг воды (100 &#176;C) в пар, нужно $L = 2{,}26 \\cdot 10^6$ Дж — это в 5,4 раза больше, чем на нагрев той же воды от 0 до 100 &#176;C."},
{sec:'final1',html:"Финал главы — синтез 11 параграфов. 7 интегрированных боссов на формулы $Q = cm\\Delta T$, $\\lambda m$, $Lm$, $qm$, баланс смешивания, КПД, цепные расчёты. За победу — +50 XP и ачивка «Мастер теплоты»."}
];
const BUILDERS = {
p1: ()=>{ build_p1(); },
p2: ()=>{ build_p2(); },
p3: ()=>{ build_p3(); },
p4: ()=>{ build_p4(); },
p5: ()=>{ build_p5(); },
p6: ()=>{ build_p6(); },
p7: ()=>{ build_p7(); },
p8: ()=>{ build_p8(); },
p9: ()=>{ build_p9(); },
p10: ()=>{ build_p10(); },
p11: ()=>{ build_p11(); },
final1: ()=>{ build_final1(); }
};
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
function loadProgress(){
try{
const s=localStorage.getItem(LS_PREFIX+'_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem(LS_PREFIX+'_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem(LS_XP)||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem(LS_PREFIX+'_progress', JSON.stringify(STATE.progress));
localStorage.setItem(LS_PREFIX+'_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem(LS_XP, String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
function loadServerReadState(){
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{
if(!d||!d.progress) return;
(d.progress.read||[]).forEach(k=>{_markedRead.add(k); if((STATE.progress[k]||0)<50) STATE.progress[k]=100;});
saveProgress(); refreshProgressUI();
}).catch(()=>{});
}
function addXp(n,src){
if(!n) return;
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
saveProgress(); refreshProgressUI();
if(window.LS&&window.LS.xp) window.LS.xp.add(n, LS_PREFIX+'-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS[PARAS[0].id];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem(LS_PREFIX+'_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem(LS_PREFIX+'_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
function ipow(base, exp){ let r=1; for(let i=0;i<Math.abs(exp);i++) r*=base; return exp<0 ? 1/r : r; }
function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
/* === SVG-хелперы === */
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
const ux = (W - 2*pad) / (xmax - xmin);
const uy = (H - 2*pad) / (ymax - ymin);
const toX = v => pad + (v - xmin) * ux;
const toY = v => H - pad - (v - ymin) * uy;
let g = '';
g += '<g stroke="#e5e7eb" stroke-width="1">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
g += '<line x1="'+toX(x)+'" y1="'+pad+'" x2="'+toX(x)+'" y2="'+(H-pad)+'"/>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
g += '<line x1="'+pad+'" y1="'+toY(y)+'" x2="'+(W-pad)+'" y2="'+toY(y)+'"/>';
}
g += '</g>';
const y0 = toY(0), x0 = toX(0);
g += '<line x1="'+pad+'" y1="'+y0+'" x2="'+(W-pad)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<line x1="'+x0+'" y1="'+pad+'" x2="'+x0+'" y2="'+(H-pad)+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<text x="'+(W-pad+2)+'" y="'+(y0-4)+'" font-size="11" fill="#0f172a">x</text>';
g += '<text x="'+(x0+4)+'" y="'+(pad-2)+'" font-size="11" fill="#0f172a">y</text>';
g += '<g font-size="10" fill="#64748b">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
if (x !== 0) g += '<text x="'+(toX(x)-3)+'" y="'+(y0+12)+'">'+x+'</text>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
if (y !== 0) g += '<text x="'+(x0+4)+'" y="'+(toY(y)+3)+'">'+y+'</text>';
}
g += '<text x="'+(x0+4)+'" y="'+(y0+12)+'">0</text>';
g += '</g>';
return { content: g, toX, toY, ux, uy };
}
function plotFunc(f, xmin, xmax, toX, toY, color, N){
N = N || 200;
let d = '';
let prevValid = false;
for (let i = 0; i <= N; i++){
const x = xmin + (xmax - xmin) * i / N;
let y;
try { y = f(x); } catch(e){ y = NaN; }
if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
prevValid = true;
}
return '<path d="'+d+'" stroke="'+color+'" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>';
}
function pointWithDrop(x, fx, toX, toY, color, label){
const px = toX(x), py = toY(fx);
let s = '';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+px+'" y2="'+toY(0)+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+toX(0)+'" y2="'+py+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<circle cx="'+px+'" cy="'+py+'" r="4.5" fill="'+color+'" stroke="#fff" stroke-width="2"/>';
if (label){
s += '<text x="'+(px+8)+'" y="'+(py-8)+'" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+color+'">'+label+'</text>';
}
return s;
}
function asymptote(orientation, value, toX, toY, xmin, xmax, ymin, ymax, color){
color = color || '#94a3b8';
if (orientation === 'h'){
const y = toY(value);
return '<line x1="'+toX(xmin)+'" y1="'+y+'" x2="'+toX(xmax)+'" y2="'+y+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
} else {
const x = toX(value);
return '<line x1="'+x+'" y1="'+toY(ymin)+'" x2="'+x+'" y2="'+toY(ymax)+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
}
}
function snapToValue(value, snapPoints, tolerance){
tolerance = tolerance || 0.1;
for (const sp of snapPoints){
if (Math.abs(value - sp) < tolerance) return sp;
}
return value;
}
function rightAngleMark(V, uIn, wIn, s){
s = s || 9;
const p1 = {x: V.x + s*uIn.x, y: V.y + s*uIn.y};
const c = {x: p1.x + s*wIn.x, y: p1.y + s*wIn.y};
const p2 = {x: V.x + s*wIn.x, y: V.y + s*wIn.y};
return p1.x+','+p1.y+' '+c.x+','+c.y+' '+p2.x+','+p2.y;
}
function angleArcAuto(V, uA, uB, R){
const sA = {x: V.x + R*uA.x, y: V.y + R*uA.y};
const eB = {x: V.x + R*uB.x, y: V.y + R*uB.y};
const cross = uA.x*uB.y - uA.y*uB.x;
const sweep = cross > 0 ? 1 : 0;
return 'M'+sA.x+','+sA.y+' A'+R+','+R+' 0 0,'+sweep+' '+eB.x+','+eB.y;
}
function unitVec(p1, p2){
const dx = p2.x - p1.x, dy = p2.y - p1.y;
const len = Math.sqrt(dx*dx + dy*dy) || 1;
return {x: dx/len, y: dy/len};
}
function deg2rad(d){ return d * Math.PI / 180; }
const ICONS = {
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'
};
function secNavFor(curId){
const idx = PARAS.findIndex(p => p.id === curId);
const prev = idx > 0 ? PARAS[idx-1].id : null;
const next = idx < PARAS.length - 1 ? PARAS[idx+1].id : null;
return secNav(prev, next);
}
function secNav(prev, next){
function lbl(id){ if(!id) return ''; const p=PARAS.find(x=>x.id===id); return p?p.num:id; }
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+lbl(prev)+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+lbl(next)+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
const p = PARAS.find(x => x.id === paraId);
const labelTail = p && p.final ? 'финал' : (p ? p.num : '?');
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал \u2014 '+labelTail+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
const aId = paraId+'_done';
if(ACH_LABELS[aId]) achievement(aId);
});
}
function setupSorter(cfg){
const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed = null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
function buildStub(id, name, phase){
return '<div class="card" style="background:linear-gradient(135deg,var(--sec-acc-soft),var(--card));border:1.5px dashed var(--sec-acc)">'
+ '<div class="card-header"><div class="card-icon theory">'+ICONS.theory+'</div><div class="card-title">В разработке</div></div>'
+ '<div class="card-body"><p>Контент <b>'+name+'</b> будет реализован в <b>'+phase+'</b> по плану <code>PLAN_PHYSICS_8.md</code>.</p>'
+ '<p style="margin-top:8px;color:var(--muted);font-size:.9rem">Phase 0 \u2014 это каркас (skeleton). Все 4 интерактива, 3 теоретические карточки и тренажёр задач будут добавлены в волне.</p>'
+ '</div></div>';
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
return arr;
})();
function initSearch(){
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
if(!modal||!inp||!out) return;
let cur=0,rows=[];
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'\u2026':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
function close(){ modal.classList.remove('show'); }
btn&&btn.addEventListener('click',open);
modal.addEventListener('click',e=>{if(e.target===modal)close();});
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
/* ======================================================================
PHASE 1 · WAVE 1 — §1, §2
====================================================================== */
/* Глобальное состояние симуляций — чтобы можно было остановить при unmount */
const _SIMS = {};
function _killSim(key){ if(_SIMS[key] && _SIMS[key].raf){ cancelAnimationFrame(_SIMS[key].raf); _SIMS[key].raf=0; } }
function _isVisible(secId){ const el=document.getElementById('sec-'+secId); return el && el.classList.contains('active'); }
/* ======== §1 — Внутренняя энергия ======== */
function build_p1(){
const box = document.getElementById('p1-body');
let h = '';
/* 3 теоретические карточки */
h += makeCard('theory', 'Что такое внутренняя энергия', '§ 1.1',
'<p>Любое тело состоит из <b>молекул</b>. Молекулы непрерывно движутся (имеют кинетическую энергию $E_k$) и взаимодействуют друг с другом (имеют потенциальную энергию $E_p$).</p>'
+'<p><b>Внутренняя энергия</b> $U$ тела — это сумма $E_k$ хаотического движения всех молекул и $E_p$ их взаимодействия:</p>'
+'<p style="text-align:center;margin:8px 0">$U = \\sum E_k\\,(\\text{молекул}) + \\sum E_p\\,(\\text{взаимодействия})$</p>'
+'<p>В отличие от механической энергии, $U$ <b>не зависит</b> от того, движется ли тело и на какой оно высоте.</p>'
);
h += makeCard('rule', 'От чего зависит $U$', '§ 1.2',
'<p>Внутренняя энергия тела <b>растёт</b>, если:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>повышается температура $T$ (молекулы движутся быстрее);</li>'
+'<li>увеличивается масса $m$ (становится больше молекул);</li>'
+'<li>меняется агрегатное состояние: лёд $\\to$ вода $\\to$ пар (рвутся связи между молекулами).</li>'
+'</ul>'
+'<p style="margin-top:6px">$U$ <b>не зависит</b> от того, движется ли тело как целое, и от его высоты над поверхностью земли.</p>'
);
h += makeCard('example', 'Сравнение', '§ 1.3',
'<p>В стакане 200 г воды при $t = 20$ &#176;C. Сравним $U$ в трёх ситуациях:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li>стакан стоит на столе;</li>'
+'<li>стакан подняли на полку высотой 1,5 м;</li>'
+'<li>стакан несут в поезде со скоростью 60 км/ч.</li>'
+'</ol>'
+'<p>Во всех трёх случаях $U$ <b>одинакова</b>, потому что $T$, $m$ и агрегатное состояние воды не изменились.</p>'
);
/* IV1 — Симуляция «Холодный vs горячий газ» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Холодный и горячий газ</div></div>'
+'<div class="wg-help">Двигайте slider\'ы $T_1$ и $T_2$ — наблюдайте, как меняется скорость молекул. Чем выше $T$, тем быстрее они движутся и тем больше внутренняя энергия $U$.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$T_1$, &#176;C: <b id="p1-t1v">20</b><input type="range" id="p1-t1" min="-100" max="500" step="5" value="20"></label>'
+'<label>$T_2$, &#176;C: <b id="p1-t2v">300</b><input type="range" id="p1-t2" min="-100" max="500" step="5" value="300"></label>'
+'</div>'
+'<svg id="p1-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Левый: $T_1 = $ <b id="p1-t1k">293</b> К,&nbsp; ср. скорость молекул <b id="p1-v1">100</b>%</span>'
+'<span>Правый: $T_2 = $ <b id="p1-t2k">573</b> К,&nbsp; ср. скорость молекул <b id="p1-v2">140</b>%</span>'
+'<span style="font-size:.84rem;color:var(--muted)">У какого тела больше $U$? &mdash; у того, где молекулы быстрее.</span>'
+'</div>'
+'</div>';
/* IV2 — Викторина «Где больше U?» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">У какого тела больше $U$?</div></div>'
+'<div class="wg-help">Сравните две ситуации и выберите ту, у которой <b>больше</b> внутренняя энергия, или «одинаково», если они равны.</div>'
+'<div id="p1-quiz"></div>'
+'<div class="actions"><button class="btn" id="p1-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p1-quiz-r">1</b> / 6</span><span>Правильно: <b id="p1-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD «Зависит / Не зависит» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">От чего зависит $U$?</div></div>'
+'<div class="wg-help">Перетащите чипы в нужную колонку. <b>Подсказка:</b> $U$ — это о молекулах, а не о теле как целом.</div>'
+'<div id="p1-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Зависит</h5><div class="drop-items" data-cat="dep"></div></div>'
+'<div class="drop-box"><h5>Не зависит</h5><div class="drop-items" data-cat="indep"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p1-dnd-check">Проверить</button><button class="btn" id="p1-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p1-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ тренажёр */
h += '<div class="wg" id="p1-mcq-wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">Ответьте на 6 вопросов о внутренней энергии. Достаточно 4 правильных, чтобы получить +15 XP.</div>'
+'<div id="p1-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p1-mcq-i">1</b> / 6</span><span>Правильно: <b id="p1-mcq-ok">0</b></span></div>'
+'</div>';
/* IV5 — Расчётные задачи (auto-injected) */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-5</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.</div>'
+'<div id="p1-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p1-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p1-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — Drag thermometer (Phase 1 flagship interactive) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Перетащи термометр</div></div>'
+'<div class="wg-help">Перетащи термометр на одно из четырёх тел и наблюдай, как меняется его температурный отсчёт. Тела разной массы и при разных условиях — оцени, в каком из них больше внутренней энергии.</div>'
+'<div class="p8-sandbox" id="p1-iv6-sandbox" style="height:320px"></div>'
+'<div class="actions" style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
+'<div class="p8-readout"><span class="p8-readout-label">T</span><span class="p8-readout-value" id="p1-iv6-T">—</span><span class="p8-readout-unit">°C</span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">U отн.</span><span class="p8-readout-value" id="p1-iv6-U">—</span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p1') + readButton('p1');
renderMath(box);
wireReadBtn('p1');
_initP1_iv6();
_initp1_iv5();
_initP1_sim();
_initP1_quiz();
_initP1_dnd();
_initP1_mcq();
}
function _initP1_iv6(){
const sandbox = document.getElementById('p1-iv6-sandbox');
if (!sandbox || !window.P8Helpers || !window.P8Drag) return;
const svg = P8Helpers.svg.create(560, 320);
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.style.display = 'block';
sandbox.appendChild(svg);
/* 4 тела: имя, T (°C), относительная U */
const bodies = [
{ name:'Лёд 1 кг', cx: 95, cy: 200, T: -10, U: 14, color:'#bfdbfe' },
{ name:'Вода 1 кг', cx: 230, cy: 200, T: 20, U: 100, color:'#7dd3fc' },
{ name:'Чай 0,3 кг',cx: 365, cy: 200, T: 80, U: 80, color:'#fb923c' },
{ name:'Пар 0,5 кг',cx: 500, cy: 200, T: 110, U: 200, color:'#ef4444' }
];
bodies.forEach(b => {
const g = P8Helpers.svg.el('g', { transform: 'translate('+b.cx+','+b.cy+')' });
g.appendChild(P8Helpers.svg.el('rect', {
x: -50, y: -55, width: 100, height: 110, rx: 12,
fill: b.color, stroke: '#0f172a', 'stroke-width': 1.5, opacity: 0.88
}));
g.appendChild(P8Helpers.svg.el('text', {
x: 0, y: -68,
'font-family': "'JetBrains Mono', monospace",
'font-size': 11, 'font-weight': 700,
fill: '#0f172a', 'text-anchor': 'middle',
text: b.name
}));
g.dataset = b;
svg.appendChild(g);
});
/* Термометр (draggable group) */
let thermoX = 50, thermoY = 70;
const thermoG = P8Helpers.svg.el('g', { transform: 'translate('+thermoX+','+thermoY+')', 'class': 'p8-draggable' });
/* Drop shadow rect */
thermoG.appendChild(P8Helpers.svg.el('rect', { x: -22, y: -10, width: 44, height: 130, fill: 'transparent' }));
/* Tube */
thermoG.appendChild(P8Helpers.svg.el('rect', {
x: -5, y: 0, width: 10, height: 100, rx: 5,
fill: '#f3f4f6', stroke: '#475569', 'stroke-width': 1.5
}));
const fill = P8Helpers.svg.el('rect', {
x: -3, y: 70, width: 6, height: 30, rx: 2, fill: '#f97316'
});
thermoG.appendChild(fill);
/* Bulb */
const bulb = P8Helpers.svg.el('circle', { cx: 0, cy: 110, r: 12, fill: '#f97316', stroke: '#475569', 'stroke-width': 1.5 });
thermoG.appendChild(bulb);
thermoG.appendChild(P8Helpers.svg.el('text', {
x: 0, y: -2,
'font-family': "'Inter', sans-serif", 'font-size': 10, 'font-weight': 700,
fill: '#0f172a', 'text-anchor': 'middle', text: 'Drag'
}));
svg.appendChild(thermoG);
/* Show current readout */
const tEl = document.getElementById('p1-iv6-T');
const uEl = document.getElementById('p1-iv6-U');
function checkHit(cx, cy){
for (const b of bodies){
if (Math.abs(cx - b.cx) < 50 && Math.abs(cy - b.cy) < 55){
const tColor = P8Helpers.thermal.tempColor((b.T + 20) / 130);
fill.setAttribute('fill', tColor);
bulb.setAttribute('fill', tColor);
if (tEl) tEl.textContent = b.T;
if (uEl) uEl.textContent = b.U;
return b;
}
}
fill.setAttribute('fill', '#94a3b8');
bulb.setAttribute('fill', '#94a3b8');
if (tEl) tEl.textContent = '—';
if (uEl) uEl.textContent = '—';
return null;
}
P8Drag.attach(thermoG, {
container: svg,
onMove: (ev, pos) => {
thermoX = Math.max(20, Math.min(540, pos.x));
thermoY = Math.max(10, Math.min(200, pos.y));
thermoG.setAttribute('transform', 'translate('+thermoX+','+thermoY+')');
checkHit(thermoX, thermoY + 110);
}
});
if (window.addXp) {
setTimeout(() => addXp(5, 'p1-iv6-explore'), 12000);
}
}
function _initp1_iv5(){
const TASKS = [{"q":"Переведите температуру $t = 27\\,^\\circ$C в кельвины. ($T = t + 273$)","ans":300,"tol":1,"why":"$T = 27 + 273 = 300$ К."},{"q":"Температура воды $T = 373$ К. Чему равно $t$ в градусах Цельсия?","ans":100,"tol":1,"why":"$t = T - 273 = 373 - 273 = 100\\,^\\circ$C — кипение воды."},{"q":"У стакана воды массой $m_1 = 0{,}5$ кг и у бочки воды массой $m_2 = 50$ кг одинаковая температура. У кого внутренняя энергия больше во сколько раз?","ans":100,"tol":1,"why":"$U \\propto m$ при одинаковой $T$. $U_2/U_1 = m_2/m_1 = 50/0{,}5 = 100$."},{"q":"Тело нагрели на $\\Delta T = 30$ К. На сколько градусов Цельсия изменилась его температура?","ans":30,"tol":0.5,"why":"Шкалы Кельвина и Цельсия отличаются только сдвигом — разность температур одинакова."},{"q":"При какой температуре по шкале Цельсия средняя кинетическая энергия молекул равна нулю (абсолютный ноль)?","ans":-273,"tol":1,"why":"Абсолютный ноль $T = 0$ К соответствует $t = 0 - 273 = -273\\,^\\circ$C."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p1-tasks5'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.55"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="actions"><input type="number" step="0.001" class="tinp" id="p1-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p1-iv5-go">Ответ</button>'
+'<button class="btn" id="p1-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p1-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p1-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p1-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p1-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p1-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p1-iv5-fb');
const wh = document.getElementById('p1-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p1-tasks5-ok').textContent = ok;
wh.style.display = 'block';
} else {
fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.';
if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
}
};
document.getElementById('p1-iv5-hint').onclick = () => {
const wh = document.getElementById('p1-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p1-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p1-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p1-iv5'); }
};
}
render();
}
/* === §1 IV-1: газ-симуляция === */
function _initP1_sim(){
_killSim('p1sim');
const svg = document.getElementById('p1-sim'); if(!svg) return;
const W=460, H=200, boxW=200, boxH=160, gap=20;
const L = {x:gap, y:(H-boxH)/2, w:boxW, h:boxH};
const R = {x:W-gap-boxW, y:(H-boxH)/2, w:boxW, h:boxH};
/* частицы */
const Nl = 22, Nr = 22;
function makeParticles(N, box){
const arr = [];
for(let i=0;i<N;i++) arr.push({
x: box.x + 6 + Math.random()*(box.w-12),
y: box.y + 6 + Math.random()*(box.h-12),
vx: (Math.random()-0.5)*2,
vy: (Math.random()-0.5)*2
});
return arr;
}
const pL = makeParticles(Nl, L);
const pR = makeParticles(Nr, R);
let T1 = 20, T2 = 300;
function getSpeed(tC){
/* скорость пропорциональна sqrt(T_K). T_K = 273+t. */
const tK = Math.max(1, 273 + tC);
/* масштаб: при 20°C (293К) — 1.0 */
return Math.sqrt(tK / 293);
}
function step(parts, box, k){
for(const p of parts){
p.x += p.vx * k * 1.6;
p.y += p.vy * k * 1.6;
if(p.x < box.x+4) { p.x = box.x+4; p.vx = -p.vx; }
if(p.x > box.x+box.w-4) { p.x = box.x+box.w-4; p.vx = -p.vx; }
if(p.y < box.y+4) { p.y = box.y+4; p.vy = -p.vy; }
if(p.y > box.y+box.h-4) { p.y = box.y+box.h-4; p.vy = -p.vy; }
}
}
function render(){
if(!_isVisible('p1')) { _SIMS.p1sim.raf = requestAnimationFrame(render); return; }
const k1 = getSpeed(T1), k2 = getSpeed(T2);
step(pL, L, k1); step(pR, R, k2);
/* HSL цвет — холодный → синий, горячий → красный */
const cL = window.PHYS.tempColor(T1, -100, 500);
const cR = window.PHYS.tempColor(T2, -100, 500);
let s = '';
s += '<rect x="'+L.x+'" y="'+L.y+'" width="'+L.w+'" height="'+L.h+'" fill="white" stroke="#0f172a" stroke-width="2" rx="6"/>';
s += '<rect x="'+R.x+'" y="'+R.y+'" width="'+R.w+'" height="'+R.h+'" fill="white" stroke="#0f172a" stroke-width="2" rx="6"/>';
for(const p of pL) s += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="4" fill="'+cL+'" stroke="#0f172a" stroke-width="0.6"/>';
for(const p of pR) s += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="4" fill="'+cR+'" stroke="#0f172a" stroke-width="0.6"/>';
s += '<text x="'+(L.x+L.w/2)+'" y="'+(L.y-6)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+cL+'">T = '+T1+' &#176;C</text>';
s += '<text x="'+(R.x+R.w/2)+'" y="'+(R.y-6)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+cR+'">T = '+T2+' &#176;C</text>';
svg.innerHTML = s;
_SIMS.p1sim.raf = requestAnimationFrame(render);
}
_SIMS.p1sim = { raf: 0 };
_SIMS.p1sim.raf = requestAnimationFrame(render);
function update(){
T1 = +document.getElementById('p1-t1').value;
T2 = +document.getElementById('p1-t2').value;
document.getElementById('p1-t1v').textContent = T1;
document.getElementById('p1-t2v').textContent = T2;
document.getElementById('p1-t1k').textContent = (273 + T1);
document.getElementById('p1-t2k').textContent = (273 + T2);
const v1 = Math.round(getSpeed(T1)*100);
const v2 = Math.round(getSpeed(T2)*100);
document.getElementById('p1-v1').textContent = v1;
document.getElementById('p1-v2').textContent = v2;
}
document.getElementById('p1-t1').addEventListener('input', update);
document.getElementById('p1-t2').addEventListener('input', update);
update();
}
/* === §1 IV-2: викторина «Где больше U?» === */
function _initP1_quiz(){
const QS = [
{A:'1 кг воды при $t = 20$ &#176;C', B:'1 кг воды при $t = 80$ &#176;C', ans:'B', why:'При большей $T$ молекулы движутся быстрее $\\Rightarrow$ больше $U$.'},
{A:'1 кг льда при $0$ &#176;C', B:'1 кг воды при $0$ &#176;C', ans:'B', why:'При плавлении (0 &#176;C, лёд$\\to$вода) поглощается теплота, поэтому у воды $U$ больше.'},
{A:'2 кг железа при $100$ &#176;C', B:'1 кг железа при $100$ &#176;C', ans:'A', why:'У большей массы — больше молекул, значит больше $U$.'},
{A:'Камень на полу', B:'Тот же камень на полке (1 м выше)', ans:'C', why:'$U$ не зависит от высоты. Это потенциальная механическая энергия, а не внутренняя.'},
{A:'Покоящийся теннисный мяч', B:'Тот же мяч, летящий со скоростью 20 м/с', ans:'C', why:'$U$ не зависит от скорости тела как целого.'},
{A:'1 кг воды при $100$ &#176;C', B:'1 кг водяного пара при $100$ &#176;C', ans:'B', why:'При кипении (вода$\\to$пар) поглощается большая теплота. У пара $U$ больше.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i];
const wrap = document.getElementById('p1-quiz');
if(!wrap) return;
wrap.innerHTML =
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">'
+'<button class="btn" data-pick="A" style="text-align:left;padding:14px"><b>A.</b> '+q.A+'</button>'
+'<button class="btn" data-pick="B" style="text-align:left;padding:14px"><b>B.</b> '+q.B+'</button>'
+'</div>'
+'<div style="display:flex;justify-content:center;margin-top:8px"><button class="btn" data-pick="C" style="padding:10px 20px">$U$ одинакова</button></div>'
+'<div class="feedback" id="p1-quiz-fb"></div>';
document.getElementById('p1-quiz-r').textContent = (i+1);
document.getElementById('p1-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
const pick = btn.dataset.pick;
wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p1-quiz-fb');
if(pick === q.ans){
ok++;
fb.className = 'feedback ok'; fb.innerHTML = '&#10003; Верно. '+q.why;
addXp(3, 'p1-quiz'); bumpProgress('p1', 4);
} else {
fb.className = 'feedback fail'; fb.innerHTML = '&#10007; Не то. '+q.why;
}
document.getElementById('p1-quiz-ok').textContent = ok;
renderMath(wrap);
});
});
renderMath(wrap);
}
document.getElementById('p1-quiz-next').addEventListener('click', ()=>{
i = (i+1) % QS.length; render();
});
render();
}
/* === §1 IV-3: DnD сортировка === */
function _initP1_dnd(){
const items = [
{id:'t', cat:'dep', html:'температура $T$'},
{id:'m', cat:'dep', html:'масса $m$'},
{id:'ag', cat:'dep', html:'агрегатное состояние'},
{id:'rd', cat:'dep', html:'род вещества'},
{id:'sp', cat:'indep', html:'скорость тела как целого'},
{id:'h', cat:'indep', html:'высота над землёй'},
{id:'fm', cat:'indep', html:'форма тела'},
{id:'cl', cat:'indep', html:'цвет тела'}
];
const wg = document.querySelector('#sec-p1 .wg:nth-of-type(7)'); /* IV-3 — 7-й (3 теории + IV1+IV2+IV3) */
const dnd = setupSorter({
poolId:'p1-dnd-pool',
scopeSelector:'#sec-p1',
cats:['dep','indep'],
items: items,
columnLayout:false
});
document.getElementById('p1-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p1-dnd-fb');
let wrong = 0, total = items.length;
items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong === 0){
fb.className = 'feedback ok'; fb.innerHTML = '&#10003; Идеально! +15 XP. $U$ — это про молекулы, а не про движение тела или его положение.';
addXp(15, 'p1-dnd'); bumpProgress('p1', 20);
} else {
fb.className = 'feedback fail'; fb.innerHTML = '&#10007; Ошибок: '+wrong+' из '+total+'. Помните: $U$ зависит от $T$, $m$, агрегатного состояния и рода вещества.';
}
renderMath(fb);
});
document.getElementById('p1-dnd-reset').addEventListener('click', ()=>{
dnd.reset();
const fb = document.getElementById('p1-dnd-fb'); fb.style.display='none';
});
}
/* === §1 IV-4: MCQ-тренажёр === */
function _initP1_mcq(){
const QS = [
{q:'Из чего складывается внутренняя энергия?', opts:['Только из $E_k$ молекул','$E_k$ + $E_p$ всех молекул','Из массы и объёма','Из температуры'], ans:1, why:'$U$ — сумма кинетических энергий хаотического движения и потенциальных энергий взаимодействия молекул.'},
{q:'При нагревании тела его внутренняя энергия…', opts:['уменьшается','растёт','не меняется','становится отрицательной'], ans:1, why:'Молекулы движутся быстрее $\\Rightarrow$ $U$ растёт.'},
{q:'Зависит ли $U$ от скорости движения тела как целого?', opts:['да','нет','только для газов','только при $v &gt; 100$ м/с'], ans:1, why:'$U$ зависит только от <i>хаотического</i> движения молекул внутри тела.'},
{q:'У какого тела $U$ больше: у стакана воды на столе или у того же стакана, поднятого на 1 м?', opts:['на полу','на полке','одинакова','зависит от формы стакана'], ans:2, why:'Высота не влияет на $U$ — это про механическую $E_p$, а не про внутреннюю.'},
{q:'Изменится ли $U$, если вещество переходит из твёрдого в жидкое состояние при той же температуре?', opts:['нет','да, растёт','да, уменьшается','зависит от формы тела'], ans:1, why:'При плавлении рвутся связи между молекулами, поглощается теплота $\\Rightarrow$ $U$ растёт.'},
{q:'Какое из утверждений верно?', opts:['$U$ — это $mv^2/2$ тела','$U$ — это $mgh$ тела','$U$ — это $E_k+E_p$ молекул внутри','$U$ — это объём $\\times$ давление'], ans:2, why:'$U$ — энергия молекул, не имеет отношения к движению или положению тела как целого.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i];
const wrap = document.getElementById('p1-mcq');
if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div>';
h += '<div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt, k)=>{
h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+(String.fromCharCode(65+k))+'. '+opt+'</button>';
});
h += '</div><div class="feedback" id="p1-mcq-fb"></div>';
h += '<div class="actions"><button class="btn" id="p1-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p1-mcq-i').textContent = (i+1);
document.getElementById('p1-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k;
const fb = document.getElementById('p1-mcq-fb');
if(k === q.ans){
ok++; done++;
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why;
addXp(2,'p1-mcq'); bumpProgress('p1', 3);
} else {
done++;
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why;
}
document.getElementById('p1-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){
awarded = true;
setTimeout(()=>{
const wgFb = document.getElementById('p1-mcq-fb');
wgFb.className='feedback ok';
wgFb.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').';
addXp(15,'p1-mcq-bonus'); bumpProgress('p1', 15);
}, 600);
}
});
});
const nextBtn = document.getElementById('p1-mcq-next');
if(nextBtn) nextBtn.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §2 — Способы изменения внутренней энергии ======== */
function build_p2(){
const box = document.getElementById('p2-body');
let h = '';
h += makeCard('theory', 'Два способа', '§ 2.1',
'<p>Внутреннюю энергию тела $U$ можно изменить двумя принципиально разными способами:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Совершение работы.</b> Над телом или само тело совершает работу (трение, сжатие газа, удар, деформация). $U$ растёт.</li>'
+'<li><b>Теплопередача.</b> Энергия передаётся от более горячего тела к более холодному без совершения работы.</li>'
+'</ul>'
+'<p>Изменение внутренней энергии обозначают $\\Delta U$. Если $T$ растёт — $\\Delta U > 0$, если падает — $\\Delta U < 0$.</p>'
);
h += makeCard('rule', 'Три вида теплопередачи', '§ 2.2',
'<p>Теплопередача бывает трёх видов:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li><b>Теплопроводность</b> (§ 3) — через прямой контакт частиц. Металлическая ложка в чае нагревается.</li>'
+'<li><b>Конвекция</b> (§ 4) — потоками жидкости или газа. Радиатор греет комнату.</li>'
+'<li><b>Излучение</b> (§ 5) — электромагнитными волнами, даже через вакуум. Солнце греет Землю.</li>'
+'</ol>'
);
h += makeCard('example', 'Примеры из жизни', '§ 2.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Трёшь ладони — нагреваются. <b>Работа</b> сил трения.</li>'
+'<li>Спички в коробке встряхиваешь — нагреваются. <b>Работа</b>.</li>'
+'<li>Чай в стакане остывает. <b>Теплопередача</b> (излучение + конвекция).</li>'
+'<li>Ложка в горячем чае нагревается. <b>Теплопередача</b> (теплопроводность).</li>'
+'<li>Велосипедный насос нагревается при работе. <b>Работа</b> (сжатие газа).</li>'
+'</ul>'
);
/* IV1 — анимация: трение vs контакт */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Два способа: работа и теплопередача</div></div>'
+'<div class="wg-help">Слева — <b>работа</b>: брусок скользит по доске, трение нагревает его. Справа — <b>теплопередача</b>: горячее тело отдаёт энергию холодному при контакте, температуры выравниваются.</div>'
+'<svg id="p2-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="actions" style="margin-top:10px"><button class="btn primary" id="p2-sim-start">Запустить</button><button class="btn" id="p2-sim-reset">Сброс</button></div>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Брусок (трение): $T = $ <b id="p2-tA">20</b> &#176;C, $\\Delta U$ <b id="p2-uA">растёт</b></span>'
+'<span>Контакт: $T_{гор} = $ <b id="p2-tH">90</b> &#176;C, $T_{хол} = $ <b id="p2-tC">10</b> &#176;C</span>'
+'</div>'
+'</div>';
/* IV2 — викторина «работа или теплопередача» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Работа или теплопередача?</div></div>'
+'<div class="wg-help">Прочитай ситуацию и определи, как меняется внутренняя энергия: совершается работа или происходит теплопередача.</div>'
+'<div id="p2-quiz"></div>'
+'<div class="actions"><button class="btn" id="p2-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p2-quiz-r">1</b> / 8</span><span>Правильно: <b id="p2-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD «работа vs теплопередача» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сортировка ситуаций</div></div>'
+'<div class="wg-help">Перетащите чипы в нужную колонку. <b>Работа</b> — когда силы перемещают что-то (трение, сжатие, удар). <b>Теплопередача</b> — без работы, просто через контакт, поток или излучение.</div>'
+'<div id="p2-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Работа</h5><div class="drop-items" data-cat="work"></div></div>'
+'<div class="drop-box"><h5>Теплопередача</h5><div class="drop-items" data-cat="heat"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p2-dnd-check">Проверить</button><button class="btn" id="p2-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p2-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ тренажёр */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">Достаточно 4 правильных ответов, чтобы получить +15 XP.</div>'
+'<div id="p2-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p2-mcq-i">1</b> / 6</span><span>Правильно: <b id="p2-mcq-ok">0</b></span></div>'
+'</div>';
/* IV5 — Расчётные задачи (auto-injected) */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-5</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.</div>'
+'<div id="p2-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p2-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p2-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — Heat Conductor Bench (Phase 1.2) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Тепловая лавочка — какой материал быстрее проводит тепло?</div></div>'
+'<div class="wg-help">Перетащи один из стержней (медь, дерево, стекло, серебро) на горелку. Цветовая карта покажет, как тепло движется по стержню. Чем больше λ — тем быстрее.</div>'
+'<div class="p8-sandbox" id="p3-iv6-sandbox" style="height:300px"></div>'
+'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
+'<div class="p8-readout"><span class="p8-readout-label">Материал</span><span class="p8-readout-value" id="p3-iv6-mat">—</span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">λ</span><span class="p8-readout-value" id="p3-iv6-lam">—</span><span class="p8-readout-unit">Вт/(м·К)</span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">T дальнего конца</span><span class="p8-readout-value" id="p3-iv6-tend">—</span><span class="p8-readout-unit">°C</span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p3') + readButton('p3');
renderMath(box);
wireReadBtn('p3');
_initP3_iv6();
_initp3_iv5();
_initP3_sim();
_initP3_quiz();
_initP3_dnd();
_initP3_mcq();
}
function _initp3_iv5(){
const TASKS = [{"q":"Через стенку площадью $S = 2$ м² с разностью температур $\\Delta T = 20$ К за $t = 100$ с прошло $Q = 200$ Дж. Найдите тепловой поток (Вт): $P = Q/t$.","ans":2,"tol":0.05,"why":"$P = Q/t = 200 / 100 = 2$ Вт."},{"q":"Тепловой поток через стенку $P = 50$ Вт. Сколько джоулей теплоты пройдёт через неё за $t = 1$ час?","ans":180000,"tol":1000,"why":"$Q = P \\cdot t = 50 \\cdot 3600 = 180\\,000$ Дж."},{"q":"У какого материала теплопроводность больше при прочих равных: у $\\lambda_1 = 400$ Вт/(м·К) (медь) или $\\lambda_2 = 0{,}5$ Вт/(м·К) (вода)? Введите $\\lambda_1/\\lambda_2$.","ans":800,"tol":5,"why":"$\\lambda_1 / \\lambda_2 = 400 / 0{,}5 = 800$ — металлы намного лучше проводят тепло."},{"q":"Стенка толщиной $d_1 = 0{,}1$ м заменена на стенку толщиной $d_2 = 0{,}05$ м из того же материала. Во сколько раз вырастет тепловой поток?","ans":2,"tol":0.05,"why":"Поток $P \\propto 1/d$, поэтому $P_2/P_1 = d_1/d_2 = 0{,}1/0{,}05 = 2$."},{"q":"Площадь стенки увеличили в 3 раза. Во сколько раз вырастет тепловой поток (при той же толщине и $\\Delta T$)?","ans":3,"tol":0.05,"why":"Поток $P \\propto S$, поэтому увеличивается в 3 раза."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p3-tasks5'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.55"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="actions"><input type="number" step="0.001" class="tinp" id="p3-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p3-iv5-go">Ответ</button>'
+'<button class="btn" id="p3-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p3-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p3-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p3-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p3-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p3-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p3-iv5-fb');
const wh = document.getElementById('p3-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p3-tasks5-ok').textContent = ok;
wh.style.display = 'block';
} else {
fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.';
if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
}
};
document.getElementById('p3-iv5-hint').onclick = () => {
const wh = document.getElementById('p3-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p3-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p3-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p3-iv5'); }
};
}
render();
}
function _initP3_sim(){
_killSim('p3sim');
const svg = document.getElementById('p3-sim'); if(!svg) return;
const W=460, H=80, padX=24, barY=20, barH=44;
let bar = window.PHYS.createHeatBar({ N: 40, tHot: 200, tCold: 0, alpha: 0.5 });
const MAT_LABELS = ['вакуум','шерсть','дерево','стекло','вода','свинец','железо','алюминий','медь','серебро'];
function alphaFromSlider(v){
/* v in 1..100, exponential mapping to 0.02..0.95 */
return 0.02 + (v/100) * 0.93;
}
function matLabel(v){
const idx = Math.min(MAT_LABELS.length-1, Math.floor(v/10));
return MAT_LABELS[idx];
}
let lastT = performance.now();
function tick(now){
if(!_isVisible('p3')){ _SIMS.p3sim.raf = requestAnimationFrame(tick); return; }
const dt = Math.min(50, now - lastT); lastT = now;
bar.step(dt * 0.06);
/* render */
let s = bar.render(padX, barY, W - 2*padX, barH);
/* подписи концов */
s += '<text x="'+(padX-4)+'" y="'+(barY-4)+'" text-anchor="start" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#dc2626">горячий</text>';
s += '<text x="'+(W-padX+4)+'" y="'+(barY-4)+'" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#2563eb">холодный</text>';
svg.innerHTML = s;
_SIMS.p3sim.raf = requestAnimationFrame(tick);
}
_SIMS.p3sim = { raf: 0 };
_SIMS.p3sim.raf = requestAnimationFrame(tick);
function applyAll(){
const th = +document.getElementById('p3-thr').value;
const tc = +document.getElementById('p3-tcr').value;
const av = +document.getElementById('p3-ar').value;
document.getElementById('p3-th').textContent = th;
document.getElementById('p3-tc').textContent = tc;
document.getElementById('p3-av').textContent = matLabel(av);
bar.setTHot(th); bar.setTCold(tc); bar.alpha = alphaFromSlider(av);
}
['p3-thr','p3-tcr','p3-ar'].forEach(id => document.getElementById(id).addEventListener('input', applyAll));
document.getElementById('p3-reset').addEventListener('click', ()=>{
bar = window.PHYS.createHeatBar({ N: 40, tHot: +document.getElementById('p3-thr').value, tCold: +document.getElementById('p3-tcr').value, alpha: alphaFromSlider(+document.getElementById('p3-ar').value) });
});
applyAll();
}
function _initP3_quiz(){
const QS = [
{A:'медная ложка', B:'деревянная ложка', ans:'A', why:'Медь — отличный проводник, дерево — изолятор.'},
{A:'железная дверная ручка', B:'пластиковая дверная ручка', ans:'A', why:'Железо проводит тепло гораздо лучше пластика, поэтому железная ручка зимой кажется холоднее.'},
{A:'воздух', B:'вода', ans:'B', why:'Жидкости проводят тепло лучше газов, хотя обе плохо.'},
{A:'стекло', B:'серебро', ans:'B', why:'Серебро — лучший проводник тепла среди обычных металлов.'},
{A:'шерстяной свитер', B:'хлопковая футболка', ans:'B', why:'Хлопок проводит тепло немного лучше шерсти; шерсть лучше задерживает воздух и потому теплее (но проводит хуже).'},
{A:'вакуум', B:'воздух', ans:'B', why:'В вакууме нет частиц, поэтому теплопроводность нулевая. Воздух хоть и плохой, но проводник.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i];
const wrap = document.getElementById('p3-quiz');
if(!wrap) return;
wrap.innerHTML =
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">'
+'<button class="btn" data-pick="A" style="padding:14px;text-align:left"><b>A.</b> '+q.A+'</button>'
+'<button class="btn" data-pick="B" style="padding:14px;text-align:left"><b>B.</b> '+q.B+'</button>'
+'</div>'
+'<div class="feedback" id="p3-quiz-fb"></div>';
document.getElementById('p3-quiz-r').textContent = (i+1);
document.getElementById('p3-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return;
const pick = btn.dataset.pick;
wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p3-quiz-fb');
if(pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p3-quiz'); bumpProgress('p3', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p3-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p3-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP3_dnd(){
const items = [
{id:'cu', cat:'good', html:'медь'},
{id:'ag', cat:'good', html:'серебро'},
{id:'al', cat:'good', html:'алюминий'},
{id:'fe', cat:'good', html:'железо'},
{id:'wo', cat:'bad', html:'дерево'},
{id:'pl', cat:'bad', html:'пластик'},
{id:'sh', cat:'bad', html:'шерсть'},
{id:'va', cat:'bad', html:'вакуум'}
];
const dnd = setupSorter({ poolId:'p3-dnd-pool', scopeSelector:'#sec-p3', cats:['good','bad'], items, columnLayout:false });
document.getElementById('p3-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p3-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Металлы — отличные проводники, остальное — плохие.'; addXp(15,'p3-dnd'); bumpProgress('p3', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: металлы — это хорошие проводники.'; }
});
document.getElementById('p3-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p3-dnd-fb'); fb.style.display='none'; });
}
function _initP3_mcq(){
const QS = [
{q:'Что происходит при теплопроводности?', opts:['Переносится вещество и энергия','Переносится только энергия','Переносится только вещество','Тело меняет форму'], ans:1, why:'Молекулы передают $E_k$ соседям, оставаясь на месте.'},
{q:'В каком веществе теплопроводность нулевая?', opts:['в воде','в воздухе','в вакууме','в стекле'], ans:2, why:'В вакууме нет частиц — нечем передавать.'},
{q:'Почему шуба греет?', opts:['Сама вырабатывает тепло','Между ворсинками воздух — плохой проводник','Отражает тепло','Уменьшает излучение'], ans:1, why:'Шерсть удерживает прослойки воздуха, которые плохо проводят тепло.'},
{q:'У какого вещества теплопроводность выше?', opts:['у воды','у льда','у пара','одинакова'], ans:1, why:'У льда (твёрдое — упорядоченные молекулы передают энергию быстрее).'},
{q:'Зачем стеклопакет с воздухом между стёклами?', opts:['Для прочности','Для красоты','Воздух — плохой проводник, теряется меньше тепла','Чтобы стекло не треснуло'], ans:2, why:'Прослойка воздуха работает как теплоизолятор.'},
{q:'Какие частицы переносят тепло в металлах быстрее всего?', opts:['Атомы','Ионы','Молекулы','Свободные электроны'], ans:3, why:'Свободные электроны движутся быстро и переносят $E_k$ через весь металл.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p3-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div>';
h += '<div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt, k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+(String.fromCharCode(65+k))+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p3-mcq-fb"></div><div class="actions"><button class="btn" id="p3-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p3-mcq-i').textContent = (i+1);
document.getElementById('p3-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p3-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p3-mcq'); bumpProgress('p3', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p3-mcq-ok').textContent = ok;
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf = document.getElementById('p3-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').'; addXp(15,'p3-mcq-bonus'); bumpProgress('p3', 15); }, 600); }
});
});
const nb = document.getElementById('p3-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
}
render();
}
/* ======== §4 — Конвекция ======== */
function build_p4(){
const box = document.getElementById('p4-body');
let h = '';
h += makeCard('theory', 'Что такое конвекция', '§ 4.1',
'<p><b>Конвекция</b> — передача внутренней энергии <b>потоками</b> жидкости или газа.</p>'
+'<p>Нагретая часть жидкости (газа) <b>расширяется</b>, становится менее плотной и <b>всплывает</b> вверх. Холодная — опускается вниз. Так образуется круговорот, который и переносит тепло.</p>'
+'<p>В <b>твёрдых телах</b> конвекции нет — молекулы зафиксированы, потоков образоваться не может.</p>'
);
h += makeCard('rule', 'Условия для конвекции', '§ 4.2',
'<ul style="padding-left:20px;margin:4px 0">'
+'<li>есть текучая среда (газ или жидкость);</li>'
+'<li>есть <b>разность температур</b> сверху и снизу: нагрев должен быть снизу или охлаждение сверху;</li>'
+'<li>есть гравитация (в невесомости конвекции почти нет).</li>'
+'</ul>'
+'<p style="margin-top:6px">Если греть сверху — конвекции не будет: горячий слой и так наверху.</p>'
);
h += makeCard('example', 'Где встречается', '§ 4.3',
'<ul style="padding-left:20px;margin:4px 0">'
+'<li>Батарея греет всю комнату — горячий воздух поднимается к потолку, холодный спускается к полу.</li>'
+'<li>Кипение чайника: пузырьки и потоки воды видны глазом.</li>'
+'<li>Ветры в атмосфере: тёплый экватор и холодные полюса.</li>'
+'<li>Течения в океане: тёплый Гольфстрим греет Европу.</li>'
+'</ul>'
);
/* IV1 — анимация конвекции */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Конвекция в сосуде</div></div>'
+'<div class="wg-help">Включи / выключи нагреватель снизу. Тёплая жидкость <b>поднимается</b> по центру, холодная <b>опускается</b> по краям.</div>'
+'<svg id="p4-sim" viewBox="0 0 460 240" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="actions" style="margin-top:8px"><button class="btn primary" id="p4-on">Включить нагрев</button><button class="btn" id="p4-reset">Сброс</button></div>'
+'</div>';
/* IV2 — викторина «где конвекция?» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Возможна ли тут конвекция?</div></div>'
+'<div class="wg-help">Назови среду или ситуацию — определи, бывает ли в ней конвекция.</div>'
+'<div id="p4-quiz"></div>'
+'<div class="actions"><button class="btn" id="p4-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p4-quiz-r">1</b> / 6</span><span>Правильно: <b id="p4-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Где конвекция, а где — нет?</div></div>'
+'<div class="wg-help">Перетащи ситуации в нужную колонку.</div>'
+'<div id="p4-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Конвекция возможна</h5><div class="drop-items" data-cat="yes"></div></div>'
+'<div class="drop-box"><h5>Конвекция невозможна</h5><div class="drop-items" data-cat="no"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p4-dnd-check">Проверить</button><button class="btn" id="p4-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p4-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных ответа — +15 XP.</div>'
+'<div id="p4-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p4-mcq-i">1</b> / 6</span><span>Правильно: <b id="p4-mcq-ok">0</b></span></div>'
+'</div>';
/* IV5 — Расчётные задачи (auto-injected) */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-5</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.</div>'
+'<div id="p4-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p4-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p4-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — Heat Mixer (Phase 1.2) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Смесь двух жидкостей — рассчитай конечную T</div></div>'
+'<div class="wg-help">Перетащи ёмкости друг к другу — они смешаются. Масса и начальная температура каждой — на скрубберах ниже. Конечная температура считается по уравнению теплового баланса $c m_1 (T_1 - T) = c m_2 (T - T_2)$.</div>'
+'<div class="p8-sandbox" id="p6-iv6-sandbox" style="height:240px"></div>'
+'<div style="margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<div class="p8-scrubber"><span class="p8-scrubber-label">m₁</span><input type="range" id="p6-iv6-m1" min="0.1" max="2" step="0.1" value="0.5"><span class="p8-scrubber-value"><span id="p6-iv6-m1-val">0.5</span><span class="p8-unit">кг</span></span></div>'
+'<div class="p8-scrubber"><span class="p8-scrubber-label">T₁</span><input type="range" id="p6-iv6-t1" min="0" max="100" step="1" value="80"><span class="p8-scrubber-value"><span id="p6-iv6-t1-val">80</span><span class="p8-unit">°C</span></span></div>'
+'<div class="p8-scrubber"><span class="p8-scrubber-label">m₂</span><input type="range" id="p6-iv6-m2" min="0.1" max="2" step="0.1" value="1"><span class="p8-scrubber-value"><span id="p6-iv6-m2-val">1.0</span><span class="p8-unit">кг</span></span></div>'
+'<div class="p8-scrubber"><span class="p8-scrubber-label">T₂</span><input type="range" id="p6-iv6-t2" min="0" max="100" step="1" value="20"><span class="p8-scrubber-value"><span id="p6-iv6-t2-val">20</span><span class="p8-unit">°C</span></span></div>'
+'</div>'
+'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
+'<div class="p8-readout"><span class="p8-readout-label">T_итог</span><span class="p8-readout-value" id="p6-iv6-tf">—</span><span class="p8-readout-unit">°C</span></div>'
+'<button class="btn primary" id="p6-iv6-mix">Смешать</button>'
+'<button class="btn" id="p6-iv6-reset">Сброс</button>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p6') + readButton('p6');
renderMath(box);
wireReadBtn('p6');
_initP6_iv6();
_initP6_calc();
_initP6_mix();
_initP6_dnd();
_initP6_tasks();
}
function _initP6_calc(){
const svg = document.getElementById('p6-sim'); if(!svg) return;
function fmtSci(x){
if(Math.abs(x) < 1000) return x.toFixed(0);
const e = Math.floor(Math.log10(Math.abs(x)));
const mant = (x / Math.pow(10, e)).toFixed(2);
return mant+' &times; 10<sup>'+e+'</sup>';
}
function update(){
const c = +document.getElementById('p6-mat').value;
const m = +document.getElementById('p6-m').value;
const d = +document.getElementById('p6-d').value;
document.getElementById('p6-mv').textContent = m.toFixed(1);
document.getElementById('p6-dv').textContent = d;
document.getElementById('p6-cv').textContent = c;
const Q = c * m * d;
document.getElementById('p6-q').innerHTML = fmtSci(Q);
document.getElementById('p6-qkj').textContent = (Q/1000).toFixed(1);
/* sim: куб + термометр, цвет по итоговой T (20 + d) */
const Tfinal = 20 + d;
const col = window.PHYS.tempColor(Tfinal, -50, 150);
let s = '';
s += '<rect x="60" y="40" width="100" height="80" fill="'+col+'" stroke="#0f172a" stroke-width="2" rx="6"/>';
s += '<text x="110" y="86" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="'+(Tfinal>50?'#fff':'#0f172a')+'">m = '+m.toFixed(1)+' кг</text>';
s += '<text x="110" y="140" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">'+(d>0?'нагреваем':d<0?'охлаждаем':'без изменения')+'</text>';
/* термометр */
s += window.PHYS.thermometer(220, 30, 100, -50, 150, Tfinal);
/* подпись Q */
s += '<text x="320" y="60" font-family="JetBrains Mono,monospace" font-size="13" fill="#0f172a">Q = c &middot; m &middot; &Delta;T</text>';
s += '<text x="320" y="82" font-family="JetBrains Mono,monospace" font-size="12" fill="#475569">= '+c+' &middot; '+m.toFixed(1)+' &middot; '+d+'</text>';
s += '<text x="320" y="104" font-family="JetBrains Mono,monospace" font-size="14" font-weight="800" fill="'+(Q>=0?'#dc2626':'#2563eb')+'">Q = '+(Q/1000).toFixed(1)+' кДж</text>';
svg.innerHTML = s;
}
document.getElementById('p6-mat').addEventListener('change', update);
document.getElementById('p6-m').addEventListener('input', update);
document.getElementById('p6-d').addEventListener('input', update);
update();
}
function _initP6_mix(){
function update(){
const m1 = +document.getElementById('p6-mx1').value;
const t1 = +document.getElementById('p6-tx1').value;
const m2 = +document.getElementById('p6-mx2').value;
const t2 = +document.getElementById('p6-tx2').value;
document.getElementById('p6-mxv1').textContent = m1.toFixed(1);
document.getElementById('p6-txv1').textContent = t1;
document.getElementById('p6-mxv2').textContent = m2.toFixed(1);
document.getElementById('p6-txv2').textContent = t2;
const T = (m1*t1 + m2*t2) / (m1 + m2);
document.getElementById('p6-tres').textContent = T.toFixed(1);
}
['p6-mx1','p6-tx1','p6-mx2','p6-tx2'].forEach(id => document.getElementById(id).addEventListener('input', update));
update();
}
function _initP6_dnd(){
/* 5 веществ по возрастанию c: свинец(130) → железо(460) → стекло(840) → дерево(2400) → вода(4200) */
const items = [
{id:'pb', cat:'r1', html:'свинец (130)'},
{id:'fe', cat:'r2', html:'железо (460)'},
{id:'gl', cat:'r3', html:'стекло (840)'},
{id:'wd', cat:'r4', html:'дерево (2400)'},
{id:'wa', cat:'r5', html:'вода (4200)'}
];
const dnd = setupSorter({ poolId:'p6-dnd-pool', scopeSelector:'#sec-p6', cats:['r1','r2','r3','r4','r5'], items, columnLayout:false });
document.getElementById('p6-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p6-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. У воды $c$ рекордно велика — её сложно нагреть и сложно охладить.'; addXp(15,'p6-dnd'); bumpProgress('p6', 20); renderMath(fb); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: металлы — мало, вода — рекорд.'; }
});
document.getElementById('p6-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p6-dnd-fb'); fb.style.display='none'; });
}
function _initP6_tasks(){
const TASKS = [
{q:'Сколько энергии в кДж нужно, чтобы нагреть 2 кг воды от 20 до 100 &#176;C? ($c_{воды} = 4200$)', ans: 672, tol: 14, why:'$Q = 4200 \\cdot 2 \\cdot 80 = 672\\,000$ Дж = $672$ кДж.'},
{q:'Какую массу алюминия (в кг) можно нагреть на 50 &#176;C, имея 92 кДж? ($c = 920$)', ans: 2, tol: 0.05, why:'$m = Q/(c \\Delta T) = 92\\,000/(920 \\cdot 50) = 2$ кг.'},
{q:'На сколько градусов нагреется 0,5 кг меди при $Q = 19\\,000$ Дж? ($c = 380$)', ans: 100, tol: 2, why:'$\\Delta T = Q/(cm) = 19\\,000/(380 \\cdot 0{,}5) = 100$ К.'},
{q:'Смешали 1 кг воды при 80 &#176;C и 3 кг воды при 20 &#176;C. Какая итоговая температура (&#176;C)?', ans: 35, tol: 0.5, why:'$T = (1 \\cdot 80 + 3 \\cdot 20)/4 = 140/4 = 35$ &#176;C.'},
{q:'Какова удельная теплоёмкость (Дж/(кг·К)) вещества, если 3 кг его при $\\Delta T = 40$ К получили 48 кДж?', ans: 400, tol: 10, why:'$c = Q/(m\\Delta T) = 48\\,000/(3 \\cdot 40) = 400$. Это близко к меди.'},
{q:'Найди $Q$ (в кДж) для остывания 4 кг железа с 200 до 50 &#176;C. ($c = 460$). Запиши абсолютную величину.', ans: 276, tol: 6, why:'$|Q| = 460 \\cdot 4 \\cdot 150 = 276\\,000$ Дж = $276$ кДж.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p6-task'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p6-task-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p6-task-go">Ответ</button>'
+'<button class="btn" id="p6-task-hint">Подсказка</button>'
+'<button class="btn" id="p6-task-next">Следующая</button></div>'
+'<div class="boss-hint-txt" id="p6-task-hint-txt">'+t.why+'</div>'
+'<div class="feedback" id="p6-task-fb"></div>';
document.getElementById('p6-task-i').textContent = (i+1);
document.getElementById('p6-task-ok').textContent = ok;
document.getElementById('p6-task-go').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p6-task-inp').value || '').replace(',','.'));
const fb = document.getElementById('p6-task-fb');
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Введи число.'; return; }
done++;
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно! '+t.why; addXp(4,'p6-task'); bumpProgress('p6', 6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Правильный ответ: '+t.ans+'. '+t.why; }
document.getElementById('p6-task-ok').textContent = ok;
renderMath(wrap);
if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p6-task-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p6-task-bonus'); bumpProgress('p6', 15); }, 600); }
});
document.getElementById('p6-task-hint').addEventListener('click', ()=>{ document.getElementById('p6-task-hint-txt').classList.toggle('show'); });
document.getElementById('p6-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §7 — Q = qm ======== */
function build_p7(){
const box = document.getElementById('p7-body');
let h = '';
h += makeCard('theory', 'Горение и теплота сгорания', '§ 7.1',
'<p>При <b>горении</b> топливо реагирует с кислородом и выделяет энергию в виде тепла. Энергия эта запасена в химических связях молекул топлива.</p>'
+'<p>Количество теплоты, выделившееся при <b>полном сгорании</b> массы $m$ топлива:</p>'
+'<p style="text-align:center;margin:8px 0">$$Q = q\\,m$$</p>'
+'<p>$q$ — <b>удельная теплота сгорания</b>, Дж/кг. Это энергия, которую даёт сгорание 1 кг данного топлива.</p>'
);
h += makeCard('rule', 'Топлива и их $q$', '§ 7.2',
'<table style="width:100%;border-collapse:collapse;font-size:.92rem"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px;text-align:left">Топливо</th><th style="padding:6px;text-align:right">$q$, $\\dfrac{\\text{Дж}}{\\text{кг}}$</th></tr></thead><tbody>'
+ MAT_Q.map(m=>'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">'+m.name+'</td><td style="padding:6px;text-align:right;border-bottom:1px dashed var(--border)"><code>'+m.q.toExponential(1).replace('+','')+'</code></td></tr>').join('')
+'</tbody></table>'
+'<p style="margin-top:8px">Чем больше $q$, тем «энергоёмче» топливо. Бензин примерно в 4,5 раза мощнее дров на кг.</p>'
);
h += makeCard('example', 'КПД нагревательного устройства', '§ 7.3',
'<p>Реально <b>не вся</b> выделившаяся при сгорании энергия идёт на полезное дело (нагрев воды, движение машины). Часть теряется в виде тепла в окружающую среду.</p>'
+'<p>$$\\eta = \\dfrac{Q_{пол}}{Q_{сгор}} = \\dfrac{Q_{пол}}{q m}$$</p>'
+'<p>У хорошего котла $\\eta \\approx 80\\%$, у плохой буржуйки — 30%.</p>'
);
/* IV1 — калькулятор Q = qm */
let optsQ = '';
MAT_Q.forEach(m=>{ optsQ += '<option value="'+m.q+'">'+m.name+' ($q = '+m.q.toExponential(1).replace('+','').replace('.','{,}')+'$)</option>'; });
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Калькулятор $Q = qm$</div></div>'
+'<div class="wg-help">Выбери топливо и массу — увидь, сколько энергии выделится при его полном сгорании.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Топливо: <select id="p7-fuel" class="tinp" style="width:auto;padding:6px 10px;font-size:.92rem">'
+ MAT_Q.map(m=>'<option value="'+m.q+'">'+m.name+' (q='+m.q.toExponential(1).replace('+','')+')</option>').join('')
+'</select></label>'
+'<label>$m$, кг: <b id="p7-mv">1.0</b><input type="range" id="p7-m" min="0.1" max="20" step="0.1" value="1"></label>'
+'</div>'
+'<svg id="p7-sim" viewBox="0 0 460 140" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$q$ = <b id="p7-qv">10<sup>7</sup></b> Дж/кг</span>'
+'<span>$Q$ = <b id="p7-q">10 МДж</b> = <b id="p7-qkw">2.8</b> кВт·ч</span>'
+'<span style="font-size:.84rem;color:var(--muted)">Эквивалент: нагрев <b id="p7-eqkg">30</b> кг воды от 20 до 100 &#176;C.</span>'
+'</div>'
+'</div>';
/* IV2 — викторина «какое топливо мощнее» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Какое топливо мощнее?</div></div>'
+'<div class="wg-help">Выбери топливо с большей удельной теплотой сгорания.</div>'
+'<div id="p7-quiz"></div>'
+'<div class="actions"><button class="btn" id="p7-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p7-quiz-r">1</b> / 6</span><span>Правильно: <b id="p7-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD ранжирование топлив */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Расставь топлива по $q$ (по возрастанию)</div></div>'
+'<div class="wg-help">От самого слабого до самого мощного по теплоте сгорания.</div>'
+'<div id="p7-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-top:10px">'
+'<div class="drop-box"><h5>1. Меньше</h5><div class="drop-items" data-cat="r1"></div></div>'
+'<div class="drop-box"><h5>2</h5><div class="drop-items" data-cat="r2"></div></div>'
+'<div class="drop-box"><h5>3</h5><div class="drop-items" data-cat="r3"></div></div>'
+'<div class="drop-box"><h5>4</h5><div class="drop-items" data-cat="r4"></div></div>'
+'<div class="drop-box"><h5>5. Больше</h5><div class="drop-items" data-cat="r5"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p7-dnd-check">Проверить</button><button class="btn" id="p7-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p7-dnd-fb"></div>'
+'</div>';
/* IV4 — тренажёр расчётных задач */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">4+ верных — +15 XP. Допуск ±3 %.</div>'
+'<div id="p7-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p7-task-i">1</b> / 5</span><span>Правильно: <b id="p7-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — Phase Diagram T(t) (Phase 1.2) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">График плавления — почему T не растёт?</div></div>'
+'<div class="wg-help">Запусти нагрев льда и наблюдай за графиком T(t). При плавлении энергия идёт на разрушение кристаллической решётки — T держится постоянной (плато при 0°C). Двигай ползунок мощности нагревателя — крутизна меняется.</div>'
+'<div class="p8-sandbox" id="p8-iv6-sandbox" style="height:280px"></div>'
+'<div style="margin-top:12px;display:flex;gap:14px;flex-wrap:wrap">'
+'<div class="p8-scrubber" style="flex:1;min-width:200px"><span class="p8-scrubber-label">Мощность</span><input type="range" id="p8-iv6-pwr" min="100" max="2000" step="50" value="500"><span class="p8-scrubber-value"><span id="p8-iv6-pwr-val">500</span><span class="p8-unit">Вт</span></span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">Фаза</span><span class="p8-readout-value" id="p8-iv6-phase">лёд</span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">T</span><span class="p8-readout-value" id="p8-iv6-temp">-20</span><span class="p8-readout-unit">°C</span></div>'
+'<button class="btn primary" id="p8-iv6-play">Старт</button>'
+'<button class="btn" id="p8-iv6-reset">Сброс</button>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p8') + readButton('p8');
renderMath(box);
wireReadBtn('p8');
_initP8_iv6();
_initp8_iv5();
_initP8_graph();
_initP8_quiz();
_initP8_dnd();
_initP8_mcq();
}
function _initp8_iv5(){
const TASKS = [{"q":"Сколько теплоты (в кДж) нужно для плавления $m = 2$ кг льда при $0\\,^\\circ$C? ($\\lambda_{льда} = 330$ кДж/кг)","ans":660,"tol":5,"why":"$Q = \\lambda m = 330 \\cdot 2 = 660$ кДж."},{"q":"Какая масса (в кг) свинца расплавится, получив $Q = 50$ кДж? ($\\lambda_{св} = 25$ кДж/кг)","ans":2,"tol":0.1,"why":"$m = Q/\\lambda = 50/25 = 2$ кг."},{"q":"Найдите удельную теплоту плавления вещества (кДж/кг), если на плавление $m = 0{,}5$ кг затрачено $Q = 100$ кДж.","ans":200,"tol":5,"why":"$\\lambda = Q/m = 100/0{,}5 = 200$ кДж/кг."},{"q":"Сколько теплоты (кДж) нужно, чтобы расплавить $m = 5$ кг алюминия при $T_{пл}$? ($\\lambda_{Al} = 380$ кДж/кг)","ans":1900,"tol":20,"why":"$Q = \\lambda m = 380 \\cdot 5 = 1900$ кДж."},{"q":"Лёд массой $m = 1$ кг при $0\\,^\\circ$C сначала нагрели до $t = 0\\,^\\circ$C (не нужно тепла), затем расплавили. Сколько кДж потратили? ($\\lambda = 330$)","ans":330,"tol":3,"why":"$Q = \\lambda m = 330 \\cdot 1 = 330$ кДж — только на плавление."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p8-tasks5'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.55"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="actions"><input type="number" step="0.001" class="tinp" id="p8-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p8-iv5-go">Ответ</button>'
+'<button class="btn" id="p8-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p8-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p8-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p8-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p8-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p8-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p8-iv5-fb');
const wh = document.getElementById('p8-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p8-tasks5-ok').textContent = ok;
wh.style.display = 'block';
} else {
fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.';
if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
}
};
document.getElementById('p8-iv5-hint').onclick = () => {
const wh = document.getElementById('p8-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p8-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p8-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p8-iv5'); }
};
}
render();
}
function _initP8_graph(){
const svg = document.getElementById('p8-sim'); if(!svg) return;
function draw(){
const Tm = +document.getElementById('p8-mat').value;
const p = +document.getElementById('p8-p').value;
document.getElementById('p8-pv').textContent = ['очень кратк.','краткая','средняя','длинная','очень длин.'][p-1];
const W = 460, H = 280, pad = 36;
/* Сегменты графика T(t).
Подбор: ось T от (Tm - 60) до (Tm + 80), ось t от 0 до 10. */
const Tmin = Math.min(Tm - 60, -50);
const Tmax = Math.max(Tm + 100, 50);
const tStart = Tm - 50;
const plateauW = p * 0.8; /* 0.8..4 единиц времени */
const segs = [
{ tStart: 0, tEnd: 2, Tstart: tStart, Tend: Tm, label: 'твёрдое' },
{ tStart: 2, tEnd: 2+plateauW, Tstart: Tm, Tend: Tm, label: 'плавление' },
{ tStart: 2+plateauW,tEnd: 10, Tstart: Tm, Tend: Tm + 60, label: 'жидкость' }
];
const r = window.PHYS.phaseGraphTT(W, H, pad, segs, 10, Tmin, Tmax);
/* Подсветка плато */
const x1 = r.toX(2), x2 = r.toX(2+plateauW), yp = r.toY(Tm);
let extra = '<rect x="'+x1+'" y="'+(yp-10)+'" width="'+(x2-x1)+'" height="20" fill="#fef3c7" opacity="0.6"/>';
/* Подпись T_пл */
extra += '<line x1="'+pad+'" y1="'+yp+'" x2="'+(W-pad)+'" y2="'+yp+'" stroke="#f59e0b" stroke-width="1.2" stroke-dasharray="4 3" opacity="0.7"/>';
extra += '<text x="'+(pad+4)+'" y="'+(yp-4)+'" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#92400e">T_пл = '+Tm+' &#176;C</text>';
/* Перекрашиваем сегменты разными цветами */
let seg1 = '<line x1="'+r.toX(0)+'" y1="'+r.toY(tStart)+'" x2="'+r.toX(2)+'" y2="'+r.toY(Tm)+'" stroke="#dc2626" stroke-width="3"/>';
let seg2 = '<line x1="'+r.toX(2)+'" y1="'+yp+'" x2="'+r.toX(2+plateauW)+'" y2="'+yp+'" stroke="#f59e0b" stroke-width="3"/>';
let seg3 = '<line x1="'+r.toX(2+plateauW)+'" y1="'+yp+'" x2="'+r.toX(10)+'" y2="'+r.toY(Tm+60)+'" stroke="#2563eb" stroke-width="3"/>';
/* собираем: оси из r.svg, нужно вырезать только path и оставить оси/подписи */
/* r.svg уже содержит оси и красный path; мы его перерисуем поверх */
svg.innerHTML = r.svg + extra + seg1 + seg2 + seg3;
}
document.getElementById('p8-mat').addEventListener('change', draw);
document.getElementById('p8-p').addEventListener('input', draw);
draw();
}
function _initP8_quiz(){
const QS = [
{sit:'В стакане плавающие кусочки льда + вода. Какая температура смеси?', opts:['-5 &#176;C','0 &#176;C','+5 &#176;C','+10 &#176;C'], ans:1, why:'Лёд и вода в равновесии — это точка плавления, $T = 0$ &#176;C.'},
{sit:'Нагреваем железо. Температура 1539 &#176;C, на плите плато. Что происходит?', opts:['Греется как обычно','Плавится','Остывает','Кипит'], ans:1, why:'1539 &#176;C — это $T_{пл}$ железа.'},
{sit:'У стекла НЕТ чёткой температуры плавления. Что это значит?', opts:['Оно никогда не плавится','Это аморфное вещество','Оно газ','Оно ядовито'], ans:1, why:'Стекло — аморфное тело, нет дальнего порядка, плавится плавно.'},
{sit:'Свинец плавится при 327 &#176;C. При 320 &#176;C он …', opts:['твёрдый','плавится','жидкий','газ'], ans:0, why:'320 &lt; 327, значит ещё твёрдый.'},
{sit:'Жидкая ртуть остывает с 0 &#176;C до -50 &#176;C. Где плато на графике?', opts:['нет плато','на -50','на -39','на 0'], ans:2, why:'$T_{пл}$ ртути $= -39$ &#176;C — там плато кристаллизации.'},
{sit:'Куда уходит подведённая теплота во время плавления?', opts:['На нагрев','На разрушение связей в кристалле','На испарение','На сжатие'], ans:1, why:'Энергия рвёт кристаллическую решётку, $T$ не меняется.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p8-quiz'); if(!wrap) return;
let html = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">'+q.sit+'</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ html += '<button class="btn" data-k="'+k+'" style="padding:10px 14px;text-align:left">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
html += '</div><div class="feedback" id="p8-quiz-fb"></div>';
wrap.innerHTML = html;
document.getElementById('p8-quiz-r').textContent = (i+1);
document.getElementById('p8-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p8-quiz-fb');
if(k===q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p8-quiz'); bumpProgress('p8', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p8-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p8-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP8_dnd(){
/* 5 веществ по возрастанию T_пл: ртуть(-39) → лёд(0) → свинец(327) → алюминий(660) → железо(1539) */
const items = [
{id:'hg', cat:'r1', html:'ртуть (-39)'},
{id:'ic', cat:'r2', html:'лёд (0)'},
{id:'pb', cat:'r3', html:'свинец (327)'},
{id:'al', cat:'r4', html:'алюминий (660)'},
{id:'fe', cat:'r5', html:'железо (1539)'}
];
const dnd = setupSorter({ poolId:'p8-dnd-pool', scopeSelector:'#sec-p8', cats:['r1','r2','r3','r4','r5'], items, columnLayout:false });
document.getElementById('p8-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p8-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Чем прочнее связи между атомами, тем выше $T_{пл}$.'; addXp(15,'p8-dnd'); bumpProgress('p8', 20); renderMath(fb); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. У ртути минимум, у железа максимум.'; }
});
document.getElementById('p8-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p8-dnd-fb'); fb.style.display='none'; });
}
function _initP8_mcq(){
const QS = [
{q:'Что происходит во время плавления?', opts:['$T$ растёт','$T$ не меняется','$T$ падает','зависит от вещества'], ans:1, why:'Энергия идёт на разрушение решётки, $T$ постоянна.'},
{q:'Какой график соответствует нагреву и плавлению?', opts:['прямая линия','зигзаг','рост, плато, рост','падение, плато, падение'], ans:2, why:'Плато плавления разделяет две прямые роста.'},
{q:'Аморфные тела (стекло, смола) …', opts:['не плавятся','имеют резкую $T_{пл}$','плавятся плавно без плато','состоят из жидкости'], ans:2, why:'Нет кристаллической решётки $\\Rightarrow$ нет резкого перехода.'},
{q:'Когда лёд тает в воде, температура смеси…', opts:['опускается','растёт','равна 0 &#176;C, пока тает весь лёд','зависит от количества'], ans:2, why:'Это точка плавления льда.'},
{q:'Что происходит при кристаллизации?', opts:['тепло поглощается','тепло выделяется','$T$ растёт','$T$ падает резко'], ans:1, why:'Молекулы выстраиваются в решётку и отдают связанную энергию.'},
{q:'$T_{пл}$ и $T_{кр}$ одного вещества…', opts:['$T_{пл} > T_{кр}$','$T_{пл} < T_{кр}$','равны','зависит от давления'], ans:2, why:'Это одна и та же температура — точка фазового равновесия.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p8-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p8-mcq-fb"></div><div class="actions"><button class="btn" id="p8-mcq-next">Следующий</button></div>';
wrap.innerHTML = h;
document.getElementById('p8-mcq-i').textContent = (i+1);
document.getElementById('p8-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p8-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p8-mcq'); bumpProgress('p8', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p8-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p8-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p8-mcq-bonus'); bumpProgress('p8', 15); }, 600); }
});
});
const nb = document.getElementById('p8-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §9 — Q = λm ======== */
function build_p9(){
const box = document.getElementById('p9-body');
let h = '';
h += makeCard('theory', 'Удельная теплота плавления', '§ 9.1',
'<p>Чтобы расплавить массу $m$ кристаллического вещества при $T = T_{пл}$, нужно подвести количество теплоты:</p>'
+'<p style="text-align:center;margin:8px 0">$$Q = \\lambda\\,m$$</p>'
+'<p>$\\lambda$ (лямбда) — <b>удельная теплота плавления</b>, Дж/кг.</p>'
+'<p>Это «цена входа» в жидкое состояние: сколько энергии нужно потратить, чтобы расплавить 1 кг. При кристаллизации та же $Q$ возвращается в окружающую среду.</p>'
);
h += makeCard('rule', 'Таблица $\\lambda$', '§ 9.2',
'<table style="width:100%;border-collapse:collapse;font-size:.92rem"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px;text-align:left">Вещество</th><th style="padding:6px;text-align:right">$T_{пл}$, &#176;C</th><th style="padding:6px;text-align:right">$\\lambda$, $\\dfrac{\\text{Дж}}{\\text{кг}}$</th></tr></thead><tbody>'
+ MAT_MELT.map(m=>'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">'+m.name+'</td><td style="padding:6px;text-align:right;border-bottom:1px dashed var(--border)">'+m.Tm+'</td><td style="padding:6px;text-align:right;border-bottom:1px dashed var(--border)"><code>'+m.lam.toExponential(2).replace('+','')+'</code></td></tr>').join('')
+'</tbody></table>'
+'<p style="margin-top:8px">У льда $\\lambda$ велика — поэтому весной снег тает медленно, поглощая много энергии.</p>'
);
h += makeCard('example', 'Сложная задача: «лёд → вода → пар»', '§ 9.3',
'<p>Если у нас есть кусок льда при -10 &#176;C и мы хотим довести его до 50 &#176;C, нужно три порции теплоты:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li>$Q_1 = c_{льда} \\cdot m \\cdot (0 - (-10))$ — нагрев льда до 0 &#176;C;</li>'
+'<li>$Q_2 = \\lambda \\cdot m$ — плавление льда при 0 &#176;C;</li>'
+'<li>$Q_3 = c_{воды} \\cdot m \\cdot (50 - 0)$ — нагрев воды до 50 &#176;C.</li>'
+'</ol>'
+'<p>Всего: $Q = Q_1 + Q_2 + Q_3$.</p>'
);
/* IV1 — калькулятор Q = λm с визуалом */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Калькулятор $Q = \\lambda m$</div></div>'
+'<div class="wg-help">Выбери вещество и массу — увидь, сколько теплоты нужно для плавления.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Вещество: <select id="p9-mat" class="tinp" style="width:auto;padding:6px 10px;font-size:.92rem">'
+ MAT_MELT.map(m=>'<option value="'+m.lam+'" data-name="'+m.name+'" data-tm="'+m.Tm+'">'+m.name+' ($\\lambda='+m.lam.toExponential(1).replace('+','')+'$)</option>').join('')
+'</select></label>'
+'<label>$m$, кг: <b id="p9-mv">1.0</b><input type="range" id="p9-m" min="0.1" max="10" step="0.1" value="1"></label>'
+'</div>'
+'<svg id="p9-sim" viewBox="0 0 460 140" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$\\lambda$ = <b id="p9-lv">3.34&times;10<sup>5</sup></b> Дж/кг</span>'
+'<span>$Q$ = <b id="p9-q">334 кДж</b></span>'
+'<span style="font-size:.84rem;color:var(--muted)">Это столько же, сколько на нагрев <b id="p9-eq">80</b> кг воды на 1 К.</span>'
+'</div>'
+'</div>';
/* IV2 — тренажёр «лёд → вода → пар» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Лёд → вода: расчёт по этапам</div></div>'
+'<div class="wg-help">У нас $m$ кг льда при $T_1$ &#176;C. Сколько энергии нужно, чтобы довести его до $T_2$ &#176;C (жидкой воды)?</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$m$, кг: <b id="p9-ymv">1.0</b><input type="range" id="p9-ym" min="0.1" max="5" step="0.1" value="1"></label>'
+'<label>$T_1$, &#176;C: <b id="p9-y1v">-20</b><input type="range" id="p9-y1" min="-40" max="0" step="5" value="-20"></label>'
+'<label>$T_2$, &#176;C: <b id="p9-y2v">40</b><input type="range" id="p9-y2" min="10" max="100" step="5" value="40"></label>'
+'</div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>1. Нагрев льда: $Q_1 = c_{л} m (0 - T_1)$ = <b id="p9-q1">42</b> кДж</span>'
+'<span>2. Плавление: $Q_2 = \\lambda m$ = <b id="p9-q2">334</b> кДж</span>'
+'<span>3. Нагрев воды: $Q_3 = c_{в} m (T_2 - 0)$ = <b id="p9-q3">168</b> кДж</span>'
+'<span style="margin-top:4px;font-weight:800">Итого: $Q$ = <b id="p9-qall">544</b> кДж</span>'
+'</div>'
+'</div>';
/* IV3 — DnD ранжирование λ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Расставь $\\lambda$ по возрастанию</div></div>'
+'<div class="wg-help">От самого «лёгкого в плавке» к самому «энергоёмкому».</div>'
+'<div id="p9-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-top:10px">'
+'<div class="drop-box"><h5>1. Меньше</h5><div class="drop-items" data-cat="r1"></div></div>'
+'<div class="drop-box"><h5>2</h5><div class="drop-items" data-cat="r2"></div></div>'
+'<div class="drop-box"><h5>3</h5><div class="drop-items" data-cat="r3"></div></div>'
+'<div class="drop-box"><h5>4</h5><div class="drop-items" data-cat="r4"></div></div>'
+'<div class="drop-box"><h5>5. Больше</h5><div class="drop-items" data-cat="r5"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p9-dnd-check">Проверить</button><button class="btn" id="p9-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p9-dnd-fb"></div>'
+'</div>';
/* IV4 — расчётные задачи */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 расчётных задач</div></div>'
+'<div class="wg-help">4+ верных — +15 XP. Допуск ±3 %.</div>'
+'<div id="p9-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p9-task-i">1</b> / 6</span><span>Правильно: <b id="p9-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.9) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §9</div></div>'
+'<div class="wg-help">Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.</div>'
+'<div style="padding:30px;text-align:center;color:var(--p8-muted);font-style:italic">'
+'<svg viewBox="0 0 24 24" style="width:32px;height:32px;stroke:currentColor;fill:none;stroke-width:1.5;opacity:.4"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'
+'<div style="margin-top:8px;font-size:.86rem">Phase 1.9 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p9') + readButton('p9');
renderMath(box);
wireReadBtn('p9');
_initP9_calc();
_initP9_chain();
_initP9_dnd();
_initP9_tasks();
}
function _initP9_calc(){
const svg = document.getElementById('p9-sim'); if(!svg) return;
function update(){
const sel = document.getElementById('p9-mat');
const opt = sel.options[sel.selectedIndex];
const lam = +sel.value;
const m = +document.getElementById('p9-m').value;
const name = opt.getAttribute('data-name');
const Tm = opt.getAttribute('data-tm');
document.getElementById('p9-mv').textContent = m.toFixed(1);
document.getElementById('p9-lv').innerHTML = lam.toExponential(2).replace('+','').replace('e','&times;10<sup>')+'</sup>';
const Q = lam * m;
const QkJ = Q / 1000;
document.getElementById('p9-q').textContent = QkJ.toFixed(0)+' кДж';
/* эквивалент в "кг воды на 1 K" */
const eqkg = Q / 4200;
document.getElementById('p9-eq').textContent = eqkg.toFixed(1);
/* SVG */
let s = '';
/* куб льда / металла, превращается в лужицу */
s += '<rect x="50" y="40" width="80" height="60" fill="#bfdbfe" stroke="#0f172a" stroke-width="1.8" rx="4"/>';
s += '<text x="90" y="74" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">'+name+'</text>';
s += '<text x="90" y="118" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#475569">m = '+m.toFixed(1)+' кг, T = '+Tm+' &#176;C</text>';
/* arrow */
s += window.PHYS.drawArrow(140, 70, 220, 70, '#f59e0b', 2.4, 10);
s += '<text x="180" y="58" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">Q = &lambda;m</text>';
/* жидкость */
s += '<path d="M 240 90 Q 270 65 305 65 Q 340 65 370 90 L 370 110 L 240 110 Z" fill="#60a5fa" stroke="#0f172a" stroke-width="1.6"/>';
s += '<text x="305" y="100" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">расплав '+name+'</text>';
/* подпись Q */
s += '<text x="305" y="130" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="800" fill="#dc2626">Q = '+QkJ.toFixed(0)+' кДж</text>';
svg.innerHTML = s;
}
document.getElementById('p9-mat').addEventListener('change', update);
document.getElementById('p9-m').addEventListener('input', update);
update();
}
function _initP9_chain(){
function update(){
const m = +document.getElementById('p9-ym').value;
const T1 = +document.getElementById('p9-y1').value;
const T2 = +document.getElementById('p9-y2').value;
document.getElementById('p9-ymv').textContent = m.toFixed(1);
document.getElementById('p9-y1v').textContent = T1;
document.getElementById('p9-y2v').textContent = T2;
const cIce = 2100, lam = 3.34e5, cW = 4200;
const Q1 = cIce * m * (0 - T1);
const Q2 = lam * m;
const Q3 = cW * m * (T2 - 0);
document.getElementById('p9-q1').textContent = (Q1/1000).toFixed(0);
document.getElementById('p9-q2').textContent = (Q2/1000).toFixed(0);
document.getElementById('p9-q3').textContent = (Q3/1000).toFixed(0);
document.getElementById('p9-qall').textContent = ((Q1+Q2+Q3)/1000).toFixed(0);
}
['p9-ym','p9-y1','p9-y2'].forEach(id => document.getElementById(id).addEventListener('input', update));
update();
}
function _initP9_dnd(){
/* 5 веществ по возрастанию λ:
ртуть(1.18e4) → свинец(2.5e4) → цинк(1.12e5) → железо(2.7e5) → лёд(3.34e5)
(алюминий 3.9e5 не используем) */
const items = [
{id:'hg', cat:'r1', html:'ртуть ($1{,}2\\cdot10^4$)'},
{id:'pb', cat:'r2', html:'свинец ($2{,}5\\cdot10^4$)'},
{id:'zn', cat:'r3', html:'цинк ($1{,}1\\cdot10^5$)'},
{id:'fe', cat:'r4', html:'железо ($2{,}7\\cdot10^5$)'},
{id:'ic', cat:'r5', html:'лёд ($3{,}3\\cdot10^5$)'}
];
const dnd = setupSorter({ poolId:'p9-dnd-pool', scopeSelector:'#sec-p9', cats:['r1','r2','r3','r4','r5'], items, columnLayout:false });
document.getElementById('p9-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p9-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. У льда $\\lambda$ велика — поэтому снег тает медленно.'; addXp(15,'p9-dnd'); bumpProgress('p9', 20); renderMath(fb); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: у льда $\\lambda$ больше всех в этом списке.'; renderMath(fb); }
});
document.getElementById('p9-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p9-dnd-fb'); fb.style.display='none'; });
}
function _initP9_tasks(){
const TASKS = [
{q:'Сколько энергии (в кДж) нужно, чтобы расплавить 0,5 кг льда при 0 &#176;C? ($\\lambda = 3{,}34 \\cdot 10^5$)', ans: 167, tol: 3, why:'$Q = \\lambda m = 3{,}34\\cdot10^5 \\cdot 0{,}5 = 1{,}67\\cdot10^5$ Дж = $167$ кДж.'},
{q:'Сколько кг свинца можно расплавить при $T_{пл}$, имея 25 кДж? ($\\lambda = 2{,}5 \\cdot 10^4$)', ans: 1, tol: 0.05, why:'$m = Q/\\lambda = 25\\,000 / 2{,}5\\cdot10^4 = 1$ кг.'},
{q:'Лёд массой 2 кг при -10 &#176;C довести до воды при 0 &#176;C. Сколько кДж нужно? ($c_{л}=2100$, $\\lambda=3{,}34\\cdot10^5$)', ans: 710, tol: 14, why:'$Q_1 = 2100 \\cdot 2 \\cdot 10 = 42\\,000$, $Q_2 = 3{,}34\\cdot10^5 \\cdot 2 = 668\\,000$, итого $710\\,000$ Дж $= 710$ кДж.'},
{q:'У какого вещества $\\lambda$ выше: у воды (лёд → жидкость) или у железа? Запиши 1 — если у льда, 2 — если у железа.', ans: 1, tol: 0.1, why:'$\\lambda_{льда} = 3{,}3\\cdot10^5$ > $\\lambda_{железа} = 2{,}7\\cdot10^5$.'},
{q:'Сколько кДж выделится при кристаллизации 1,5 кг олова? ($\\lambda \\approx 5{,}9 \\cdot 10^4$)', ans: 88.5, tol: 2, why:'$Q = \\lambda m = 5{,}9\\cdot10^4 \\cdot 1{,}5 = 88\\,500$ Дж = $88{,}5$ кДж.'},
{q:'Кубик льда 200 г (0 &#176;C) положили в стакан с водой и он расплавился. Сколько кДж теплоты он отнял у воды?', ans: 66.8, tol: 2, why:'$Q = \\lambda m = 3{,}34\\cdot10^5 \\cdot 0{,}2 = 66\\,800$ Дж = $66{,}8$ кДж.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p9-task'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p9-task-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p9-task-go">Ответ</button>'
+'<button class="btn" id="p9-task-hint">Подсказка</button>'
+'<button class="btn" id="p9-task-next">Следующая</button></div>'
+'<div class="boss-hint-txt" id="p9-task-hint-txt">'+t.why+'</div>'
+'<div class="feedback" id="p9-task-fb"></div>';
document.getElementById('p9-task-i').textContent = (i+1);
document.getElementById('p9-task-ok').textContent = ok;
document.getElementById('p9-task-go').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p9-task-inp').value || '').replace(',','.'));
const fb = document.getElementById('p9-task-fb');
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Введи число.'; return; }
done++;
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно! '+t.why; addXp(4,'p9-task'); bumpProgress('p9', 6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Правильный ответ: '+t.ans+'. '+t.why; }
document.getElementById('p9-task-ok').textContent = ok;
renderMath(wrap);
if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p9-task-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p9-task-bonus'); bumpProgress('p9', 15); }, 600); }
});
document.getElementById('p9-task-hint').addEventListener('click', ()=>{ document.getElementById('p9-task-hint-txt').classList.toggle('show'); });
document.getElementById('p9-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======================================================================
PHASE 1 · WAVE 5 — §10, §11, FINAL 1
====================================================================== */
/* ======== §10 — Испарение жидкостей ======== */
function build_p10(){
const box = document.getElementById('p10-body');
let h = '';
h += makeCard('theory', 'Что такое испарение', '§ 10.1',
'<p><b>Испарение</b> — переход жидкости в пар <b>с поверхности</b>. Идёт при любой температуре (даже при 0 &#176;C, даже зимой).</p>'
+'<p>Механизм: молекулы у поверхности движутся хаотически. Самые быстрые из них преодолевают притяжение остальных и улетают в воздух — становятся паром.</p>'
+'<p>Поэтому при испарении жидкость <b>остывает</b> — её покидают наиболее «горячие» молекулы, средняя $E_k$ оставшихся падает.</p>'
);
h += makeCard('rule', 'От чего зависит скорость испарения', '§ 10.2',
'<ol style="padding-left:20px;margin:6px 0">'
+'<li><b>Температура</b>: чем выше $T$, тем больше быстрых молекул, испарение интенсивнее.</li>'
+'<li><b>Площадь свободной поверхности</b>: чем больше $S$, тем больше «выходов».</li>'
+'<li><b>Движение воздуха</b> (ветер): унося пар, ветер не даёт ему вернуться обратно.</li>'
+'<li><b>Род жидкости</b>: эфир, спирт испаряются быстро; вода медленнее; ртуть — почти не испаряется.</li>'
+'</ol>'
+'<p>Обратный процесс — <b>конденсация</b> (пар $\\to$ жидкость), при ней теплота <b>выделяется</b>.</p>'
);
h += makeCard('example', 'Где встречается', '§ 10.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Пот охлаждает тело — испаряясь, он уносит тепло.</li>'
+'<li>Мокрая рука или одежда зябнет на ветру.</li>'
+'<li>Лужа на ветреной площади сохнет быстрее, чем в безветренной комнате.</li>'
+'<li>Бельё сохнет быстрее на солнце и на ветру.</li>'
+'<li>Зимой бельё, вынесенное на мороз, тоже постепенно высыхает (возгонка).</li>'
+'</ul>'
);
/* IV1 — симуляция «молекулы покидают поверхность» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Испарение с поверхности</div></div>'
+'<div class="wg-help">Меняй температуру и ветер — наблюдай, как быстро молекулы покидают жидкость и уносятся в воздух.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$T$, &#176;C: <b id="p10-tv">40</b><input type="range" id="p10-t" min="5" max="90" step="5" value="40"></label>'
+'<label>Ветер: <b id="p10-wv">умеренный</b><input type="range" id="p10-w" min="0" max="3" step="1" value="1"></label>'
+'</div>'
+'<svg id="p10-sim" viewBox="0 0 460 220" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px"><span>Испарилось молекул: <b id="p10-evap">0</b></span><span>Скорость: <b id="p10-rate">средняя</b></span></div>'
+'</div>';
/* IV2 — викторина «лужа высохнет быстрее…» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Где жидкость испарится быстрее?</div></div>'
+'<div class="wg-help">Сравни две ситуации.</div>'
+'<div id="p10-quiz"></div>'
+'<div class="actions"><button class="btn" id="p10-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p10-quiz-r">1</b> / 6</span><span>Правильно: <b id="p10-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD факторы */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Ускоряет или замедляет испарение?</div></div>'
+'<div class="wg-help">Перетащи факторы в две колонки.</div>'
+'<div id="p10-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Ускоряет</h5><div class="drop-items" data-cat="up"></div></div>'
+'<div class="drop-box"><h5>Замедляет</h5><div class="drop-items" data-cat="dn"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p10-dnd-check">Проверить</button><button class="btn" id="p10-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p10-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP.</div>'
+'<div id="p10-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p10-mcq-i">1</b> / 6</span><span>Правильно: <b id="p10-mcq-ok">0</b></span></div>'
+'</div>';
/* IV5 — Расчётные задачи (auto-injected) */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-5</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.</div>'
+'<div id="p10-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p10-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p10-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.10) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §10</div></div>'
+'<div class="wg-help">Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.</div>'
+'<div style="padding:30px;text-align:center;color:var(--p8-muted);font-style:italic">'
+'<svg viewBox="0 0 24 24" style="width:32px;height:32px;stroke:currentColor;fill:none;stroke-width:1.5;opacity:.4"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'
+'<div style="margin-top:8px;font-size:.86rem">Phase 1.10 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p10') + readButton('p10');
renderMath(box);
wireReadBtn('p10');
_initp10_iv5();
_initP10_sim();
_initP10_quiz();
_initP10_dnd();
_initP10_mcq();
}
function _initp10_iv5(){
const TASKS = [{"q":"Сколько теплоты (в кДж) нужно, чтобы испарить $m = 0{,}2$ кг воды при $100\\,^\\circ$C? ($r_{воды} = 2300$ кДж/кг)","ans":460,"tol":5,"why":"$Q = rm = 2300 \\cdot 0{,}2 = 460$ кДж."},{"q":"При испарении $m = 5$ кг этилового спирта поглощено $Q = 4500$ кДж. Найдите $r$ (кДж/кг).","ans":900,"tol":10,"why":"$r = Q/m = 4500/5 = 900$ кДж/кг."},{"q":"Какая масса (в кг) воды испарится, если ей сообщили $Q = 1150$ кДж при $100\\,^\\circ$C? ($r = 2300$)","ans":0.5,"tol":0.02,"why":"$m = Q/r = 1150/2300 = 0{,}5$ кг."},{"q":"Лужа площадью $S = 0{,}5$ м² и толщиной $d = 1$ мм испаряется. Сколько кДж нужно? ($\\rho_{воды} = 1000$ кг/м³, $r = 2300$ кДж/кг)","ans":1.15,"tol":0.05,"why":"$V = Sd = 0{,}5 \\cdot 0{,}001 = 5 \\cdot 10^{-4}$ м³, $m = \\rho V = 0{,}5$ кг $\\cdot 10^{-3} = 0{,}0005$ кг, $Q = rm = 2300 \\cdot 0{,}0005 = 1{,}15$ кДж."},{"q":"Почему пот холодит кожу? При испарении пота поглощается теплота. Если испарилось $m = 100$ г пота ($r \\approx 2400$ кДж/кг), сколько кДж унесено с кожи?","ans":240,"tol":5,"why":"$Q = rm = 2400 \\cdot 0{,}1 = 240$ кДж."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p10-tasks5'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.55"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="actions"><input type="number" step="0.001" class="tinp" id="p10-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p10-iv5-go">Ответ</button>'
+'<button class="btn" id="p10-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p10-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p10-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p10-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p10-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p10-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p10-iv5-fb');
const wh = document.getElementById('p10-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p10-tasks5-ok').textContent = ok;
wh.style.display = 'block';
} else {
fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.';
if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
}
};
document.getElementById('p10-iv5-hint').onclick = () => {
const wh = document.getElementById('p10-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p10-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p10-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p10-iv5'); }
};
}
render();
}
function _initP10_sim(){
_killSim('p10sim');
const svg = document.getElementById('p10-sim'); if(!svg) return;
const W = 460, H = 220;
/* жидкость в нижней половине, частицы вылетают вверх */
const liqY = 130;
const N = 28;
const ps = [];
for(let i=0;i<N;i++) ps.push({ x: 80+Math.random()*300, y: liqY+10+Math.random()*70, vx: 0, vy: 0, evap: false });
let T = 40, wind = 1;
let evapCount = 0;
function tick(){
if(!_isVisible('p10')){ _SIMS.p10sim.raf = requestAnimationFrame(tick); return; }
/* вероятность испарения для частиц у поверхности */
const pEvap = (T / 90) * 0.018; /* при 90°C около 1.8% за тик */
for(const p of ps){
if(p.evap){
p.x += p.vx; p.y += p.vy;
p.vx += wind * 0.04;
if(p.x > W + 10 || p.y < -10){
/* "вернуть" молекулу обратно */
p.x = 80 + Math.random()*300; p.y = liqY + 10 + Math.random()*70;
p.vx = 0; p.vy = 0; p.evap = false;
}
} else {
/* у поверхности (y близок к liqY) есть шанс испариться */
if(p.y < liqY + 14 && Math.random() < pEvap){
p.evap = true;
p.vy = -1.0 - Math.random()*0.6;
p.vx = (Math.random() - 0.4) * 0.5 + wind*0.2;
evapCount++;
} else {
/* небольшое броуновское движение */
p.x += (Math.random()-0.5)*0.6;
p.y += (Math.random()-0.5)*0.6;
if(p.x < 75) p.x = 75; if(p.x > 385) p.x = 385;
if(p.y < liqY + 6) p.y = liqY + 6;
if(p.y > 200) p.y = 200;
}
}
}
/* draw */
let s = '';
/* сосуд + жидкость */
s += '<rect x="60" y="125" width="340" height="85" fill="#bfdbfe" stroke="#0f172a" stroke-width="2" rx="4"/>';
s += '<line x1="60" y1="'+liqY+'" x2="400" y2="'+liqY+'" stroke="#1d4ed8" stroke-width="1.6"/>';
/* небо */
s += '<rect x="0" y="0" width="'+W+'" height="125" fill="#f0f9ff"/>';
/* солнце-температура */
const tCol = window.PHYS.tempColor(T, 5, 90);
s += '<circle cx="420" cy="30" r="16" fill="'+tCol+'" stroke="#0f172a" stroke-width="1.4"/>';
s += '<text x="420" y="34" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">'+T+'&#176;</text>';
/* ветер-стрелки */
if(wind > 0){
for(let i=0;i<wind;i++){
const wy = 50 + i*15;
s += window.PHYS.drawArrow(20, wy, 80, wy, '#0891b2', 1.6, 8);
}
}
/* частицы */
for(const p of ps){
const col = p.evap ? '#94a3b8' : '#3b82f6';
s += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="3.6" fill="'+col+'" stroke="#0f172a" stroke-width="0.4"/>';
}
svg.innerHTML = s;
_SIMS.p10sim.raf = requestAnimationFrame(tick);
}
_SIMS.p10sim = { raf: 0 };
_SIMS.p10sim.raf = requestAnimationFrame(tick);
function update(){
T = +document.getElementById('p10-t').value;
wind = +document.getElementById('p10-w').value;
document.getElementById('p10-tv').textContent = T;
document.getElementById('p10-wv').textContent = ['нет','слабый','умеренный','сильный'][wind];
const rate = T*0.4 + wind*5;
let rateLabel = 'низкая';
if(rate > 25) rateLabel = 'высокая';
else if(rate > 15) rateLabel = 'средняя';
document.getElementById('p10-rate').textContent = rateLabel;
document.getElementById('p10-evap').textContent = evapCount;
}
document.getElementById('p10-t').addEventListener('input', update);
document.getElementById('p10-w').addEventListener('input', update);
setInterval(()=>{ document.getElementById('p10-evap').textContent = evapCount; }, 500);
update();
}
function _initP10_quiz(){
const QS = [
{A:'лужа в безветренной комнате при 20 &#176;C', B:'лужа на ветреной площади при 20 &#176;C', ans:'B', why:'Ветер ускоряет испарение, унося пар.'},
{A:'вода в стакане', B:'та же вода, разлитая по тарелке', ans:'B', why:'Больше площадь свободной поверхности — быстрее испарение.'},
{A:'мокрая ткань на 20 &#176;C', B:'та же ткань на 5 &#176;C', ans:'A', why:'Выше $T$ — больше быстрых молекул.'},
{A:'вода', B:'эфир (при той же $T$)', ans:'B', why:'У эфира слабее связи между молекулами, он испаряется в десятки раз быстрее воды.'},
{A:'бельё в подвале', B:'бельё на солнце и ветру', ans:'B', why:'Солнечное излучение нагревает + ветер уносит пар.'},
{A:'мокрая рука на воздухе', B:'мокрая рука в воде', ans:'A', why:'В воде испарения почти нет — нет свободной поверхности «жидкость-воздух».'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p10-quiz'); if(!wrap) return;
wrap.innerHTML =
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">'
+'<button class="btn" data-pick="A" style="padding:14px;text-align:left"><b>A.</b> '+q.A+'</button>'
+'<button class="btn" data-pick="B" style="padding:14px;text-align:left"><b>B.</b> '+q.B+'</button>'
+'</div>'
+'<div class="feedback" id="p10-quiz-fb"></div>';
document.getElementById('p10-quiz-r').textContent = (i+1);
document.getElementById('p10-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p10-quiz-fb');
if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p10-quiz'); bumpProgress('p10', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p10-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p10-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP10_dnd(){
const items = [
{id:'up_t', cat:'up', html:'нагрев'},
{id:'up_s', cat:'up', html:'большая площадь'},
{id:'up_w', cat:'up', html:'ветер'},
{id:'up_e', cat:'up', html:'эфир вместо воды'},
{id:'dn_t', cat:'dn', html:'охлаждение'},
{id:'dn_s', cat:'dn', html:'узкое горлышко'},
{id:'dn_w', cat:'dn', html:'закрытая банка'},
{id:'dn_m', cat:'dn', html:'ртуть вместо воды'}
];
const dnd = setupSorter({ poolId:'p10-dnd-pool', scopeSelector:'#sec-p10', cats:['up','dn'], items, columnLayout:false });
document.getElementById('p10-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p10-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Четыре фактора: T, S, ветер, род жидкости.'; addXp(15,'p10-dnd'); bumpProgress('p10', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: ртуть и закрытая банка — это блокирует испарение.'; }
});
document.getElementById('p10-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p10-dnd-fb'); fb.style.display='none'; });
}
function _initP10_mcq(){
const QS = [
{q:'При испарении жидкость …', opts:['нагревается','остывает','не меняет $T$','становится твёрже'], ans:1, why:'Уходят самые быстрые молекулы — средняя $E_k$ остающихся падает.'},
{q:'При какой температуре идёт испарение?', opts:['только при $T_{кип}$','при любой $T$','только зимой','только под солнцем'], ans:1, why:'Испарение идёт всегда, даже у льда (возгонка).'},
{q:'Почему мокрая рука зябнет на ветру?', opts:['ветер холодный','вода уносит тепло','испарение охлаждает','оба B и C'], ans:3, why:'И вода уносит тепло, и испаряясь, охлаждает кожу.'},
{q:'Где жидкость испаряется быстрее?', opts:['в стакане','в тарелке','в бутылке','в пробирке'], ans:1, why:'В тарелке самая большая площадь свободной поверхности.'},
{q:'Что произойдёт, если над паром резко охладить?', opts:['он замёрзнет','он расширится','он конденсируется','ничего'], ans:2, why:'Конденсация — пар возвращается в жидкость, тепло выделяется.'},
{q:'Когда лужа высохнет быстрее?', opts:['ночью','днём','в тумане','в подвале'], ans:1, why:'Днём — больше тепла, обычно ветер.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p10-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p10-mcq-fb"></div><div class="actions"><button class="btn" id="p10-mcq-next">Следующий</button></div>';
wrap.innerHTML = h;
document.getElementById('p10-mcq-i').textContent = (i+1);
document.getElementById('p10-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p10-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p10-mcq'); bumpProgress('p10', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p10-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p10-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p10-mcq-bonus'); bumpProgress('p10', 15); }, 600); }
});
});
const nb = document.getElementById('p10-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §11 — Кипение, удельная теплота парообразования ======== */
function build_p11(){
const box = document.getElementById('p11-body');
let h = '';
h += makeCard('theory', 'Что такое кипение', '§ 11.1',
'<p><b>Кипение</b> — переход жидкости в пар <b>по всему объёму</b> при определённой температуре $T_{кип}$.</p>'
+'<p>В жидкости всегда есть растворённый воздух с пузырьками пара. При $T = T_{кип}$ давление пара в пузырьках становится равно атмосферному, пузырьки растут, всплывают и лопаются — это и есть кипение.</p>'
+'<p>Во время кипения температура жидкости <b>не меняется</b>, как и при плавлении.</p>'
);
h += makeCard('rule', '$Q = Lm$ — удельная теплота парообразования', '§ 11.2',
'<p>Чтобы перевести массу $m$ жидкости (при $T = T_{кип}$) в пар, нужно:</p>'
+'<p style="text-align:center;margin:8px 0">$$Q = L\\,m$$</p>'
+'<p>$L$ — удельная теплота парообразования, Дж/кг. Для воды:</p>'
+'<p style="text-align:center"><code>L = 2,26 &middot; 10<sup>6</sup> Дж/кг = 2260 кДж/кг</code></p>'
+'<p>Это огромная величина — поэтому ожог паром опаснее ожога кипятком (пар конденсируется на коже и выделяет $L$).</p>'
);
h += makeCard('example', '$T_{кип}$ зависит от давления', '§ 11.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>При нормальном давлении (1 атм $= 10^5$ Па) вода кипит при $100$ &#176;C.</li>'
+'<li>На горе $T_{кип}$ ниже (давление меньше). На Эвересте — около $70$ &#176;C.</li>'
+'<li>В скороварке давление выше — $T_{кип}$ выше ($120$ &#176;C), еда варится быстрее.</li>'
+'<li>В вакууме вода кипит при комнатной температуре.</li>'
+'</ul>'
);
/* IV1 — главный визуал: график «лёд → вода → пар» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Полный цикл «лёд → вода → пар»</div></div>'
+'<div class="wg-help">Нагреваем 1 кг льда от -20 &#176;C до пара при 100 &#176;C. График $T(t)$ имеет <b>два плато</b>: плавление и кипение.</div>'
+'<svg id="p11-sim" viewBox="0 0 460 280" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span><span style="color:#2563eb">&#9632;</span> 1. Нагрев льда</span>'
+'<span><span style="color:#f59e0b">&#9632;</span> 2. Плавление при 0 &#176;C ($\\lambda m$)</span>'
+'<span><span style="color:#dc2626">&#9632;</span> 3. Нагрев воды</span>'
+'<span><span style="color:#ef4444">&#9632;</span> 4. Кипение при 100 &#176;C ($L m$) — самое длинное плато!</span>'
+'<span><span style="color:#7c3aed">&#9632;</span> 5. Нагрев пара</span>'
+'</div>'
+'</div>';
/* IV2 — калькулятор Q = Lm */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Калькулятор $Q = Lm$</div></div>'
+'<div class="wg-help">Сколько энергии нужно, чтобы перевести жидкость в пар при $T = T_{кип}$?</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Жидкость: <select id="p11-liq" class="tinp" style="width:auto;padding:6px 10px;font-size:.92rem">'
+'<option value="2.26e6">вода ($L = 2{,}26 \\cdot 10^6$)</option>'
+'<option value="9.0e5">спирт ($L = 9{,}0 \\cdot 10^5$)</option>'
+'<option value="3.0e5">эфир ($L = 3{,}0 \\cdot 10^5$)</option>'
+'<option value="2.85e5">ртуть ($L = 2{,}85 \\cdot 10^5$)</option>'
+'</select></label>'
+'<label>$m$, кг: <b id="p11-mv">1.0</b><input type="range" id="p11-m" min="0.1" max="5" step="0.1" value="1"></label>'
+'</div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$Q$ = <b id="p11-q">2.26 МДж</b> = <b id="p11-qkw">0.63</b> кВт·ч</span>'
+'<span style="font-size:.84rem;color:var(--muted)">Эквивалент: нагрев <b id="p11-eqkg">6.7</b> кг воды от 20 до 100 &#176;C.</span>'
+'</div>'
+'</div>';
/* IV3 — DnD «фазовый переход» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Какой переход?</div></div>'
+'<div class="wg-help">Распредели процессы по типам.</div>'
+'<div id="p11-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Плавление</h5><div class="drop-items" data-cat="melt"></div></div>'
+'<div class="drop-box"><h5>Испарение / кипение</h5><div class="drop-items" data-cat="evap"></div></div>'
+'<div class="drop-box"><h5>Конденсация / кристаллизация</h5><div class="drop-items" data-cat="cond"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p11-dnd-check">Проверить</button><button class="btn" id="p11-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p11-dnd-fb"></div>'
+'</div>';
/* IV4 — расчётные задачи */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 расчётных задач</div></div>'
+'<div class="wg-help">4+ верных — +15 XP.</div>'
+'<div id="p11-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p11-task-i">1</b> / 6</span><span>Правильно: <b id="p11-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.11) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §11</div></div>'
+'<div class="wg-help">Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.</div>'
+'<div style="padding:30px;text-align:center;color:var(--p8-muted);font-style:italic">'
+'<svg viewBox="0 0 24 24" style="width:32px;height:32px;stroke:currentColor;fill:none;stroke-width:1.5;opacity:.4"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'
+'<div style="margin-top:8px;font-size:.86rem">Phase 1.11 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p11') + readButton('p11');
renderMath(box);
wireReadBtn('p11');
_initP11_graph();
_initP11_calc();
_initP11_dnd();
_initP11_tasks();
}
function _initP11_graph(){
const svg = document.getElementById('p11-sim'); if(!svg) return;
/* строим график T(t) для 1 кг воды/льда/пара
сегменты по времени:
1) -20→0 (лёд): t = m·c_ice·20 / P
2) плавление: t = m·λ / P
3) 0→100 (вода): t = m·c_w·100 / P
4) кипение: t = m·L / P
5) пар после 100, нагрев на 80 К (с pp = 2010 для пара)
Используем относительные времена. */
const m = 1;
const cIce=2100, lam=3.34e5, cW=4200, L=2.26e6, cSt=2010;
const t1 = m*cIce*20; /* 42 000 */
const t2 = m*lam; /* 334 000 */
const t3 = m*cW*100; /* 420 000 */
const t4 = m*L; /* 2 260 000 */
const t5 = m*cSt*80; /* 160 800 */
const T1 = 0, T2 = 100, T3 = 180;
const tot = t1+t2+t3+t4+t5; /* нормируем к 10 */
const sc = 10/tot;
/* points */
let cur = 0;
const segs = [
{ tStart: cur, tEnd: (cur+=t1*sc)*1, Tstart: -20, Tend: 0, col: '#2563eb', label:'лёд' },
{ tStart: (cur), tEnd: (cur+=t2*sc), Tstart: 0, Tend: 0, col: '#f59e0b', label:'плавление' },
{ tStart: (cur), tEnd: (cur+=t3*sc), Tstart: 0, Tend: 100, col: '#dc2626', label:'вода' },
{ tStart: (cur), tEnd: (cur+=t4*sc), Tstart: 100, Tend: 100, col: '#ef4444', label:'кипение' },
{ tStart: (cur), tEnd: (cur+=t5*sc), Tstart: 100, Tend: 180, col: '#7c3aed', label:'пар' }
];
const W=460, H=280, pad=36;
const r = window.PHYS.phaseGraphTT(W, H, pad, segs.map(s=>({tStart:s.tStart,tEnd:s.tEnd,Tstart:s.Tstart,Tend:s.Tend})), 10, -40, 200);
/* Раскрашиваем сегменты разными цветами поверх */
let extra = '';
/* Горизонталь 0 и 100 */
extra += '<line x1="'+pad+'" y1="'+r.toY(0)+'" x2="'+(W-pad)+'" y2="'+r.toY(0)+'" stroke="#f59e0b" stroke-width="1" stroke-dasharray="3 3" opacity="0.6"/>';
extra += '<line x1="'+pad+'" y1="'+r.toY(100)+'" x2="'+(W-pad)+'" y2="'+r.toY(100)+'" stroke="#ef4444" stroke-width="1" stroke-dasharray="3 3" opacity="0.6"/>';
/* подписи 0 и 100 */
extra += '<text x="'+(pad+4)+'" y="'+(r.toY(0)-4)+'" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#92400e">0 &#176;C (плавление)</text>';
extra += '<text x="'+(pad+4)+'" y="'+(r.toY(100)-4)+'" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#7f1d1d">100 &#176;C (кипение)</text>';
/* перерисовываем сегменты */
for(const s of segs){
extra += '<line x1="'+r.toX(s.tStart)+'" y1="'+r.toY(s.Tstart)+'" x2="'+r.toX(s.tEnd)+'" y2="'+r.toY(s.Tend)+'" stroke="'+s.col+'" stroke-width="3.5"/>';
}
svg.innerHTML = r.svg + extra;
}
function _initP11_calc(){
function update(){
const L = +document.getElementById('p11-liq').value;
const m = +document.getElementById('p11-m').value;
document.getElementById('p11-mv').textContent = m.toFixed(1);
const Q = L*m;
document.getElementById('p11-q').textContent = (Q/1e6).toFixed(2)+' МДж';
document.getElementById('p11-qkw').textContent = (Q/3.6e6).toFixed(2);
/* эквивалент: нагрев воды от 20 до 100 (ΔT=80, c=4200) */
document.getElementById('p11-eqkg').textContent = (Q/(4200*80)).toFixed(1);
}
document.getElementById('p11-liq').addEventListener('change', update);
document.getElementById('p11-m').addEventListener('input', update);
update();
}
function _initP11_dnd(){
const items = [
{id:'a', cat:'melt', html:'лёд тает в стакане'},
{id:'b', cat:'melt', html:'свинец становится жидким'},
{id:'c', cat:'evap', html:'лужа высыхает'},
{id:'d', cat:'evap', html:'кипящий чайник пускает пар'},
{id:'e', cat:'evap', html:'спирт испаряется с кожи'},
{id:'f', cat:'cond', html:'роса утром на траве'},
{id:'g', cat:'cond', html:'пар оседает на холодном стекле'},
{id:'h', cat:'cond', html:'жидкое золото застывает в слитки'}
];
const dnd = setupSorter({ poolId:'p11-dnd-pool', scopeSelector:'#sec-p11', cats:['melt','evap','cond'], items, columnLayout:false });
document.getElementById('p11-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p11-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Все фазовые переходы — это пары прямой и обратный.'; addXp(15,'p11-dnd'); bumpProgress('p11', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: роса — это пар → жидкость (конденсация).'; }
});
document.getElementById('p11-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p11-dnd-fb'); fb.style.display='none'; });
}
function _initP11_tasks(){
const TASKS = [
{q:'Сколько энергии (в МДж) нужно, чтобы превратить 0,5 кг воды (100 &#176;C) в пар? ($L = 2{,}26 \\cdot 10^6$)', ans: 1.13, tol: 0.04, why:'$Q = Lm = 2{,}26\\cdot10^6 \\cdot 0{,}5 = 1{,}13\\cdot10^6$ Дж = $1{,}13$ МДж.'},
{q:'Сколько кг воды можно превратить в пар, имея 11,3 МДж энергии?', ans: 5, tol: 0.1, why:'$m = Q/L = 1{,}13\\cdot10^7/(2{,}26\\cdot10^6) = 5$ кг.'},
{q:'$Q_1$ — нагрев 1 кг воды от 20 до 100 &#176;C. $Q_2$ — испарение этой воды. Найди отношение $Q_2/Q_1$ (целое число).', ans: 7, tol: 0.5, why:'$Q_1 = 4200 \\cdot 80 = 336$ кДж. $Q_2 = 2260$ кДж. $Q_2/Q_1 \\approx 6{,}7 \\approx 7$.'},
{q:'Спирт массой 2 кг полностью испаряется. Сколько кДж нужно? ($L = 9 \\cdot 10^5$)', ans: 1800, tol: 30, why:'$Q = 9\\cdot10^5 \\cdot 2 = 1{,}8\\cdot10^6$ Дж = $1800$ кДж.'},
{q:'При конденсации 100 г пара (100 &#176;C) сколько кДж выделится?', ans: 226, tol: 6, why:'$Q = Lm = 2{,}26\\cdot10^6 \\cdot 0{,}1 = 2{,}26\\cdot10^5$ Дж = $226$ кДж.'},
{q:'1 кг льда (-10 &#176;C) превратить полностью в пар (100 &#176;C). Сколько МДж нужно? ($c_л=2100$, $\\lambda=3{,}34\\cdot10^5$, $c_в=4200$, $L=2{,}26\\cdot10^6$)', ans: 3.04, tol: 0.06, why:'$Q_1=21\\,000$, $Q_2=334\\,000$, $Q_3=420\\,000$, $Q_4=2\\,260\\,000$. Итого $3{,}04$ МДж.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p11-task'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p11-task-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p11-task-go">Ответ</button>'
+'<button class="btn" id="p11-task-hint">Подсказка</button>'
+'<button class="btn" id="p11-task-next">Следующая</button></div>'
+'<div class="boss-hint-txt" id="p11-task-hint-txt">'+t.why+'</div>'
+'<div class="feedback" id="p11-task-fb"></div>';
document.getElementById('p11-task-i').textContent = (i+1);
document.getElementById('p11-task-ok').textContent = ok;
document.getElementById('p11-task-go').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p11-task-inp').value || '').replace(',','.'));
const fb = document.getElementById('p11-task-fb');
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Введи число.'; return; }
done++;
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно! '+t.why; addXp(4,'p11-task'); bumpProgress('p11', 6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Правильный ответ: '+t.ans+'. '+t.why; }
document.getElementById('p11-task-ok').textContent = ok;
renderMath(wrap);
if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p11-task-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p11-task-bonus'); bumpProgress('p11', 15); }, 600); }
});
document.getElementById('p11-task-hint').addEventListener('click', ()=>{ document.getElementById('p11-task-hint-txt').classList.toggle('show'); });
document.getElementById('p11-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== ФИНАЛ ГЛАВЫ 1 ======== */
function build_final1(){
const box = document.getElementById('final1-body');
let h = '';
/* Шпаргалка главы */
h += '<div class="card" style="background:linear-gradient(135deg,var(--sec-acc-soft),var(--card));border:1.5px solid var(--sec-acc)">'
+'<div class="card-header"><div class="card-icon rule">'+ICONS.rule+'</div><div class="card-title">Шпаргалка главы 1</div></div>'
+'<div class="card-body" style="display:grid;grid-template-columns:1fr 1fr;gap:14px">'
+'<div><b>Тепло</b> при изменении $T$: $Q = c m \\Delta T$<br><i>c — Дж/(кг·К). У воды 4200.</i></div>'
+'<div><b>Тепло</b> при сгорании: $Q = q m$<br><i>q — Дж/кг. У бензина 4{,}6·10⁷.</i></div>'
+'<div><b>Тепло</b> на плавление: $Q = \\lambda m$<br><i>λ — Дж/кг. У льда 3{,}34·10⁵.</i></div>'
+'<div><b>Тепло</b> на парообразование: $Q = L m$<br><i>L — Дж/кг. У воды 2{,}26·10⁶.</i></div>'
+'<div><b>Баланс</b>: $Q_{отд} = Q_{пол}$</div>'
+'<div><b>3 вида теплопередачи</b>: проводность, конвекция, излучение</div>'
+'</div></div>';
/* 7 интегрированных боссов */
const BOSSES = [
{n:1, title:'Нагрев + расчёт', q:'На сколько градусов нагреется 2 кг алюминия при подведении 92 кДж? ($c = 920$)', hint:'$\\Delta T = Q/(cm) = 92\\,000/(920 \\cdot 2) = 50$ К.', ans:50, tol:1, step:'1'},
{n:2, title:'Смешивание воды', q:'Смешали 2 кг воды при $80$ &#176;C и 3 кг воды при $20$ &#176;C. Итоговая $T$ (&#176;C)?', hint:'$T = (2\\cdot80 + 3\\cdot20)/5 = 220/5 = 44$ &#176;C.', ans:44, tol:0.5, step:'1'},
{n:3, title:'Плавление', q:'Сколько кДж нужно, чтобы расплавить 3 кг льда при 0 &#176;C? ($\\lambda = 3{,}34\\cdot10^5$)', hint:'$Q = \\lambda m = 3{,}34\\cdot10^5 \\cdot 3 = 1{,}002\\cdot10^6$ Дж = $1002$ кДж.', ans:1002, tol:10, step:'1'},
{n:4, title:'Кипение', q:'1 кг воды при 100 &#176;C полностью испарили. Сколько МДж энергии затрачено? ($L = 2{,}26\\cdot10^6$)', hint:'$Q = Lm = 2{,}26$ МДж.', ans:2.26, tol:0.05, step:'0.01'},
{n:5, title:'Цепная задача', q:'1 кг льда при -10 &#176;C довели до воды при 50 &#176;C. Сколько кДж? ($c_л=2100$, $\\lambda=3{,}34\\cdot10^5$, $c_в=4200$)', hint:'$Q_1 = 21\\,000$, $Q_2 = 334\\,000$, $Q_3 = 210\\,000$, итого $565\\,000$ Дж = $565$ кДж.', ans:565, tol:12, step:'1'},
{n:6, title:'Топливо + КПД', q:'Котёл с $\\eta = 60\\%$ сжёг 5 кг дров. Какая полезная энергия в МДж? ($q = 10^7$)', hint:'$Q_{сгор} = 5\\cdot10^7 = 50$ МДж. $Q_{пол} = 0{,}6 \\cdot 50 = 30$ МДж.', ans:30, tol:0.5, step:'1'},
{n:7, title:'Полный цикл', q:'Сколько МДж нужно, чтобы 0,5 кг льда (0 &#176;C) превратить в пар (100 &#176;C)? ($\\lambda = 3{,}34\\cdot10^5$, $c_в = 4200$, $L = 2{,}26\\cdot10^6$). Округли до сотых.', hint:'$Q_{пл}=167\\,000$, $Q_{нагр}=210\\,000$, $Q_{исп}=1\\,130\\,000$. Итого $1{,}51$ МДж.', ans:1.51, tol:0.04, step:'0.01'}
];
h += '<div class="card" style="margin-top:14px"><div class="card-header"><div class="card-icon example">'+ICONS.example+'</div><div class="card-title">Боссы главы 1</div></div><div class="card-body">'
+'<div class="boss-overall-bar" style="background:linear-gradient(135deg,rgba(15,23,42,.04),rgba(124,58,237,.04));border-radius:11px;padding:12px;display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-bottom:14px">'
+'<span style="font-weight:700">Боссов побеждено: <b id="f1-won">0</b> / 7</span>'
+'<div style="flex:1;min-width:160px;height:8px;background:rgba(0,0,0,.08);border-radius:4px;overflow:hidden">'
+'<div id="f1-bar" style="height:100%;background:linear-gradient(90deg,var(--sec-acc),var(--sec-acc-d));width:0%;transition:width .4s"></div>'
+'</div></div>'
+'<div id="f1-bosses"></div>'
+'</div></div>';
box.innerHTML = h + secNavFor('final1') + readButton('final1');
renderMath(box);
wireReadBtn('final1');
_initFinal1_bosses(BOSSES);
}
function _initFinal1_bosses(BOSSES){
const KEY = 'physics8_ch1_bosses';
function loadState(){ try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch(e){ return {}; } }
function saveState(s){ try { localStorage.setItem(KEY, JSON.stringify(s)); } catch(e){} }
function updateBar(){
const s = loadState();
let won = 0; for(const k in s) if(s[k]) won++;
document.getElementById('f1-won').textContent = won;
document.getElementById('f1-bar').style.width = Math.round(won*100/BOSSES.length)+'%';
if(won >= BOSSES.length && !STATE.achievements.has('thermal_master')){
addXp(50, 'thermal-master');
achievement('thermal_master');
}
return won;
}
function renderAll(){
const cont = document.getElementById('f1-bosses');
const state = loadState();
let html = '';
BOSSES.forEach(b=>{
const solved = state[b.n];
html += '<div class="boss-card'+(solved?' solved':'')+'" id="f1-boss-'+b.n+'" style="border:2px solid '+(solved?'#10b981':'var(--border)')+';border-radius:12px;padding:14px;margin-bottom:10px;background:var(--card)">'
+'<div style="display:flex;gap:10px;align-items:center;margin-bottom:8px"><span style="font-family:Unbounded,sans-serif;font-size:.7rem;font-weight:800;padding:3px 8px;border-radius:99px;background:var(--sec-acc-soft);color:var(--sec-acc-d);text-transform:uppercase">Босс '+b.n+'</span><span style="font-weight:700">'+b.title+'</span></div>'
+'<div style="padding:10px 12px;background:rgba(15,23,42,.04);border-radius:8px;margin-bottom:8px;font-size:.94rem;line-height:1.5">'+b.q+'</div>'
+'<div class="boss-row">'
+'<input type="number" step="'+b.step+'" class="tinp" id="f1-b'+b.n+'-inp" placeholder="число" style="width:140px"'+(solved?' value="'+b.ans+'" disabled':'')+'>'
+'<button class="btn primary" id="f1-b'+b.n+'-go"'+(solved?' disabled':'')+'>Атаковать</button>'
+'<button class="btn" id="f1-b'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="boss-hint-txt" id="f1-b'+b.n+'-ht" style="margin-top:8px;padding:9px 13px;background:rgba(245,158,11,.12);border-left:3px solid #f59e0b;border-radius:6px;font-size:.86rem;display:none;line-height:1.5">'+b.hint+'</div>'
+'<div class="feedback'+(solved?' ok':'')+'" id="f1-b'+b.n+'-fb" style="display:'+(solved?'block':'none')+'">'+(solved?'&#10003; Победа! +10 XP. Босс повержен.':'')+'</div>'
+'</div>';
});
cont.innerHTML = html;
BOSSES.forEach(b=>{
const go = document.getElementById('f1-b'+b.n+'-go');
const inp = document.getElementById('f1-b'+b.n+'-inp');
const fb = document.getElementById('f1-b'+b.n+'-fb');
const ht = document.getElementById('f1-b'+b.n+'-ht');
const hintBtn = document.getElementById('f1-b'+b.n+'-hint');
if(hintBtn) hintBtn.addEventListener('click', ()=>{ ht.style.display = ht.style.display==='block'?'none':'block'; });
if(!go || go.disabled) return;
go.addEventListener('click', ()=>{
const v = parseFloat((inp.value || '').replace(',','.'));
if(isNaN(v)){ fb.style.display='block'; fb.className='feedback fail'; fb.innerHTML='Введите число.'; return; }
if(Math.abs(v - b.ans) < b.tol){
fb.style.display='block'; fb.className='feedback ok'; fb.innerHTML='&#10003; Победа! +10 XP. '+b.hint;
go.disabled = true; inp.disabled = true;
document.getElementById('f1-boss-'+b.n).classList.add('solved');
const s = loadState();
if(!s[b.n]){ s[b.n]=true; saveState(s); addXp(10,'f1-boss-'+b.n); bumpProgress('final1', 12); }
updateBar();
renderMath(fb);
} else {
fb.style.display='block'; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Перепроверь и попробуй снова.';
}
});
inp.addEventListener('keydown', e=>{ if(e.key === 'Enter') go.click(); });
});
renderMath(cont);
updateBar();
}
renderAll();
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
</script>
<script>
/* P8 hero meter — анимированный счётчик в углу (Phase 1 thermal) */
(function(){
function init(){
const el = document.getElementById('p8-meter-val');
if (!el || !window.P8Anim) return;
const targets = [37, 100, 0, -10, 25, 80];
let i = 0;
function step(){
const from = parseInt(el.textContent) || 0;
const to = targets[i % targets.length];
P8Anim.tween({
from, to, duration: 1400, easing: 'cubicInOut',
onUpdate: v => { el.textContent = Math.round(v); },
onComplete: () => { i++; setTimeout(step, 1800); }
});
}
setTimeout(step, 1200);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script>
</body>
</html>