Files
Learn_System/frontend/textbooks/physics_8_ch1.html
T

4905 lines
350 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
.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';
function _initP11_iv6(){
const sb = document.getElementById('p11-iv6-sandbox');
if (!sb || !window.P8Helpers || !window.P8Anim) return;
const W = 560, H = 240;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.display = 'block';
sb.appendChild(canvas);
const ctx = canvas.getContext('2d');
let pressure = 1;
/* Approximate T_boil(p) — log curve */
function Tboil(p){ return Math.round(100 + 20 * Math.log(p) / Math.log(2)); }
const bubbles = [];
function spawnBubble(){
bubbles.push({ x: 100 + Math.random() * 250, y: 200, r: 3 + Math.random() * 5, vy: -0.5 - Math.random() * 1.5 });
if (bubbles.length > 30) bubbles.shift();
}
function render(){
ctx.clearRect(0, 0, W, H);
/* Pot */
ctx.strokeStyle = '#475569';
ctx.lineWidth = 3;
ctx.fillStyle = '#cbd5e1';
ctx.fillRect(70, 80, 320, 130);
ctx.strokeRect(70, 80, 320, 130);
/* Lid */
ctx.fillStyle = pressure > 1.3 ? '#475569' : '#94a3b8';
ctx.fillRect(60, 70, 340, 12);
ctx.strokeRect(60, 70, 340, 12);
/* Water */
const T = Tboil(pressure);
const waterColor = T > 100 ? '#fb923c' : '#7dd3fc';
ctx.fillStyle = waterColor;
ctx.globalAlpha = 0.7;
ctx.fillRect(75, 130, 310, 75);
ctx.globalAlpha = 1;
/* Steam if T high enough */
const intensity = (pressure - 0.5) / 2.5;
/* Bubbles */
if (Math.random() < 0.3 + intensity * 0.5) spawnBubble();
bubbles.forEach(b => {
b.y += b.vy;
ctx.fillStyle = '#fff';
ctx.globalAlpha = 0.7;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI);
ctx.fill();
ctx.globalAlpha = 1;
});
/* Filter dead bubbles */
for (let i = bubbles.length - 1; i >= 0; i--) if (bubbles[i].y < 130) bubbles.splice(i, 1);
/* Pressure gauge */
ctx.fillStyle = '#0f172a';
ctx.font = "bold 14px 'JetBrains Mono', monospace";
ctx.textAlign = 'left';
ctx.fillText('P = '+pressure.toFixed(2)+' атм', 410, 110);
ctx.fillText('T_кип = '+T+'°C', 410, 135);
/* T arrow */
ctx.strokeStyle = '#dc2626';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(410, 150);
ctx.lineTo(410, 150 - (T - 80) * 1.2);
ctx.stroke();
ctx.fillStyle = '#dc2626';
ctx.beginPath();
ctx.moveTo(410, 150 - (T - 80) * 1.2);
ctx.lineTo(405, 150 - (T - 80) * 1.2 + 8);
ctx.lineTo(415, 150 - (T - 80) * 1.2 + 8);
ctx.fill();
}
const raf = P8Anim.raf(render);
raf.start();
document.getElementById('p11-iv6-p').oninput = ev => {
pressure = +ev.target.value;
document.getElementById('p11-iv6-p-val').textContent = pressure.toFixed(2);
document.getElementById('p11-iv6-tboil').textContent = Tboil(pressure);
};
}
function _initP10_iv6(){
const sb = document.getElementById('p10-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 220);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
let T = 50, S = 0.3, W = 2;
function render(){
svg.innerHTML = '';
/* Water surface */
const surfaceW = 100 + S * 320;
const surfaceX = (560 - surfaceW) / 2;
svg.appendChild(P8Helpers.svg.el('rect', { x: surfaceX, y: 150, width: surfaceW, height: 60, fill: P8Helpers.thermal.tempColor(T/130), opacity: 0.85, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 200, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':600, fill:'#fff', 'text-anchor':'middle', text: 'T='+T+'°C, S='+S.toFixed(2)+' м²' }));
/* Evaporation rate (relative) */
const rate = (T / 100) * (0.5 + S) * (0.5 + W / 10);
const numArrows = Math.round(3 + rate * 12);
for (let i = 0; i < numArrows; i++) {
const x = surfaceX + (i + 0.5) / numArrows * surfaceW + (Math.random() - 0.5) * 8;
const len = 30 + rate * 60 + Math.random() * 20;
const skew = W * 6 * (Math.random() - 0.3);
const arrow = P8Helpers.svg.gradientArrow(svg, x, 150, x + skew, 150 - len, { colorFrom: '#7dd3fc', colorTo: '#bae6fd', width: 1.5, headSize: 7 });
if (arrow) svg.appendChild(arrow);
}
/* Wind indicator */
if (W > 1) {
svg.appendChild(P8Helpers.svg.el('text', { x: 510, y: 30, 'font-family':"'Inter',sans-serif", 'font-size':22, fill: '#64748b', text: '~'.repeat(Math.min(3, Math.round(W/2))) }));
svg.appendChild(P8Helpers.svg.el('text', { x: 480, y: 50, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill: '#64748b', text: 'ветер →' }));
}
document.getElementById('p10-iv6-rate').textContent = rate.toFixed(2);
}
document.getElementById('p10-iv6-t').oninput = ev => { T = +ev.target.value; document.getElementById('p10-iv6-t-val').textContent = T; render(); };
document.getElementById('p10-iv6-s').oninput = ev => { S = +ev.target.value; document.getElementById('p10-iv6-s-val').textContent = S.toFixed(2); render(); };
document.getElementById('p10-iv6-w').oninput = ev => { W = +ev.target.value; document.getElementById('p10-iv6-w-val').textContent = W.toFixed(1); render(); };
render();
}
function _initP9_iv6(){
const sb = document.getElementById('p9-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 200);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
const mats = {
ice: { lambda: 330, color: '#bfdbfe', name: 'Лёд' },
lead: { lambda: 25, color: '#9ca3af', name: 'Свинец' },
al: { lambda: 380, color: '#cbd5e1', name: 'Алюминий' },
iron: { lambda: 270, color: '#64748b', name: 'Железо' }
};
let mat = 'ice', mass = 1;
function render(){
svg.innerHTML = '';
const m = mats[mat];
const Q = m.lambda * mass;
/* Block of material */
const blockH = 50 + mass * 25;
svg.appendChild(P8Helpers.svg.el('rect', { x: 100, y: 100 - blockH/2, width: 100, height: blockH, fill: m.color, stroke: '#0f172a', 'stroke-width': 2, rx: 6 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 150, y: 100, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: m.name }));
svg.appendChild(P8Helpers.svg.el('text', { x: 150, y: 116, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, fill:'#0f172a', 'text-anchor':'middle', text: mass.toFixed(1)+' кг' }));
/* Arrow Q → */
const arrow = P8Helpers.svg.gradientArrow(svg, 220, 100, 360, 100, { colorFrom:'#fde047', colorTo:'#dc2626', width: 4, headSize: 16, glow: true });
svg.appendChild(arrow);
svg.appendChild(P8Helpers.svg.el('text', { x: 290, y: 88, 'font-family':"'Unbounded',sans-serif", 'font-size':13, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'Q = '+Q+' кДж' }));
/* Melted state */
svg.appendChild(P8Helpers.svg.el('rect', { x: 380, y: 130, width: 100, height: blockH * 0.55, fill: m.color, opacity: 0.7, stroke: '#0f172a', 'stroke-width': 1.5, rx: 4 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 430, y: 156, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'расплав' }));
document.getElementById('p9-iv6-q').textContent = Q;
}
document.getElementById('p9-iv6-mat').onchange = ev => { mat = ev.target.value; render(); };
document.getElementById('p9-iv6-m').oninput = ev => {
mass = +ev.target.value;
document.getElementById('p9-iv6-m-val').textContent = mass.toFixed(1);
render();
};
render();
}
function _initP7_iv6(){
const sb = document.getElementById('p7-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 220);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
const fuels = {
wood: { q: 10e6, color: '#a16207' },
coal: { q: 29e6, color: '#1e293b' },
gas: { q: 44e6, color: '#3b82f6' }
};
let activeFuel = 'wood';
let mass = 0.1;
function render(){
svg.innerHTML = '';
const f = fuels[activeFuel];
/* Vessel with water */
svg.appendChild(P8Helpers.svg.el('rect', { x: 200, y: 30, width: 160, height: 100, fill: 'rgba(125, 211, 252, .55)', stroke: '#0f172a', 'stroke-width': 2, rx: 5 }));
/* Fuel pile */
const pileH = 10 + mass * 50;
svg.appendChild(P8Helpers.svg.el('rect', { x: 240, y: 140 + (40 - pileH), width: 80, height: pileH, fill: f.color, rx: 3 }));
/* Flame */
const Q = f.q * mass;
const intensity = Math.min(1, Q / 5e6);
svg.appendChild(P8Helpers.svg.el('rect', { x: 230, y: 130 - intensity * 20, width: 100, height: 12 + intensity * 15, fill: '#dc2626', opacity: 0.85, rx: 4 }));
/* Steam if hot enough */
const cm = 4200 * 1;
const dT = Q / cm;
if (dT > 60) {
[0, 1, 2].forEach(i => {
svg.appendChild(P8Helpers.svg.el('circle', { cx: 240 + i * 40, cy: 20 - intensity * 10, r: 6 + intensity * 2, fill: '#cbd5e1', opacity: 0.7 }));
});
}
/* Readouts */
document.getElementById('p7-iv6-q').textContent = (Q / 1e6).toFixed(2);
document.getElementById('p7-iv6-dt').textContent = Math.round(dT);
}
document.querySelectorAll('#p7-iv6-sandbox ~ * [data-fuel]').forEach(btn => {
btn.onclick = ev => {
activeFuel = ev.currentTarget.dataset.fuel;
document.querySelectorAll('[data-fuel]').forEach(b => b.style.outline = '');
ev.currentTarget.style.outline = '2px solid var(--p8-brand,#7c3aed)';
render();
};
});
/* Click on palette items globally */
Array.from(document.querySelectorAll('[data-fuel]')).forEach(btn => {
btn.onclick = ev => {
activeFuel = btn.dataset.fuel;
document.querySelectorAll('[data-fuel]').forEach(b => b.style.outline = '');
btn.style.outline = '2px solid var(--p8-brand,#7c3aed)';
render();
if (window.addXp) addXp(5, 'p7-iv6-fuel-'+activeFuel);
};
});
document.getElementById('p7-iv6-m').oninput = ev => {
mass = +ev.target.value;
document.getElementById('p7-iv6-m-val').textContent = mass.toFixed(2);
render();
};
render();
}
function _initP5_iv6(){
const sb = document.getElementById('p5-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);
let lampPower = 60;
const bodies = [
{ name: 'Чёрное', absorption: 0.95, fill: '#0f172a', x: 130, T: 20 },
{ name: 'Белое', absorption: 0.20, fill: '#f1f5f9', x: 280, T: 20 },
{ name: 'Зеркало', absorption: 0.05, fill: '#cbd5e1', x: 430, T: 20 }
];
let running = false;
function render(){
svg.innerHTML = '';
/* Lamp */
svg.appendChild(P8Helpers.svg.el('rect', { x: 240, y: 8, width: 80, height: 20, fill: '#facc15', rx: 4 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 22, 'font-family':"'Unbounded',sans-serif", 'font-size':10, 'font-weight':800, fill:'#0f172a', 'text-anchor':'middle', text: lampPower+'%' }));
/* Radiation rays */
bodies.forEach(b => {
const len = lampPower * 1.2;
const opacity = lampPower / 100;
svg.appendChild(P8Helpers.svg.el('line', {
x1: 280, y1: 28, x2: b.x, y2: 130,
stroke: '#fde047', 'stroke-width': 2,
opacity: opacity, 'stroke-dasharray': '4 3'
}));
});
/* Bodies */
bodies.forEach(b => {
const g = P8Helpers.svg.el('g', { transform: 'translate('+b.x+',150)' });
g.appendChild(P8Helpers.svg.el('rect', { x:-32, y:-12, width:64, height:48, rx:5, fill: b.fill, stroke:'#0f172a', 'stroke-width':1.5 }));
g.appendChild(P8Helpers.svg.el('text', { x:0, y:-22, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: b.name }));
/* Glow when warm */
const tempNorm = Math.min(1, (b.T - 20) / 80);
if (tempNorm > 0.05) {
g.appendChild(P8Helpers.svg.el('rect', { x:-36, y:-16, width:72, height:56, rx:8, fill: 'none', stroke: P8Helpers.thermal.tempColor(tempNorm*0.5 + 0.5), 'stroke-width': 4, opacity: 0.5 + tempNorm * 0.5 }));
}
svg.appendChild(g);
/* T readout */
svg.appendChild(P8Helpers.svg.el('text', { x: b.x, y: 220, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill: P8Helpers.thermal.tempColor(tempNorm*0.5 + 0.4), 'text-anchor':'middle', text: 'T='+Math.round(b.T)+'°C' }));
});
}
const raf = P8Anim.raf(dt => {
if (!running) return;
bodies.forEach(b => {
b.T += dt * (lampPower / 100) * b.absorption * 10;
if (b.T > 120) b.T = 120;
});
render();
});
document.getElementById('p5-iv6-lamp').oninput = ev => {
lampPower = +ev.target.value;
document.getElementById('p5-iv6-lamp-val').textContent = lampPower;
render();
};
document.getElementById('p5-iv6-play').onclick = () => {
if (!running) { running = true; raf.start(); if (window.addXp) addXp(10, 'p5-iv6-rad'); }
};
document.getElementById('p5-iv6-reset').onclick = () => {
running = false; raf.stop();
bodies.forEach(b => b.T = 20);
render();
};
render();
}
function _initP4_iv6(){
const sb = document.getElementById('p4-iv6-sandbox');
if (!sb || !window.P8Helpers || !window.P8Anim) return;
const W = 560, H = 280;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.display = 'block';
sb.appendChild(canvas);
const ctx = canvas.getContext('2d');
let power = 40;
/* Particles in convection loops */
const particles = [];
const NP = 60;
for (let i = 0; i < NP; i++) {
particles.push({
x: 60 + Math.random() * (W - 120),
y: 40 + Math.random() * (H - 100),
angle: Math.random() * 2 * Math.PI,
speed: 0.3 + Math.random() * 0.4,
r: 2.5 + Math.random() * 1.5
});
}
function step(dt){
/* Clear */
ctx.clearRect(0, 0, W, H);
/* Container */
ctx.strokeStyle = '#0f172a';
ctx.lineWidth = 2;
ctx.strokeRect(40, 20, W - 80, H - 60);
/* Burner */
ctx.fillStyle = '#475569';
ctx.fillRect(180, H - 30, 200, 16);
/* Flame */
const flameH = (power / 100) * 16;
ctx.fillStyle = '#dc2626';
ctx.fillRect(200, H - 30 - flameH, 160, flameH);
/* Particles — convection loop: rise in center, fall on edges */
const cx = W / 2;
particles.forEach(p => {
const dx = p.x - cx;
const localTemp = Math.max(0, 1 - (p.y - 40) / (H - 100)); /* bottom = hot */
/* Vertical velocity: up in center (proportional to power), down on sides */
let vy = 0;
if (Math.abs(dx) < 80) {
vy = -p.speed * (power / 50) * (0.6 + localTemp);
} else {
vy = p.speed * 0.4;
}
/* Horizontal: drift inward at top, outward at bottom */
let vx = 0;
if (p.y < 60) vx = -dx * 0.005 * (power / 50);
else if (p.y > H - 70) vx = dx * 0.008 * (power / 50);
p.x += vx * dt * 60;
p.y += vy * dt * 60;
/* Constrain */
if (p.x < 50) p.x = 50;
if (p.x > W - 50) p.x = W - 50;
if (p.y < 30) p.y = 30;
if (p.y > H - 38) p.y = H - 38;
/* Color by temp (hotter = closer to bottom and rising) */
const tempVal = localTemp * (power / 100);
ctx.fillStyle = P8Helpers.thermal.tempColor(tempVal * 0.7 + 0.15);
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, 2 * Math.PI);
ctx.fill();
});
}
const raf = P8Anim.raf(dt => step(Math.min(dt, 0.05)));
document.getElementById('p4-iv6-pwr').oninput = ev => {
power = +ev.target.value;
document.getElementById('p4-iv6-pwr-val').textContent = power;
};
document.getElementById('p4-iv6-play').onclick = () => {
if (!raf.running) { raf.start(); if (window.addXp) addXp(10, 'p4-iv6-conv'); }
};
document.getElementById('p4-iv6-pause').onclick = () => raf.stop();
/* Auto-start on first view */
raf.start();
step(0);
}
function _initP2_iv6(){
const sb = document.getElementById('p2-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 240);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
const state = { comp: 0, q: 0 };
function render(){
svg.innerHTML = '';
/* Cylinder */
const compFraction = state.comp / 100;
const cylX = 80, cylY = 60, cylW = 320, cylH = 120;
const pistonX = cylX + cylW * (0.2 + compFraction * 0.5);
svg.appendChild(P8Helpers.svg.el('rect', { x: cylX, y: cylY, width: cylW, height: cylH, rx: 8, fill: 'none', stroke: '#0f172a', 'stroke-width': 2 }));
/* Gas region (compressed = brighter, hotter) */
const T = 20 + compFraction * 60 + state.q * 0.1;
const gasColor = P8Helpers.thermal.tempColor(T / 200);
svg.appendChild(P8Helpers.svg.el('rect', { x: cylX + 2, y: cylY + 2, width: pistonX - cylX - 4, height: cylH - 4, rx: 6, fill: gasColor, opacity: 0.6 }));
/* Molecules — count grows with T */
const numMol = Math.round(8 + T * 0.3);
for (let i = 0; i < numMol; i++) {
const px = cylX + 8 + Math.random() * (pistonX - cylX - 16);
const py = cylY + 8 + Math.random() * (cylH - 16);
svg.appendChild(P8Helpers.svg.el('circle', { cx: px, cy: py, r: 3, fill: '#fff', opacity: 0.7 }));
}
/* Piston */
svg.appendChild(P8Helpers.svg.el('rect', { x: pistonX - 6, y: cylY - 8, width: 12, height: cylH + 16, fill: '#475569', stroke: '#0f172a' }));
svg.appendChild(P8Helpers.svg.el('rect', { x: pistonX + 6, y: cylY + cylH/2 - 6, width: 100, height: 12, fill: '#94a3b8', stroke: '#0f172a' }));
/* Heat source if q>0 */
if (state.q > 0) {
svg.appendChild(P8Helpers.svg.el('rect', { x: cylX + 60, y: cylY + cylH + 4, width: 60, height: 12, fill: '#dc2626', rx: 4 }));
svg.appendChild(P8Helpers.svg.el('text', { x: cylX + 90, y: cylY + cylH + 32, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: 'Q = '+state.q+' Дж' }));
}
if (state.comp > 0) {
svg.appendChild(P8Helpers.svg.el('text', { x: pistonX + 56, y: cylY + cylH/2 - 14, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'A над газом' }));
}
/* Readouts */
document.getElementById('p2-iv6-t').textContent = Math.round(T);
document.getElementById('p2-iv6-u').textContent = Math.round(100 + compFraction * 60 + state.q * 0.5);
}
document.getElementById('p2-iv6-comp').oninput = ev => {
state.comp = +ev.target.value;
document.getElementById('p2-iv6-comp-val').textContent = state.comp;
render();
};
document.getElementById('p2-iv6-q').oninput = ev => {
state.q = +ev.target.value;
document.getElementById('p2-iv6-q-val').textContent = state.q;
render();
};
render();
}
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);
const m = 0.5;
const c_ice = 2100, c_water = 4200, lambda = 330000, r_vap = 2300000;
let power = 500, energyAccumulated = 0, running = false;
let points = [{ t: 0, T: -20 }];
const pad = { l: 50, r: 18, t: 22, b: 32 };
const plotW = W - pad.l - pad.r;
const plotH = H - pad.t - pad.b;
svg.appendChild(P8Helpers.svg.el('rect', { x: pad.l, y: pad.t, width: plotW, height: plotH, fill: '#fafafa', stroke: '#e5e7eb' }));
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); }
[-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+'°' }));
});
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 }));
});
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' }));
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: 'Время, с' }));
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 < 0.5 && energyAccumulated < lambda * m) return 'плавление';
if (T < 100) return 'вода';
if (T < 100.5 && energyAccumulated < (lambda + r_vap) * m) return 'кипение';
return 'пар';
}
function tick(dt){
if (!running) return;
const energy = power * dt;
let T = currentT();
let newT = T;
if (T < 0) {
const dT = energy / (c_ice * m);
newT = T + dT;
if (newT > 0) newT = 0;
} else if (T < 0.5 && energyAccumulated < lambda * m) {
energyAccumulated += energy;
newT = 0;
if (energyAccumulated >= lambda * m) newT = 0.5;
} else if (T < 100) {
const dT = energy / (c_water * m);
newT = T + dT;
if (newT > 100) newT = 100;
} else if (T < 100.5 && energyAccumulated < (lambda + r_vap) * m) {
energyAccumulated += energy;
newT = 100;
if (energyAccumulated >= (lambda + r_vap) * m) newT = 100.5;
} else if (T < 120) {
const dT = energy / (c_water * m);
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;
}
const raf = P8Anim.raf(dt => tick(Math.min(dt * 4, 0.5)));
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);
const v1 = { x: 140, y: 130, m: 0.5, T: 80 };
const v2 = { x: 420, y: 130, m: 1.0, T: 20 };
const finalState = { active: false, T: 50 };
function drawVessel(x, y, m, T){
const g = P8Helpers.svg.el('g', { transform: 'translate('+x+','+y+')' });
const ht = 30 + m * 50; const w = 70;
g.appendChild(P8Helpers.svg.el('rect', { x:-w/2, y:-ht, width:w, height:ht, rx:6, fill:'rgba(255,255,255,.6)', stroke:'#0f172a', 'stroke-width':1.5 }));
g.appendChild(P8Helpers.svg.el('rect', { x:-w/2+3, y:-ht+5, width:w-6, height:ht-8, rx:4, fill: P8Helpers.thermal.tempColor(T/100) }));
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;
}
function redraw(){
svg.innerHTML = '';
if (!finalState.active) {
svg.appendChild(drawVessel(v1.x, v1.y, v1.m, v1.T));
svg.appendChild(drawVessel(v2.x, v2.y, v2.m, v2.T));
} else {
svg.appendChild(drawVessel(280, 130, v1.m + v2.m, finalState.T));
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' }));
}
}
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');
document.getElementById('p6-iv6-mix').onclick = () => {
const T = (v1.m * v1.T + v2.m * v2.T) / (v1.m + v2.m);
finalState.active = true;
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);
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);
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 => {
const g = P8Helpers.svg.el('g', { transform: 'translate('+rod.x+','+rod.y+')' });
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);
}
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 });
});
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;
const lamNorm = Math.min(1, Math.log10(rodObj.rod.lam + 1) / Math.log10(500));
simLoop = P8Anim.raf(dt => {
simTime += dt;
const speed = lamNorm * 0.8 + 0.04;
rodObj.segs.forEach((seg, i) => {
const pos = i / (rodObj.segs.length - 1);
const heat = Math.max(0, Math.min(1, speed * simTime - pos));
seg.setAttribute('fill', P8Helpers.thermal.tempColor(heat * 0.85 + 0.1));
});
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;
}
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) => {
if (Math.abs(pos.x - 80) < 70 && Math.abs(pos.y - 240) < 50) {
rodEls.forEach((other, j) => { if (j !== i) resetColors(other); });
startSim(rodObj);
if (window.addXp) addXp(10, 'p3-iv6-conduct');
}
}
});
});
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: 'Перетащи стержень на горелку • Чем выше λ — тем быстрее цвет дойдёт до конца'
}));
}
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._body = 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 — Piston (Phase 1.3) */
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">Поршень — два способа изменить U</div></div>'
+'<div class="wg-help">Двигай поршень — газ сжимается и нагревается (работа над газом → ΔU > 0). Или подавай тепло — U растёт без работы. Сравни графики.</div>'
+'<div class="p8-sandbox" id="p2-iv6-sandbox" style="height:240px"></div>'
+'<div style="margin-top:10px;display:flex;gap:14px;flex-wrap:wrap">'
+'<div class="p8-scrubber" style="flex:1;min-width:180px"><span class="p8-scrubber-label">Сжатие</span><input type="range" id="p2-iv6-comp" min="0" max="100" step="1" value="0"><span class="p8-scrubber-value"><span id="p2-iv6-comp-val">0</span><span class="p8-unit">%</span></span></div>'
+'<div class="p8-scrubber" style="flex:1;min-width:180px"><span class="p8-scrubber-label">Q</span><input type="range" id="p2-iv6-q" min="0" max="500" step="10" value="0"><span class="p8-scrubber-value"><span id="p2-iv6-q-val">0</span><span class="p8-unit">Дж</span></span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">T</span><span class="p8-readout-value" id="p2-iv6-t">20</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="p2-iv6-u">100</span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p2') + readButton('p2');
renderMath(box);
wireReadBtn('p2');
_initP2_iv6();
_initp2_iv5();
_initP2_sim();
_initP2_quiz();
_initP2_dnd();
_initP2_mcq();
}
function _initp2_iv5(){
const TASKS = [{"q":"Газу передали $Q = 200$ Дж теплоты, и он совершил работу $A = 60$ Дж. На сколько увеличилась его внутренняя энергия? ($\\Delta U = Q - A$)","ans":140,"tol":2,"why":"$\\Delta U = Q - A = 200 - 60 = 140$ Дж (первое начало термодинамики)."},{"q":"Над газом совершили работу $A_{внеш} = 150$ Дж, газ отдал $Q = 50$ Дж тепла. На сколько изменилась $U$?","ans":100,"tol":2,"why":"$\\Delta U = A_{внеш} - Q_{отд} = 150 - 50 = 100$ Дж."},{"q":"Газ адиабатно (без теплообмена, $Q = 0$) расширился, совершив $A = 80$ Дж. Найдите $|\\Delta U|$.","ans":80,"tol":2,"why":"При $Q = 0$: $\\Delta U = -A = -80$ Дж. Модуль изменения $|\\Delta U| = 80$ Дж."},{"q":"Молотом массой $0{,}5$ кг, движущимся со скоростью $v = 4$ м/с, ударили по гвоздю. Вся кинетическая энергия перешла в тепло. На сколько Джоулей увеличилась $U$ гвоздя?","ans":4,"tol":0.1,"why":"$E_к = \\dfrac{mv^2}{2} = \\dfrac{0{,}5 \\cdot 16}{2} = 4$ Дж $= \\Delta U$."},{"q":"Газу сообщили $Q = 500$ Дж, при этом $\\Delta U = 350$ Дж. Какую работу совершил газ?","ans":150,"tol":3,"why":"$A = Q - \\Delta U = 500 - 350 = 150$ Дж."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p2-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="p2-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p2-iv5-go">Ответ</button>'
+'<button class="btn" id="p2-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p2-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p2-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p2-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p2-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p2-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p2-iv5-fb');
const wh = document.getElementById('p2-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p2-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('p2-iv5-hint').onclick = () => {
const wh = document.getElementById('p2-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p2-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p2-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p2-iv5'); }
};
}
render();
}
/* === §2 IV-1: симуляция «работа vs теплопередача» === */
function _initP2_sim(){
_killSim('p2sim');
const svg = document.getElementById('p2-sim'); if(!svg) return;
const W=460, H=200;
/* левая сцена: брусок скользит по доске */
/* правая сцена: два тела касаются, температуры → среднее */
let running = false;
let tA = 20; /* температура бруска */
let tH = 90, tC = 10; /* контакт горячий/холодный */
let blockX = 60;
let t = 0;
function reset(){
tA = 20; tH = 90; tC = 10; blockX = 60; t = 0;
document.getElementById('p2-tA').textContent = '20';
document.getElementById('p2-uA').textContent = '0';
document.getElementById('p2-tH').textContent = '90';
document.getElementById('p2-tC').textContent = '10';
paint();
}
function paint(){
let s = '';
/* === ЛЕВАЯ СЦЕНА: трение === */
/* доска */
s += '<rect x="20" y="120" width="200" height="14" fill="#a16207" stroke="#0f172a" stroke-width="1.5" rx="2"/>';
/* штриховка под доской */
for(let i=22;i<218;i+=10) s += '<line x1="'+i+'" y1="134" x2="'+(i+4)+'" y2="140" stroke="#92400e" stroke-width="1"/>';
/* брусок (цвет по температуре) */
const cA = window.PHYS.tempColor(tA, 20, 80);
s += '<rect x="'+blockX+'" y="92" width="50" height="28" fill="'+cA+'" stroke="#0f172a" stroke-width="1.6" rx="3"/>';
/* стрелка-сила F (рука толкает) */
s += window.PHYS.drawArrow ? window.PHYS.drawArrow(blockX-30, 106, blockX-5, 106, '#10b981', 2.2, 9) : '';
s += '<text x="'+(blockX-32)+'" y="100" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#10b981">F</text>';
/* трение (внизу бруска) */
s += '<text x="'+(blockX+25)+'" y="80" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#dc2626">трение нагревает</text>';
/* термометр для бруска */
s += window.PHYS.thermometer(220, 50, 80, 0, 100, tA);
/* подпись */
s += '<text x="120" y="170" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">РАБОТА (трение)</text>';
/* === разделитель === */
s += '<line x1="240" y1="20" x2="240" y2="180" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="4 4"/>';
/* === ПРАВАЯ СЦЕНА: контакт === */
const cH = window.PHYS.tempColor(tH, 0, 100);
const cCo = window.PHYS.tempColor(tC, 0, 100);
s += '<rect x="270" y="80" width="60" height="60" fill="'+cH+'" stroke="#0f172a" stroke-width="1.6" rx="4"/>';
s += '<rect x="335" y="80" width="60" height="60" fill="'+cCo+'" stroke="#0f172a" stroke-width="1.6" rx="4"/>';
/* стрелки потока тепла */
s += window.PHYS.drawArrow ? window.PHYS.drawArrow(326, 100, 344, 100, '#dc2626', 2.2, 8) : '';
s += window.PHYS.drawArrow ? window.PHYS.drawArrow(326, 120, 344, 120, '#dc2626', 2.2, 8) : '';
s += '<text x="335" y="72" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626">Q</text>';
/* подписи температур */
s += '<text x="300" y="160" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">'+tH.toFixed(0)+' &#176;C</text>';
s += '<text x="365" y="160" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">'+tC.toFixed(0)+' &#176;C</text>';
s += '<text x="332" y="174" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">ТЕПЛОПЕРЕДАЧА</text>';
svg.innerHTML = s;
}
function tick(){
if(!running){ _SIMS.p2sim.raf = requestAnimationFrame(tick); return; }
if(!_isVisible('p2')){ _SIMS.p2sim.raf = requestAnimationFrame(tick); return; }
t += 1;
/* брусок двигается слева направо и нагревается */
blockX += 0.8;
if(blockX > 170){ blockX = 60; }
if(tA < 80) tA += 0.18;
/* контакт: tH и tC сходятся к среднему */
const avg = (tH + tC) / 2;
if(Math.abs(tH - avg) > 0.05){
tH -= (tH - avg) * 0.012;
tC += (avg - tC) * 0.012;
}
document.getElementById('p2-tA').textContent = tA.toFixed(0);
document.getElementById('p2-uA').textContent = (tA - 20).toFixed(0) === '0' ? '0' : '+'+(tA - 20).toFixed(0);
document.getElementById('p2-tH').textContent = tH.toFixed(0);
document.getElementById('p2-tC').textContent = tC.toFixed(0);
paint();
_SIMS.p2sim.raf = requestAnimationFrame(tick);
}
document.getElementById('p2-sim-start').addEventListener('click', ()=>{
running = !running;
document.getElementById('p2-sim-start').textContent = running ? 'Пауза' : 'Запустить';
});
document.getElementById('p2-sim-reset').addEventListener('click', ()=>{
running = false;
document.getElementById('p2-sim-start').textContent = 'Запустить';
reset();
});
_SIMS.p2sim = { raf: 0 };
reset();
_SIMS.p2sim.raf = requestAnimationFrame(tick);
}
/* === §2 IV-2: викторина работа/теплопередача === */
function _initP2_quiz(){
const QS = [
{sit:'Долго трёшь руки одна о другую — они нагреваются.', ans:'W', why:'Работа сил трения переходит во внутреннюю энергию рук.'},
{sit:'Чай в стакане остывает на столе.', ans:'H', why:'Теплопередача от чая в воздух и стенки стакана — без совершения работы.'},
{sit:'Велосипедный насос нагревается при накачивании колеса.', ans:'W', why:'Воздух в насосе сжимается — над ним совершается работа.'},
{sit:'Металлическая ложка в горячем чае нагревается.', ans:'H', why:'Теплопередача (теплопроводность) от чая к ложке.'},
{sit:'Гвоздь забивают молотком — шляпка гвоздя нагревается.', ans:'W', why:'Кинетическая энергия молотка переходит в работу деформации/трения.'},
{sit:'Лёд в холодильнике плавится, если открыть дверцу.', ans:'H', why:'Тёплый воздух комнаты передаёт энергию льду.'},
{sit:'Греется проволока в фене — горячий воздух дует на руки.', ans:'H', why:'Конвекция: поток горячего воздуха переносит энергию.'},
{sit:'Спички в коробке от долгой тряски нагрелись.', ans:'W', why:'Работа сил трения внутри коробки.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i];
const wrap = document.getElementById('p2-quiz');
if(!wrap) return;
wrap.innerHTML =
'<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:10px">'
+'<button class="btn" data-pick="W" style="padding:14px"><b>Работа</b></button>'
+'<button class="btn" data-pick="H" style="padding:14px"><b>Теплопередача</b></button>'
+'</div>'
+'<div class="feedback" id="p2-quiz-fb"></div>';
document.getElementById('p2-quiz-r').textContent = (i+1);
document.getElementById('p2-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('p2-quiz-fb');
if(pick === q.ans){
ok++;
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why;
addXp(3,'p2-quiz'); bumpProgress('p2', 4);
} else {
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why;
}
document.getElementById('p2-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p2-quiz-next').addEventListener('click', ()=>{
i = (i+1) % QS.length; render();
});
render();
}
/* === §2 IV-3: DnD сортировка === */
function _initP2_dnd(){
const items = [
{id:'a', cat:'work', html:'трение ладонями'},
{id:'b', cat:'work', html:'сжатие воздуха в насосе'},
{id:'c', cat:'work', html:'удар молотком по гвоздю'},
{id:'d', cat:'work', html:'строгание доски рубанком'},
{id:'e', cat:'heat', html:'остывание чая в стакане'},
{id:'f', cat:'heat', html:'нагрев ложки в супе'},
{id:'g', cat:'heat', html:'Солнце греет асфальт'},
{id:'h', cat:'heat', html:'батарея греет комнату'}
];
const dnd = setupSorter({
poolId:'p2-dnd-pool',
scopeSelector:'#sec-p2',
cats:['work','heat'],
items: items,
columnLayout:false
});
document.getElementById('p2-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p2-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. Работа связана с движением макротел, теплопередача — с разностью температур.';
addXp(15,'p2-dnd'); bumpProgress('p2', 20);
} else {
fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+' из '+total+'. Подсказка: трение, сжатие, удар, строгание — это работа.';
}
});
document.getElementById('p2-dnd-reset').addEventListener('click', ()=>{
dnd.reset();
const fb = document.getElementById('p2-dnd-fb'); fb.style.display='none';
});
}
/* === §2 IV-4: MCQ тренажёр === */
function _initP2_mcq(){
const QS = [
{q:'Какими способами можно изменить $U$ тела?', opts:['Только работой','Только теплопередачей','Работой и теплопередачей','Только нагревом'], ans:2, why:'Это два равноправных способа.'},
{q:'Что нагревает наковальню при ковке?', opts:['Теплопередача','Работа удара','Излучение','Конвекция'], ans:1, why:'Кинетическая энергия молотка совершает работу деформации.'},
{q:'Какой вид теплопередачи греет руку от костра, если не подходить близко?', opts:['Теплопроводность','Конвекция','Излучение','Работа'], ans:2, why:'Через воздух (плохой проводник) и без контакта — это излучение.'},
{q:'Чай в термосе долго не остывает, потому что…', opts:['у термоса большая масса','стенки термоса тонкие','двойные стенки и вакуум между ними подавляют все виды теплопередачи','чай совершает работу'], ans:2, why:'Вакуум — нет конвекции и проводности; зеркальные стенки уменьшают излучение.'},
{q:'Какой пример НЕ относится к теплопередаче?', opts:['Ложка нагрелась в супе','Воздух у радиатора поднимается вверх','Снег тает на солнце','Гвоздь нагрелся, когда его забили молотком'], ans:3, why:'Гвоздь нагрелся работой удара.'},
{q:'Знак $\\Delta U$ для остывающего тела?', opts:['$\\Delta U > 0$','$\\Delta U < 0$','$\\Delta U = 0$','зависит от массы'], ans:1, why:'$T$ падает $\\Rightarrow$ $U$ уменьшается $\\Rightarrow$ $\\Delta U < 0$.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i];
const wrap = document.getElementById('p2-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="p2-mcq-fb"></div>';
h += '<div class="actions"><button class="btn" id="p2-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p2-mcq-i').textContent = (i+1);
document.getElementById('p2-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('p2-mcq-fb');
if(k === q.ans){
ok++; done++;
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why;
addXp(2,'p2-mcq'); bumpProgress('p2', 3);
} else {
done++;
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why;
}
document.getElementById('p2-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){
awarded = true;
setTimeout(()=>{
const wgFb = document.getElementById('p2-mcq-fb');
wgFb.className='feedback ok';
wgFb.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').';
addXp(15,'p2-mcq-bonus'); bumpProgress('p2', 15);
}, 600);
}
});
});
const nextBtn = document.getElementById('p2-mcq-next');
if(nextBtn) nextBtn.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======================================================================
PHASE 1 · WAVE 2 — §3, §4, §5
====================================================================== */
/* ======== §3 — Теплопроводность ======== */
function build_p3(){
const box = document.getElementById('p3-body');
let h = '';
h += makeCard('theory', 'Что такое теплопроводность', '§ 3.1',
'<p><b>Теплопроводность</b> — передача внутренней энергии от более горячих частей тела к более холодным <b>без переноса вещества</b>.</p>'
+'<p>Механизм: быстрые молекулы (в горячей части) сталкиваются с медленными (в холодной) и передают им часть своей $E_k$. Так энергия путешествует через тело молекула за молекулой.</p>'
+'<p>Само вещество при этом остаётся на месте — двигается только энергия.</p>'
);
h += makeCard('rule', 'Хорошие и плохие проводники', '§ 3.2',
'<p><b>Хорошие проводники</b> тепла:</p>'
+'<ul style="padding-left:20px;margin:4px 0"><li>металлы: серебро, медь, алюминий, железо;</li><li>в них свободные электроны быстро переносят энергию.</li></ul>'
+'<p style="margin-top:8px"><b>Плохие проводники</b> (тепловые изоляторы):</p>'
+'<ul style="padding-left:20px;margin:4px 0"><li>дерево, стекло, кирпич, пластик;</li><li>шерсть, мех, пух, вата (они «держат» воздух между волокнами);</li><li>сами газы и жидкости (кроме ртути);</li><li><b>вакуум</b> — идеальный изолятор: нет частиц $\\Rightarrow$ нечем передавать.</li></ul>'
);
h += makeCard('example', 'Зачем это нам', '§ 3.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>$T_{гор}$</b>), другой — холодный (<b>$T_{хол}$</b>). Тепло перетекает с горячего конца на холодный. Цвет показывает температуру вдоль стержня. Меняй $\\alpha$ — коэффициент теплопроводности.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$T_{гор}$, &#176;C: <b id="p3-th">200</b><input type="range" id="p3-thr" min="50" max="500" step="10" value="200"></label>'
+'<label>$T_{хол}$, &#176;C: <b id="p3-tc">0</b><input type="range" id="p3-tcr" min="-50" max="100" step="5" value="0"></label>'
+'<label>$\\alpha$ (проводимость): <b id="p3-av">медь</b><input type="range" id="p3-ar" min="1" max="100" step="1" value="50"></label>'
+'</div>'
+'<svg id="p3-sim" viewBox="0 0 460 80" 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" id="p3-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="p3-quiz"></div>'
+'<div class="actions"><button class="btn" id="p3-quiz-next">Следующий раунд</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p3-quiz-r">1</b> / 6</span><span>Правильно: <b id="p3-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="p3-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="good"></div></div>'
+'<div class="drop-box"><h5>Плохие проводники</h5><div class="drop-items" data-cat="bad"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p3-dnd-check">Проверить</button><button class="btn" id="p3-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p3-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="p3-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p3-mcq-i">1</b> / 6</span><span>Правильно: <b id="p3-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="p3-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p3-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p3-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 — Convection (Phase 1.3) */
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="p4-iv6-sandbox" style="height:280px"></div>'
+'<div style="margin-top:10px;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="p4-iv6-pwr" min="0" max="100" step="1" value="40"><span class="p8-scrubber-value"><span id="p4-iv6-pwr-val">40</span><span class="p8-unit">%</span></span></div>'
+'<button class="btn primary" id="p4-iv6-play">Пуск</button>'
+'<button class="btn" id="p4-iv6-pause">Стоп</button>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p4') + readButton('p4');
renderMath(box);
wireReadBtn('p4');
_initP4_iv6();
_initp4_iv5();
_initP4_sim();
_initP4_quiz();
_initP4_dnd();
_initP4_mcq();
}
function _initp4_iv5(){
const TASKS = [{"q":"Плотность тёплого воздуха в $\\rho_1 = 1{,}1$ кг/м³, холодного $\\rho_2 = 1{,}3$ кг/м³. На сколько % холодный плотнее? $((\\rho_2 - \\rho_1)/\\rho_1) \\cdot 100$.","ans":18,"tol":1,"why":"$(1{,}3 - 1{,}1)/1{,}1 \\cdot 100 \\approx 18\\,\\%$."},{"q":"Радиатор отдаёт мощность $P = 1500$ Вт, нагревая воздух массой $m = 50$ кг за $t = 60$ с. На сколько $\\Delta T$ нагрелся воздух? ($c_{возд} = 1000$ Дж/(кг·К))","ans":1.8,"tol":0.1,"why":"$\\Delta T = Q/(cm) = (P\\cdot t)/(cm) = (1500 \\cdot 60)/(1000 \\cdot 50) = 1{,}8$ К."},{"q":"Вода нагревается снизу. Где будет тёплая вода: $a)$ снизу, $b)$ сверху? Введите 2, если сверху, 1, если снизу.","ans":2,"tol":0.1,"why":"Тёплая вода легче — поднимается вверх. Это и есть конвекция."},{"q":"Холодильник остужает $m = 2$ кг воздуха с $T_1 = 25$ до $T_2 = 5\\,^\\circ$C. Какое тепло (в кДж) он унёс? ($c = 1000$)","ans":40,"tol":1,"why":"$Q = cm\\Delta T = 1000 \\cdot 2 \\cdot 20 = 40\\,000$ Дж $= 40$ кДж."},{"q":"Ветер охлаждает кожу. Если без ветра тело отдаёт $P_0 = 50$ Вт, а с ветром $P = 200$ Вт, во сколько раз быстрее идёт теплоотдача?","ans":4,"tol":0.1,"why":"$P/P_0 = 200/50 = 4$ раза."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p4-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="p4-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p4-iv5-go">Ответ</button>'
+'<button class="btn" id="p4-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p4-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p4-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p4-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p4-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p4-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p4-iv5-fb');
const wh = document.getElementById('p4-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p4-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('p4-iv5-hint').onclick = () => {
const wh = document.getElementById('p4-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p4-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p4-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p4-iv5'); }
};
}
render();
}
function _initP4_sim(){
_killSim('p4sim');
const svg = document.getElementById('p4-sim'); if(!svg) return;
const W=460, H=240, vesselX=120, vesselY=20, vesselW=220, vesselH=180;
/* 30 частиц жидкости */
const N = 30;
const ps = [];
for(let i=0;i<N;i++){
ps.push({
x: vesselX + 10 + Math.random()*(vesselW-20),
y: vesselY + 10 + Math.random()*(vesselH-20),
vx: 0, vy: 0,
T: 20
});
}
let heaterOn = false;
function tick(){
if(!_isVisible('p4')){ _SIMS.p4sim.raf = requestAnimationFrame(tick); return; }
/* Движение по тороидальному полю при включённом нагреве */
for(const p of ps){
if(heaterOn){
/* нагрев в нижней зоне */
const distFromBottom = (vesselY + vesselH) - p.y;
if(distFromBottom < 30) p.T = Math.min(90, p.T + 0.3);
else p.T = Math.max(20, p.T - 0.04);
/* поле скорости: тёплая частица в нижней половине → вверх,
холодная в верхней → вниз. По краям движение в обратные стороны. */
const cx = vesselX + vesselW/2;
const dx = p.x - cx;
const yNorm = (p.y - vesselY) / vesselH; /* 0 верх .. 1 низ */
/* основной вертикальный поток: вверх у центра, вниз у стенок */
if(Math.abs(dx) < vesselW*0.25) p.vy = -0.9 * (p.T - 20)/70 - 0.2;
else p.vy = 0.6 * (1 - yNorm)*0.8 + 0.2;
/* горизонтальное замыкание петли */
if(yNorm < 0.18) p.vx = (dx > 0 ? 1 : -1) * 0.8;
else if(yNorm > 0.82) p.vx = (dx < 0 ? 1 : -1) * 0.8;
else p.vx *= 0.85;
} else {
p.T = Math.max(20, p.T - 0.1);
p.vx *= 0.94; p.vy *= 0.94;
}
p.x += p.vx; p.y += p.vy;
/* стенки */
if(p.x < vesselX+6){ p.x = vesselX+6; p.vx = Math.abs(p.vx); }
if(p.x > vesselX+vesselW-6){ p.x = vesselX+vesselW-6; p.vx = -Math.abs(p.vx); }
if(p.y < vesselY+6){ p.y = vesselY+6; p.vy = Math.abs(p.vy); }
if(p.y > vesselY+vesselH-6){ p.y = vesselY+vesselH-6; p.vy = -Math.abs(p.vy); }
}
let s = '';
/* сосуд */
s += '<rect x="'+vesselX+'" y="'+vesselY+'" width="'+vesselW+'" height="'+vesselH+'" fill="#e0f2fe" stroke="#0f172a" stroke-width="2" rx="6"/>';
/* нагреватель снизу */
s += '<rect x="'+(vesselX-4)+'" y="'+(vesselY+vesselH+4)+'" width="'+(vesselW+8)+'" height="18" fill="'+(heaterOn?'#dc2626':'#94a3b8')+'" stroke="#0f172a" stroke-width="1.5" rx="4"/>';
s += '<text x="'+(vesselX+vesselW/2)+'" y="'+(vesselY+vesselH+17)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">'+(heaterOn?'НАГРЕВАТЕЛЬ ВКЛ':'выкл')+'</text>';
/* частицы */
for(const p of ps){
const c = window.PHYS.tempColor(p.T, 20, 90);
s += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="5" fill="'+c+'" stroke="#0f172a" stroke-width="0.5"/>';
}
/* стрелки потока (декоративные) */
if(heaterOn){
const cx = vesselX + vesselW/2;
s += window.PHYS.drawArrow(cx, vesselY+vesselH-20, cx, vesselY+30, '#dc2626', 2, 9);
s += window.PHYS.drawArrow(vesselX+18, vesselY+30, vesselX+18, vesselY+vesselH-20, '#2563eb', 2, 9);
s += window.PHYS.drawArrow(vesselX+vesselW-18, vesselY+30, vesselX+vesselW-18, vesselY+vesselH-20, '#2563eb', 2, 9);
}
svg.innerHTML = s;
_SIMS.p4sim.raf = requestAnimationFrame(tick);
}
_SIMS.p4sim = { raf: 0 };
_SIMS.p4sim.raf = requestAnimationFrame(tick);
document.getElementById('p4-on').addEventListener('click', ()=>{
heaterOn = !heaterOn;
document.getElementById('p4-on').textContent = heaterOn ? 'Выключить нагрев' : 'Включить нагрев';
});
document.getElementById('p4-reset').addEventListener('click', ()=>{
heaterOn = false;
document.getElementById('p4-on').textContent = 'Включить нагрев';
for(let i=0;i<N;i++){
ps[i].x = vesselX + 10 + Math.random()*(vesselW-20);
ps[i].y = vesselY + 10 + Math.random()*(vesselH-20);
ps[i].vx = 0; ps[i].vy = 0; ps[i].T = 20;
}
});
}
function _initP4_quiz(){
const QS = [
{sit:'Воздух в комнате с радиатором у стены', ans:'Y', why:'Тёплый воздух поднимается, холодный спускается — классическая конвекция.'},
{sit:'Вода в кастрюле, нагреваемой снизу', ans:'Y', why:'Нагрев снизу + жидкость = конвекционный поток.'},
{sit:'Камень во время летнего полудня', ans:'N', why:'Камень — твёрдое тело, частицы не движутся, конвекции нет.'},
{sit:'Вода в сосуде, нагреваемом сверху', ans:'N', why:'Горячий слой уже наверху — он там и останется, потоков нет.'},
{sit:'Атмосфера планеты', ans:'Y', why:'Газ + разность температур (день/ночь, экватор/полюса) = ветры.'},
{sit:'Металлический стержень, нагреваемый с конца', ans:'N', why:'Это твёрдое тело — теплопроводность, а не конвекция.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p4-quiz'); if(!wrap) return;
wrap.innerHTML =
'<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:10px">'
+'<button class="btn" data-pick="Y" style="padding:14px"><b>Да, конвекция</b></button>'
+'<button class="btn" data-pick="N" style="padding:14px"><b>Нет, не конвекция</b></button>'
+'</div>'
+'<div class="feedback" id="p4-quiz-fb"></div>';
document.getElementById('p4-quiz-r').textContent = (i+1);
document.getElementById('p4-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('p4-quiz-fb');
if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p4-quiz'); bumpProgress('p4', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p4-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p4-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP4_dnd(){
const items = [
{id:'rad', cat:'yes', html:'воздух у радиатора'},
{id:'ket', cat:'yes', html:'вода в кипящем чайнике'},
{id:'oce', cat:'yes', html:'течения в океане'},
{id:'wnd', cat:'yes', html:'ветер в атмосфере'},
{id:'sto', cat:'no', html:'нагрев стального стержня'},
{id:'top', cat:'no', html:'вода, нагреваемая сверху'},
{id:'vac', cat:'no', html:'жидкость в невесомости'},
{id:'wal', cat:'no', html:'кирпичная стена'}
];
const dnd = setupSorter({ poolId:'p4-dnd-pool', scopeSelector:'#sec-p4', cats:['yes','no'], items, columnLayout:false });
document.getElementById('p4-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p4-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,'p4-dnd'); bumpProgress('p4', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. В твёрдых телах и при нагреве сверху конвекции нет.'; }
});
document.getElementById('p4-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p4-dnd-fb'); fb.style.display='none'; });
}
function _initP4_mcq(){
const QS = [
{q:'Что переносится при конвекции?', opts:['Только энергия','Только вещество','Вещество вместе с энергией','Электромагнитные волны'], ans:2, why:'Жидкость или газ движется вместе с теплотой.'},
{q:'Почему в твёрдых телах нет конвекции?', opts:['Они холодные','Молекулы не могут свободно перемещаться','Они не проводят тепло','Они отражают энергию'], ans:1, why:'В твёрдом теле молекулы прочно связаны и не образуют потоков.'},
{q:'Как направлен поток в нагреваемой снизу жидкости?', opts:['Горячий вниз, холодный вверх','Горячий вверх, холодный вниз','Хаотически','Только по краям'], ans:1, 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('p4-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="p4-mcq-fb"></div><div class="actions"><button class="btn" id="p4-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p4-mcq-i').textContent = (i+1);
document.getElementById('p4-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('p4-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p4-mcq'); bumpProgress('p4', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p4-mcq-ok').textContent = ok;
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf = document.getElementById('p4-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').'; addXp(15,'p4-mcq-bonus'); bumpProgress('p4', 15); }, 600); }
});
});
const nb = document.getElementById('p4-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
}
render();
}
/* ======== §5 — Излучение ======== */
function build_p5(){
const box = document.getElementById('p5-body');
let h = '';
h += makeCard('theory', 'Что такое тепловое излучение', '§ 5.1',
'<p><b>Излучение</b> — передача энергии электромагнитными волнами. Не требует среды: идёт через вакуум.</p>'
+'<p>Любое тело с температурой выше абсолютного нуля излучает. Чем выше $T$, тем интенсивнее излучение и тем более коротковолновое (горячая печь — инфракрасный диапазон; Солнце — видимый свет).</p>'
+'<p>Так Солнце греет Землю, прошив 150 млн км вакуума за 8 минут.</p>'
);
h += makeCard('rule', 'Поглощение и отражение', '§ 5.2',
'<ul style="padding-left:20px;margin:4px 0">'
+'<li><b>Тёмные</b> и шероховатые поверхности — <b>хорошо поглощают</b> излучение (нагреваются быстрее).</li>'
+'<li><b>Светлые</b> и зеркальные — <b>отражают</b> излучение (нагреваются медленнее).</li>'
+'<li>Те же тёмные поверхности и <b>излучают</b> сильнее: чёрный чайник остывает быстрее.</li>'
+'</ul>'
+'<p style="margin-top:6px"><b>Правило:</b> что хорошо поглощает, то хорошо и излучает.</p>'
);
h += makeCard('example', 'Применение', '§ 5.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="p5-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="p5-on">Включить Солнце</button><button class="btn" id="p5-reset">Сброс</button></div>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Чёрная пластина: $T = $ <b id="p5-tb">20</b> &#176;C</span>'
+'<span>Белая пластина: $T = $ <b id="p5-tw">20</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="p5-quiz"></div>'
+'<div class="actions"><button class="btn" id="p5-quiz-next">Следующее</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p5-quiz-r">1</b> / 7</span><span>Правильно: <b id="p5-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD 3 вида теплопередачи */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">3 вида теплопередачи</div></div>'
+'<div class="wg-help">Сортируй примеры на три группы: проводность, конвекция, излучение.</div>'
+'<div id="p5-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="cond"></div></div>'
+'<div class="drop-box"><h5>Конвекция</h5><div class="drop-items" data-cat="conv"></div></div>'
+'<div class="drop-box"><h5>Излучение</h5><div class="drop-items" data-cat="rad"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p5-dnd-check">Проверить</button><button class="btn" id="p5-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p5-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="p5-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p5-mcq-i">1</b> / 6</span><span>Правильно: <b id="p5-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="p5-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p5-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p5-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — Radiation (Phase 1.3) */
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">Под лампой — три тела разного цвета: чёрное, белое, зеркальное. Двигай мощность лампы, наблюдай, как растёт T каждого. Чёрное поглощает почти всё, белое — мало, зеркало — почти ничего.</div>'
+'<div class="p8-sandbox" id="p5-iv6-sandbox" style="height:240px"></div>'
+'<div style="margin-top:10px;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="p5-iv6-lamp" min="0" max="100" step="1" value="60"><span class="p8-scrubber-value"><span id="p5-iv6-lamp-val">60</span><span class="p8-unit">%</span></span></div>'
+'<button class="btn primary" id="p5-iv6-play">Старт</button>'
+'<button class="btn" id="p5-iv6-reset">Сброс</button>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p5') + readButton('p5');
renderMath(box);
wireReadBtn('p5');
_initP5_iv6();
_initp5_iv5();
_initP5_sim();
_initP5_quiz();
_initP5_dnd();
_initP5_mcq();
}
function _initp5_iv5(){
const TASKS = [{"q":"Солнце нагревает квадратный метр земной поверхности с мощностью $P = 1000$ Вт. Сколько теплоты получит $S = 5$ м² за $t = 60$ с?","ans":300000,"tol":5000,"why":"$Q = P \\cdot S \\cdot t = 1000 \\cdot 5 \\cdot 60 = 300\\,000$ Дж = $300$ кДж."},{"q":"Черное тело излучает в 2 раза эффективнее белого. Если белое тело отдаёт $P_1 = 100$ Вт, сколько отдаст чёрное при той же $T$?","ans":200,"tol":5,"why":"$P_{черн} = 2 \\cdot P_{белого} = 2 \\cdot 100 = 200$ Вт."},{"q":"Какая температура (в К) горячей плиты, если её излучение в 16 раз сильнее излучения тела при $T_0 = 300$ К? ($P \\propto T^4$)","ans":600,"tol":10,"why":"$P/P_0 = (T/T_0)^4 = 16$, откуда $T/T_0 = 2$, $T = 600$ К."},{"q":"Какой цвет одежды летом холоднее: белый или чёрный? Введите 1, если чёрный, 2, если белый.","ans":2,"tol":0.1,"why":"Белая отражает солнечное излучение лучше — в ней прохладнее."},{"q":"Тело площадью $S = 0{,}5$ м² излучает $P = 200$ Вт. Найдите интенсивность излучения $I = P/S$ (Вт/м²).","ans":400,"tol":10,"why":"$I = P/S = 200/0{,}5 = 400$ Вт/м²."}];
let i = 0, ok = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p5-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="p5-iv5-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p5-iv5-go">Ответ</button>'
+'<button class="btn" id="p5-iv5-hint">Подсказка</button>'
+'<button class="btn" id="p5-iv5-next">Следующая</button></div>'
+'<details class="spoiler" id="p5-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
+'<div class="feedback" id="p5-iv5-fb"></div>';
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
document.getElementById('p5-iv5-go').onclick = () => {
const v = parseFloat(document.getElementById('p5-iv5-inp').value.replace(',','.'));
const fb = document.getElementById('p5-iv5-fb');
const wh = document.getElementById('p5-iv5-why-wrap');
if (Math.abs(v - t.ans) <= t.tol) {
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
document.getElementById('p5-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('p5-iv5-hint').onclick = () => {
const wh = document.getElementById('p5-iv5-why-wrap');
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
};
document.getElementById('p5-iv5-next').onclick = () => {
i = (i + 1) % TASKS.length;
document.getElementById('p5-tasks5-i').textContent = i + 1;
render();
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p5-iv5'); }
};
}
render();
}
function _initP5_sim(){
_killSim('p5sim');
const svg = document.getElementById('p5-sim'); if(!svg) return;
const W=460, H=240;
let sunOn = false;
let tB = 20, tW = 20; /* black, white plate temps */
function reset(){ tB=20; tW=20; sunOn=false; document.getElementById('p5-on').textContent='Включить Солнце'; document.getElementById('p5-tb').textContent='20'; document.getElementById('p5-tw').textContent='20'; }
function tick(){
if(!_isVisible('p5')){ _SIMS.p5sim.raf = requestAnimationFrame(tick); return; }
if(sunOn){
/* чёрная: поглощает 0.92, белая: 0.15 */
if(tB < 75) tB += 0.32;
if(tW < 35) tW += 0.05;
} else {
if(tB > 20) tB -= 0.10;
if(tW > 20) tW -= 0.10;
}
document.getElementById('p5-tb').textContent = tB.toFixed(0);
document.getElementById('p5-tw').textContent = tW.toFixed(0);
/* draw */
let s = '';
/* Солнце */
const cx = 230, cy = 36;
s += '<circle cx="'+cx+'" cy="'+cy+'" r="22" fill="'+(sunOn?'#fbbf24':'#cbd5e1')+'" stroke="#0f172a" stroke-width="1.5"/>';
if(sunOn){
for(let i=0;i<12;i++){
const a = i*Math.PI/6;
const x1 = cx + 26*Math.cos(a), y1 = cy + 26*Math.sin(a);
const x2 = cx + 36*Math.cos(a), y2 = cy + 36*Math.sin(a);
s += '<line x1="'+x1.toFixed(1)+'" y1="'+y1.toFixed(1)+'" x2="'+x2.toFixed(1)+'" y2="'+y2.toFixed(1)+'" stroke="#fbbf24" stroke-width="2.5" stroke-linecap="round"/>';
}
}
/* Лучи к пластинам */
if(sunOn){
const ray = (x1,y1,x2,y2,col)=>'<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="'+col+'" stroke-width="1.6" stroke-dasharray="6 4" opacity="0.8"/>';
/* к чёрной */
s += ray(cx-22, cy+18, 120, 175, '#fbbf24');
s += ray(cx-10, cy+22, 145, 175, '#fbbf24');
s += ray(cx, cy+22, 170, 175, '#fbbf24');
/* к белой */
s += ray(cx+22, cy+18, 340, 175, '#fbbf24');
s += ray(cx+10, cy+22, 315, 175, '#fbbf24');
s += ray(cx, cy+22, 290, 175, '#fbbf24');
/* отражённые от белой (вверх) */
s += '<line x1="290" y1="172" x2="270" y2="100" stroke="#cbd5e1" stroke-width="1.4" stroke-dasharray="4 4" opacity="0.7"/>';
s += '<line x1="315" y1="172" x2="310" y2="100" stroke="#cbd5e1" stroke-width="1.4" stroke-dasharray="4 4" opacity="0.7"/>';
s += '<line x1="340" y1="172" x2="350" y2="100" stroke="#cbd5e1" stroke-width="1.4" stroke-dasharray="4 4" opacity="0.7"/>';
}
/* чёрная пластина */
const cB = sunOn ? window.PHYS.tempColor(tB, 20, 80) : '#1f2937';
s += '<rect x="100" y="175" width="100" height="40" fill="'+cB+'" stroke="#0f172a" stroke-width="2" rx="3"/>';
s += '<text x="150" y="200" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="'+(tB>50?'#fff':'#0f172a')+'">'+tB.toFixed(0)+' &#176;C</text>';
s += '<text x="150" y="230" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#0f172a">ЧЁРНАЯ</text>';
/* белая пластина */
s += '<rect x="270" y="175" width="100" height="40" fill="#f1f5f9" stroke="#0f172a" stroke-width="2" rx="3"/>';
s += '<text x="320" y="200" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="#0f172a">'+tW.toFixed(0)+' &#176;C</text>';
s += '<text x="320" y="230" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#0f172a">БЕЛАЯ</text>';
svg.innerHTML = s;
_SIMS.p5sim.raf = requestAnimationFrame(tick);
}
_SIMS.p5sim = { raf: 0 };
_SIMS.p5sim.raf = requestAnimationFrame(tick);
document.getElementById('p5-on').addEventListener('click', ()=>{
sunOn = !sunOn;
document.getElementById('p5-on').textContent = sunOn ? 'Выключить Солнце' : 'Включить Солнце';
});
document.getElementById('p5-reset').addEventListener('click', reset);
}
function _initP5_quiz(){
const QS = [
{st:'Излучение требует среды (например, воздуха) для распространения.', ans:'F', why:'Излучение проходит через вакуум, что и доказывает солнечный свет.'},
{st:'Тёмные тела сильнее нагреваются под Солнцем, чем светлые.', ans:'T', why:'Тёмная поверхность поглощает большую долю излучения.'},
{st:'Любое тело с $T > 0$ К излучает.', ans:'T', why:'Тепловое излучение есть всегда, чем горячее — тем сильнее.'},
{st:'Чем горячее тело, тем меньше излучения оно отдаёт.', ans:'F', why:'Наоборот: интенсивность излучения резко растёт с температурой.'},
{st:'Хороший поглотитель — хороший излучатель.', ans:'T', why:'Это правило Кирхгофа: $a = \\varepsilon$.'},
{st:'Зеркальная поверхность плохо излучает.', ans:'T', why:'Зеркало почти всё отражает — а значит и излучает мало.'},
{st:'Излучение нельзя обнаружить, если тело не светится видимым светом.', ans:'F', why:'Тело при комнатной температуре излучает инфракрасные волны — их видит тепловизор.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p5-quiz'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">"'+q.st+'"</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<button class="btn" data-pick="T" style="padding:14px"><b>Правда</b></button>'
+'<button class="btn" data-pick="F" style="padding:14px"><b>Ложь</b></button>'
+'</div>'
+'<div class="feedback" id="p5-quiz-fb"></div>';
document.getElementById('p5-quiz-r').textContent = (i+1);
document.getElementById('p5-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('p5-quiz-fb');
if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p5-quiz'); bumpProgress('p5', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p5-quiz-ok').textContent = ok;
renderMath(wrap);
});
});
renderMath(wrap);
}
document.getElementById('p5-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP5_dnd(){
const items = [
{id:'spoon', cat:'cond', html:'нагрев ложки в супе'},
{id:'bar', cat:'cond', html:'нагрев конца железного прута'},
{id:'iron', cat:'cond', html:'утюг гладит бельё'},
{id:'rad', cat:'conv', html:'батарея греет комнату'},
{id:'ket', cat:'conv', html:'вода в кипящем чайнике'},
{id:'wnd', cat:'conv', html:'ветры в атмосфере'},
{id:'sun', cat:'rad', html:'Солнце греет Землю'},
{id:'fire', cat:'rad', html:'тепло от костра на расстоянии'},
{id:'lamp', cat:'rad', html:'нагрев руки под лампой накаливания'}
];
const dnd = setupSorter({ poolId:'p5-dnd-pool', scopeSelector:'#sec-p5', cats:['cond','conv','rad'], items, columnLayout:false });
document.getElementById('p5-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p5-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; Идеально! +20 XP. Ты освоил все 3 вида теплопередачи.'; addXp(20,'p5-dnd'); bumpProgress('p5', 25); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Проверь: контакт = проводность; потоки = конвекция; через пустоту = излучение.'; }
});
document.getElementById('p5-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p5-dnd-fb'); fb.style.display='none'; });
}
function _initP5_mcq(){
const QS = [
{q:'Через какую среду НЕ может идти излучение?', opts:['Через воздух','Через стекло','Через вакуум','Излучение проходит через всё перечисленное'], ans:3, why:'Излучение распространяется и в среде, и в вакууме.'},
{q:'Какая поверхность нагреется сильнее на Солнце?', opts:['Зеркальная','Белая','Чёрная','Прозрачная'], ans:2, why:'Чёрная поглощает больше всего излучения.'},
{q:'Какие тела излучают?', opts:['Только горячие','Только светящиеся','Любые с $T > 0$ К','Только Солнце'], ans:2, why:'Тепловое излучение существует у любого тела с ненулевой температурой.'},
{q:'Какой вид теплопередачи переносит тепло от Солнца к Земле?', opts:['Теплопроводность','Конвекция','Излучение','Все три'], ans:2, why:'Между Солнцем и Землёй вакуум — работает только излучение.'},
{q:'Чем покрашен правильный термос изнутри?', opts:['Чёрной краской','Зеркальной плёнкой','Деревом','Стеклом'], ans:1, 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('p5-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="p5-mcq-fb"></div><div class="actions"><button class="btn" id="p5-mcq-next">Следующий вопрос</button></div>';
wrap.innerHTML = h;
document.getElementById('p5-mcq-i').textContent = (i+1);
document.getElementById('p5-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('p5-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p5-mcq'); bumpProgress('p5', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p5-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf = document.getElementById('p5-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').'; addXp(15,'p5-mcq-bonus'); bumpProgress('p5', 15); }, 600); }
});
});
const nb = document.getElementById('p5-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======================================================================
PHASE 1 · WAVE 3 — §6, §7
====================================================================== */
/* Таблица удельных теплоёмкостей (Дж/(кг·К)) */
const MAT_C = [
{key:'water', name:'вода', c:4200},
{key:'ice', name:'лёд', c:2100},
{key:'oil', name:'масло раст.',c:2000},
{key:'wood', name:'дерево', c:2400},
{key:'glass', name:'стекло', c:840 },
{key:'al', name:'алюминий', c:920 },
{key:'fe', name:'железо', c:460 },
{key:'brass', name:'латунь', c:380 },
{key:'cu', name:'медь', c:380 },
{key:'lead', name:'свинец', c:130 },
{key:'hg', name:'ртуть', c:140 }
];
/* Таблица удельных теплот сгорания (Дж/кг) */
const MAT_Q = [
{key:'wood', name:'дрова', q:1.0e7},
{key:'peat', name:'торф', q:1.5e7},
{key:'coal', name:'каменный уголь', q:3.0e7},
{key:'alc', name:'спирт', q:2.7e7},
{key:'oil', name:'нефть', q:4.4e7},
{key:'gas', name:'природный газ', q:4.4e7},
{key:'kero', name:'керосин', q:4.6e7},
{key:'gasoline',name:'бензин', q:4.6e7}
];
/* ======== §6 — Q = cm ΔT ======== */
function build_p6(){
const box = document.getElementById('p6-body');
let h = '';
h += makeCard('theory', 'Закон нагревания', '§ 6.1',
'<p>Чтобы изменить температуру тела массой $m$ на $\\Delta T = T_2 - T_1$, нужно сообщить ему (или отнять у него) количество теплоты:</p>'
+'<p style="text-align:center;margin:8px 0">$$Q = c\\,m\\,\\Delta T$$</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>$Q$ — количество теплоты, Дж;</li>'
+'<li>$c$ — <b>удельная теплоёмкость</b> вещества, Дж/(кг·К);</li>'
+'<li>$m$ — масса, кг;</li>'
+'<li>$\\Delta T$ — изменение температуры, К или &#176;C (разница одна).</li>'
+'</ul>'
+'<p>Если $T_2 > T_1$ — тело нагревается, $Q > 0$. Если $T_2 < T_1$ — остывает, $Q < 0$.</p>'
);
h += makeCard('rule', 'Что такое удельная теплоёмкость', '§ 6.2',
'<p><b>$c$</b> — количество теплоты, нужное чтобы нагреть <b>1 кг</b> вещества на <b>1 К</b> (или 1 &#176;C).</p>'
+'<p>У воды $c = 4200$ Дж/(кг·К) — это очень много. Поэтому:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>вода долго греется на плите;</li>'
+'<li>вода долго остывает (поэтому грелка из воды держит тепло);</li>'
+'<li>океан смягчает климат прибрежных стран.</li>'
+'</ul>'
+'<p>У металлов $c$ маленькая: медь 380, железо 460. Они быстро нагреваются и быстро остывают.</p>'
);
h += makeCard('example', 'Уравнение теплового баланса', '§ 6.3',
'<p>Когда горячее тело отдаёт тепло холодному в изолированной системе (без потерь), вся отданная теплота получена холодным:</p>'
+'<p style="text-align:center;margin:8px 0">$$Q_{отд} = Q_{пол}$$</p>'
+'<p>Если смешать массы $m_1$ при $T_1$ и $m_2$ при $T_2$ одного вещества:</p>'
+'<p style="text-align:center">$$T = \\dfrac{m_1 T_1 + m_2 T_2}{m_1 + m_2}$$</p>'
+'<p>Это <b>средневзвешенная</b> температура.</p>'
);
/* IV1 — калькулятор Q = cmΔT с анимацией */
let optsC = '';
MAT_C.forEach(m=>{ optsC += '<option value="'+m.c+'">'+m.name+' (c = '+m.c+')</option>'; });
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Калькулятор $Q = cm\\Delta T$</div></div>'
+'<div class="wg-help">Выбери вещество, массу и изменение температуры — увидь, сколько энергии нужно. Термометр покажет процесс.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Вещество: <select id="p6-mat" class="tinp" style="width:auto;padding:6px 10px;font-size:.92rem">'+optsC+'</select></label>'
+'<label>$m$, кг: <b id="p6-mv">1.0</b><input type="range" id="p6-m" min="0.1" max="10" step="0.1" value="1"></label>'
+'<label>$\\Delta T$, &#176;C: <b id="p6-dv">50</b><input type="range" id="p6-d" min="-50" max="100" step="5" value="50"></label>'
+'</div>'
+'<svg id="p6-sim" viewBox="0 0 460 160" 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>$c$ = <b id="p6-cv">4200</b> Дж/(кг·К)</span>'
+'<span>$Q$ = <b id="p6-q">2.10 &times; 10^5</b> Дж = <b id="p6-qkj">210</b> кДж</span>'
+'<span style="font-size:.84rem;color:var(--muted)">Это столько энергии съедает плита или электрочайник.</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_1$ кг воды при $T_1$ &#176;C и долили $m_2$ кг воды при $T_2$ &#176;C. Какая будет итоговая температура (без потерь)? Формула: $T = (m_1 T_1 + m_2 T_2)/(m_1 + m_2)$.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$m_1$, кг: <b id="p6-mxv1">1.0</b><input type="range" id="p6-mx1" min="0.1" max="5" step="0.1" value="1"></label>'
+'<label>$T_1$, &#176;C: <b id="p6-txv1">90</b><input type="range" id="p6-tx1" min="0" max="100" step="5" value="90"></label>'
+'<label>$m_2$, кг: <b id="p6-mxv2">2.0</b><input type="range" id="p6-mx2" min="0.1" max="5" step="0.1" value="2"></label>'
+'<label>$T_2$, &#176;C: <b id="p6-txv2">10</b><input type="range" id="p6-tx2" min="0" max="100" step="5" value="10"></label>'
+'</div>'
+'<div class="score-display" style="margin-top:6px"><span>$T_{итог} = $ <b id="p6-tres">36.7</b> &#176;C</span></div>'
+'</div>';
/* IV3 — DnD ранжирование c */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">У какого вещества $c$ больше?</div></div>'
+'<div class="wg-help">Перетащи вещества так, чтобы они шли от <b>меньшего</b> $c$ к <b>большему</b>. Подсказка: у воды $c$ рекордно велика, у металлов — мала.</div>'
+'<div id="p6-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="p6-dnd-check">Проверить</button><button class="btn" id="p6-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p6-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. Допуск ±2%.</div>'
+'<div id="p6-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p6-task-i">1</b> / 6</span><span>Правильно: <b id="p6-task-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">Установи массы и начальные T двух ёмкостей скрубберами, нажми «Смешать» и наблюдай за итоговой температурой по уравнению теплового баланса $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 — Fuel burn (Phase 1.3) */
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">Тепло сгорания: $Q = qm$</div></div>'
+'<div class="wg-help">Выбери топливо и его массу — посчитаем выделенное тепло и нагрев воды массой 1 кг ($c=4200$). $Q = qm$, $\\Delta T = Q / (cm_в)$.</div>'
+'<div class="p8-sandbox" id="p7-iv6-sandbox" style="height:220px"></div>'
+'<div style="margin-top:10px;display:flex;gap:14px;flex-wrap:wrap;align-items:center">'
+'<div class="p8-palette" style="margin:0;padding:6px;background:transparent">'
+'<button class="p8-palette-item" data-fuel="wood">Дрова (q=10 МДж/кг)</button>'
+'<button class="p8-palette-item" data-fuel="coal">Уголь (q=29 МДж/кг)</button>'
+'<button class="p8-palette-item" data-fuel="gas">Газ (q=44 МДж/кг)</button>'
+'</div>'
+'</div>'
+'<div style="margin-top:6px;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="p7-iv6-m" min="0.01" max="1" step="0.01" value="0.1"><span class="p8-scrubber-value"><span id="p7-iv6-m-val">0.10</span><span class="p8-unit">кг</span></span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">Q</span><span class="p8-readout-value" id="p7-iv6-q">1.0</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="p7-iv6-dt">238</span><span class="p8-readout-unit">К</span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p7') + readButton('p7');
renderMath(box);
wireReadBtn('p7');
_initP7_iv6();
_initP7_calc();
_initP7_quiz();
_initP7_dnd();
_initP7_tasks();
}
function _initP7_calc(){
const svg = document.getElementById('p7-sim'); if(!svg) return;
function update(){
const q = +document.getElementById('p7-fuel').value;
const m = +document.getElementById('p7-m').value;
document.getElementById('p7-mv').textContent = m.toFixed(1);
document.getElementById('p7-qv').innerHTML = q.toExponential(1).replace('+','').replace('e', ' &times; 10<sup>')+'</sup>';
const Q = q * m;
document.getElementById('p7-q').innerHTML = (Q/1e6).toFixed(1)+' МДж';
document.getElementById('p7-qkw').textContent = (Q/3.6e6).toFixed(2);
/* эквивалент: нагрев воды от 20 до 100 → ΔT=80, Q=cmΔT, m = Q/(c·80) */
document.getElementById('p7-eqkg').textContent = (Q/(4200*80)).toFixed(1);
/* SVG: огонь + котелок */
let s = '';
/* печь */
s += '<rect x="40" y="50" width="120" height="80" fill="#374151" stroke="#0f172a" stroke-width="2" rx="4"/>';
/* пламя */
const flameH = 30 + Math.min(50, m*5);
s += '<path d="M 70 90 Q 65 '+(90-flameH)+' 80 '+(80-flameH/2)+' Q 90 '+(85-flameH)+' 100 '+(70-flameH/2)+' Q 110 '+(85-flameH)+' 120 '+(80-flameH/2)+' Q 135 '+(90-flameH)+' 130 90 Z" fill="#f59e0b"/>';
s += '<path d="M 80 90 Q 78 '+(90-flameH*0.6)+' 90 '+(85-flameH*0.3)+' Q 100 '+(88-flameH*0.6)+' 110 '+(85-flameH*0.3)+' Q 122 '+(90-flameH*0.6)+' 120 90 Z" fill="#fbbf24"/>';
/* топливо */
s += '<text x="100" y="115" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#fde047">'+m.toFixed(1)+' кг</text>';
/* стрелка-энергия */
s += '<text x="200" y="60" font-family="JetBrains Mono,monospace" font-size="13" fill="#0f172a">Q = q m</text>';
s += '<text x="200" y="84" font-family="JetBrains Mono,monospace" font-size="12" fill="#475569">= '+q.toExponential(1).replace('+','')+' &middot; '+m.toFixed(1)+'</text>';
s += '<text x="200" y="108" font-family="JetBrains Mono,monospace" font-size="14" font-weight="800" fill="#dc2626">Q = '+(Q/1e6).toFixed(1)+' МДж</text>';
s += '<text x="200" y="128" font-family="Inter,sans-serif" font-size="11" fill="#475569">'+(Q/3.6e6).toFixed(2)+' кВт&middot;ч</text>';
svg.innerHTML = s;
}
document.getElementById('p7-fuel').addEventListener('change', update);
document.getElementById('p7-m').addEventListener('input', update);
update();
}
function _initP7_quiz(){
const QS = [
{A:'дрова', B:'бензин', ans:'B', why:'У бензина $q \\approx 4{,}6 \\cdot 10^7$, у дров $\\approx 10^7$ — бензин в 4,5 раза мощнее.'},
{A:'торф', B:'каменный уголь', ans:'B', why:'У угля $q$ вдвое больше, чем у торфа.'},
{A:'спирт', B:'природный газ', ans:'B', why:'У газа $4{,}4 \\cdot 10^7$, у спирта $2{,}7 \\cdot 10^7$.'},
{A:'керосин', B:'дрова', ans:'A', why:'Керосин — продукт переработки нефти, $q$ в 4,6 раза больше.'},
{A:'нефть', B:'бензин', ans:'B', why:'У бензина чуть больше: $4{,}6$ против $4{,}4 \\cdot 10^7$.'},
{A:'торф', B:'дрова', ans:'B', why:'У дров $q \\approx 10^7$, у торфа $1{,}5 \\cdot 10^7$ — торф мощнее. Правильный ответ: <b>торф</b>, ответ A.', flip:true}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p7-quiz'); if(!wrap) return;
/* для flip-задачи правильный ответ A */
const correctAns = q.flip ? 'A' : q.ans;
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"><b>A.</b> '+q.A+'</button>'
+'<button class="btn" data-pick="B" style="padding:14px"><b>B.</b> '+q.B+'</button>'
+'</div>'
+'<div class="feedback" id="p7-quiz-fb"></div>';
document.getElementById('p7-quiz-r').textContent = (i+1);
document.getElementById('p7-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('p7-quiz-fb');
if(btn.dataset.pick === correctAns){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p7-quiz'); bumpProgress('p7', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p7-quiz-ok').textContent = ok;
renderMath(wrap);
});
});
renderMath(wrap);
}
document.getElementById('p7-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP7_dnd(){
/* 5 топлив по возрастанию q: дрова(1) → торф(1.5) → спирт(2.7) → уголь(3) → бензин(4.6), все ×10^7 */
const items = [
{id:'wd', cat:'r1', html:'дрова ($10^7$)'},
{id:'pt', cat:'r2', html:'торф ($1{,}5 \\cdot 10^7$)'},
{id:'al', cat:'r3', html:'спирт ($2{,}7 \\cdot 10^7$)'},
{id:'cl', cat:'r4', html:'уголь ($3 \\cdot 10^7$)'},
{id:'gs', cat:'r5', html:'бензин ($4{,}6 \\cdot 10^7$)'}
];
const dnd = setupSorter({ poolId:'p7-dnd-pool', scopeSelector:'#sec-p7', cats:['r1','r2','r3','r4','r5'], items, columnLayout:false });
document.getElementById('p7-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p7-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,'p7-dnd'); bumpProgress('p7', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: бензин — самый мощный из распространённых.'; }
});
document.getElementById('p7-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p7-dnd-fb'); fb.style.display='none'; });
}
function _initP7_tasks(){
const TASKS = [
{q:'Сколько энергии (в МДж) выделится при полном сгорании 2 кг бензина? ($q = 4{,}6 \\cdot 10^7$)', ans: 92, tol: 2, why:'$Q = qm = 4{,}6 \\cdot 10^7 \\cdot 2 = 9{,}2 \\cdot 10^7 = 92$ МДж.'},
{q:'Какую массу (в кг) дров нужно сжечь, чтобы получить 50 МДж энергии? ($q = 10^7$)', ans: 5, tol: 0.1, why:'$m = Q/q = 5 \\cdot 10^7 / 10^7 = 5$ кг.'},
{q:'При сгорании 0,5 кг угля выделилось 15 МДж. Чему равно $q$ (в МДж/кг)?', ans: 30, tol: 1, why:'$q = Q/m = 15/0{,}5 = 30$ МДж/кг — это каменный уголь.'},
{q:'Котёл с $\\eta = 80\\%$ сжёг 2 кг дров. Какая полезная энергия (в МДж)? ($q = 10^7$)', ans: 16, tol: 0.3, why:'$Q_{сгор}=2 \\cdot 10^7 = 20$ МДж. $Q_{пол} = \\eta \\cdot Q_{сгор} = 0{,}8 \\cdot 20 = 16$ МДж.'},
{q:'Сколько кг бензина даст столько же энергии, что и 9,2 кг дров? ($q_{дров}=10^7$, $q_{бенз}=4{,}6 \\cdot 10^7$)', ans: 2, tol: 0.05, why:'Энергия дров: $9{,}2 \\cdot 10^7$ Дж. $m_{бенз}=E/q_{бенз}=9{,}2 \\cdot 10^7/(4{,}6 \\cdot 10^7) = 2$ кг.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p7-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="p7-task-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p7-task-go">Ответ</button>'
+'<button class="btn" id="p7-task-hint">Подсказка</button>'
+'<button class="btn" id="p7-task-next">Следующая</button></div>'
+'<div class="boss-hint-txt" id="p7-task-hint-txt">'+t.why+'</div>'
+'<div class="feedback" id="p7-task-fb"></div>';
document.getElementById('p7-task-i').textContent = (i+1);
document.getElementById('p7-task-ok').textContent = ok;
document.getElementById('p7-task-go').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p7-task-inp').value || '').replace(',','.'));
const fb = document.getElementById('p7-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,'p7-task'); bumpProgress('p7', 6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Правильный ответ: '+t.ans+'. '+t.why; }
document.getElementById('p7-task-ok').textContent = ok;
renderMath(wrap);
if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p7-task-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p7-task-bonus'); bumpProgress('p7', 15); }, 600); }
});
document.getElementById('p7-task-hint').addEventListener('click', ()=>{ document.getElementById('p7-task-hint-txt').classList.toggle('show'); });
document.getElementById('p7-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======================================================================
PHASE 1 · WAVE 4 — §8, §9
====================================================================== */
/* Таблица температур плавления и λ */
const MAT_MELT = [
{key:'mercury',name:'ртуть', Tm:-39, lam:1.18e4},
{key:'ice', name:'лёд', Tm:0, lam:3.34e5},
{key:'lead', name:'свинец', Tm:327, lam:2.5e4},
{key:'zinc', name:'цинк', Tm:420, lam:1.12e5},
{key:'al', name:'алюминий', Tm:660, lam:3.9e5},
{key:'cu', name:'медь', Tm:1085, lam:2.1e5},
{key:'fe', name:'железо', Tm:1539, lam:2.7e5}
];
/* ======== §8 — Плавление и кристаллизация ======== */
function build_p8(){
const box = document.getElementById('p8-body');
let h = '';
h += makeCard('theory', 'Плавление', '§ 8.1',
'<p><b>Плавление</b> — переход вещества из твёрдого состояния в жидкое.</p>'
+'<p>У каждого <b>кристаллического</b> вещества есть своя <b>температура плавления</b> $T_{пл}$ — она одинакова и для плавления, и для обратного процесса — кристаллизации.</p>'
+'<p>Во время плавления температура смеси «твёрдое + жидкое» <b>не меняется</b>, пока всё не расплавится. Подведённая теплота тратится на разрушение кристаллической решётки.</p>'
);
h += makeCard('rule', 'График фазового перехода', '§ 8.2',
'<p>Если нагревать твёрдое тело и записывать температуру со временем, получится характерный график с <b>плато</b>:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li>нагрев твёрдого ($T$ растёт);</li>'
+'<li>плавление ($T = T_{пл} = $ const, идёт время);</li>'
+'<li>нагрев жидкости ($T$ снова растёт).</li>'
+'</ol>'
+'<p>При охлаждении — зеркальный процесс: $T$ падает $\\to$ плато кристаллизации $\\to$ $T$ продолжает падать.</p>'
);
h += makeCard('example', 'Примеры', '§ 8.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Лёд в стакане воды держит $T = 0$ &#176;C, пока не растает весь.</li>'
+'<li>Снег зимой переносит много энергии при таянии — поэтому весной долго холодно.</li>'
+'<li>Расплавленный металл (магма, чугун) застывает в кристаллы при остывании.</li>'
+'<li>Аморфные тела (стекло, смола) <b>не имеют</b> чёткой $T_{пл}$ — они плавятся плавно.</li>'
+'</ul>'
);
/* IV1 — главный визуал: график T(t) с плато */
let optsT = '';
MAT_MELT.forEach(m=>{ optsT += '<option value="'+m.Tm+'">'+m.name+' (T́пл = '+m.Tm+' °C)</option>'; });
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">График плавления $T(t)$</div></div>'
+'<div class="wg-help">Выбери вещество — увидь характерный «ступенчатый» график. Время плато ∝ массе и удельной теплоте плавления.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Вещество: <select id="p8-mat" class="tinp" style="width:auto;padding:6px 10px;font-size:.92rem">'+optsT+'</select></label>'
+'<label>Длительность плато: <b id="p8-pv">средняя</b><input type="range" id="p8-p" min="1" max="5" step="1" value="3"></label>'
+'</div>'
+'<svg id="p8-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:8px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span><span style="color:#dc2626">&#9632;</span> участок 1 — нагрев твёрдого</span>'
+'<span><span style="color:#f59e0b">&#9632;</span> участок 2 — плавление ($T = T_{пл}$)</span>'
+'<span><span style="color:#2563eb">&#9632;</span> участок 3 — нагрев жидкости</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="p8-quiz"></div>'
+'<div class="actions"><button class="btn" id="p8-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p8-quiz-r">1</b> / 6</span><span>Правильно: <b id="p8-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD веществ по T_пл */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Расставь по $T_{пл}$ (по возрастанию)</div></div>'
+'<div class="wg-help">От самого «мягкого» (плавится при низкой $T$) до самого тугоплавкого.</div>'
+'<div id="p8-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="p8-dnd-check">Проверить</button><button class="btn" id="p8-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p8-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="p8-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p8-mcq-i">1</b> / 6</span><span>Правильно: <b id="p8-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="p8-tasks5"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p8-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p8-tasks5-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+' (λ = '+(m.lam/1e3)+' кДж/кг)</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 — Lambda meter (Phase 1.3) */
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">Удельная теплота плавления: $Q = \\lambda m$</div></div>'
+'<div class="wg-help">Выбери вещество и массу — рассчитаем энергию, нужную для полного плавления. $Q = \\lambda \\cdot m$, где $\\lambda$ — удельная теплота плавления.</div>'
+'<div class="p8-sandbox" id="p9-iv6-sandbox" style="height:200px"></div>'
+'<div style="margin-top:10px;display:flex;gap:14px;flex-wrap:wrap;align-items:center">'
+'<select id="p9-iv6-mat" class="tinp" style="font-family:var(--p8-body)">'
+'<option value="ice">Лёд (λ=330 кДж/кг)</option>'
+'<option value="lead">Свинец (λ=25 кДж/кг)</option>'
+'<option value="al">Алюминий (λ=380 кДж/кг)</option>'
+'<option value="iron">Железо (λ=270 кДж/кг)</option>'
+'</select>'
+'<div class="p8-scrubber" style="flex:1;min-width:200px"><span class="p8-scrubber-label">Масса</span><input type="range" id="p9-iv6-m" min="0.1" max="5" step="0.1" value="1"><span class="p8-scrubber-value"><span id="p9-iv6-m-val">1.0</span><span class="p8-unit">кг</span></span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">Q</span><span class="p8-readout-value" id="p9-iv6-q">330</span><span class="p8-readout-unit">кДж</span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p9') + readButton('p9');
renderMath(box);
wireReadBtn('p9');
_initP9_iv6();
_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 — Evaporation (Phase 1.3) */
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="p10-iv6-sandbox" style="height:220px"></div>'
+'<div style="margin-top:10px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px">'
+'<div class="p8-scrubber" style="min-width:0"><span class="p8-scrubber-label" style="min-width:0;white-space:nowrap">T</span><input type="range" id="p10-iv6-t" min="0" max="100" step="1" value="50"><span class="p8-scrubber-value"><span id="p10-iv6-t-val">50</span><span class="p8-unit">°C</span></span></div>'
+'<div class="p8-scrubber" style="min-width:0"><span class="p8-scrubber-label" style="min-width:0;white-space:nowrap">Площадь</span><input type="range" id="p10-iv6-s" min="0.01" max="1" step="0.01" value="0.3"><span class="p8-scrubber-value"><span id="p10-iv6-s-val">0.30</span><span class="p8-unit">м²</span></span></div>'
+'<div class="p8-scrubber" style="min-width:0"><span class="p8-scrubber-label" style="min-width:0;white-space:nowrap">Ветер</span><input type="range" id="p10-iv6-w" min="0" max="10" step="0.1" value="2"><span class="p8-scrubber-value"><span id="p10-iv6-w-val">2.0</span><span class="p8-unit">м/с</span></span></div>'
+'</div>'
+'<div style="margin-top:6px;display:flex;gap:10px"><div class="p8-readout"><span class="p8-readout-label">Скорость испарения</span><span class="p8-readout-value" id="p10-iv6-rate">—</span><span class="p8-readout-unit">отн.</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('p10') + readButton('p10');
renderMath(box);
wireReadBtn('p10');
_initP10_iv6();
_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 — Pressure cooker (Phase 1.3) */
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">При нормальном давлении вода кипит при 100°C. В скороварке давление 2 атм — T кипения растёт до 120°C. В горах (0.7 атм) — снижается до 90°C. Кривая зависимости — упрощённая модель.</div>'
+'<div class="p8-sandbox" id="p11-iv6-sandbox" style="height:240px"></div>'
+'<div style="margin-top:10px;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="p11-iv6-p" min="0.5" max="3" step="0.05" value="1"><span class="p8-scrubber-value"><span id="p11-iv6-p-val">1.0</span><span class="p8-unit">атм</span></span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">T кипения</span><span class="p8-readout-value" id="p11-iv6-tboil">100</span><span class="p8-readout-unit">°C</span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p11') + readButton('p11');
renderMath(box);
wireReadBtn('p11');
_initP11_iv6();
_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>