Files
Learn_System/frontend/textbooks/physics_8_ch1.html
T
Maxim Dolgolyov 244a063363 feat(phys8 ch1): Phase 1 Wave 1 — §1 «Внутренняя энергия» + §2 «Способы изменения U»
§1 — Внутренняя энергия:
- 3 теории: определение U, факторы зависимости, сравнение состояний
- IV-1: симуляция «холодный vs горячий газ» — 2 сосуда с молекулами,
  скорость ∝ √T_K, цвет по tempColor
- IV-2: викторина из 6 раундов «У какого тела U больше?»
- IV-3: DnD на 8 факторов «Зависит / Не зависит»
- IV-4: MCQ-тренажёр на 6 вопросов с XP-наградой

§2 — Способы изменения внутренней энергии:
- 3 теории: 2 способа, 3 вида теплопередачи, примеры из жизни
- IV-1: двойная анимация «работа (брусок-трение) vs теплопередача
  (контакт горячее+холодное)» с термометром и стрелками потока тепла
- IV-2: викторина из 8 ситуаций «работа или теплопередача?»
- IV-3: DnD-сортировка 8 ситуаций по 2 категориям
- IV-4: MCQ-тренажёр с XP-бонусом

Инфраструктура: _SIMS, _killSim, _isVisible — управление RAF для
паузы симуляций при переключении секций.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 22:50:01 +03:00

1314 lines
96 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Физика 8 · Глава 1 · «Тепловые явления»</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<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/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(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>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 8 · Глава 1</h1>
<div class="hdr-sub">Внутренняя энергия · теплопередача · фазовые переходы</div>
</div>
<div class="hdr-side">
<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="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="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="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="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="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="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="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="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="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="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="sec-header"><span class="sec-num">&sect; 11</span><h2 class="sec-h">Кипение жидкостей. Удельная теплота парообразования</h2></div><div id="p11-body"></div></section>
<section id="sec-final1" class="sec"><div class="sec-header"><span class="sec-num">&#9733;</span><h2 class="sec-h">Финал главы</h2></div><div id="final1-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Физика 8» · Глава 1 · «Тепловые явления» · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<div id="search-modal" class="search-modal" role="dialog">
<div class="search-box">
<input type="text" id="search-input" class="search-input" placeholder="Поиск…" autocomplete="off">
<div id="search-results" class="search-results"></div>
<div class="search-foot"><span><kbd>↑↓</kbd> навигация</span><span><kbd>Enter</kbd> открыть</span><span><kbd>Esc</kbd> закрыть</span></div>
</div>
</div>
<script>
'use strict';
const STATE = { current:'p1', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 12;
const _TB_SLUG = 'physics-8-ch1';
const LS_PREFIX = 'physics8_ch1';
const LS_XP = 'physics8_xp';
const PARAS = [
{ id:'p1', num:'\u00a7 1', name:'Внутренняя энергия', sub:'$U$ зависит от $T$' },
{ id:'p2', num:'\u00a7 2', name:'Способы изменения внутренней энергии', sub:'Работа и теплопередача' },
{ id:'p3', num:'\u00a7 3', name:'Теплопроводность', sub:'Передача без переноса в-ва' },
{ id:'p4', num:'\u00a7 4', name:'Конвекция', sub:'Перенос потоками' },
{ id:'p5', num:'\u00a7 5', name:'Излучение', sub:'Тепловое излучение' },
{ id:'p6', num:'\u00a7 6', name:'Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость', sub:'$Q = cm\\Delta T$' },
{ id:'p7', num:'\u00a7 7', name:'Горение. Удельная теплота сгорания топлива', sub:'$Q = qm$' },
{ id:'p8', num:'\u00a7 8', name:'Плавление и кристаллизация', sub:'$T_{пл}$, графики $T(t)$' },
{ id:'p9', num:'\u00a7 9', name:'Удельная теплота плавления и кристаллизации', sub:'$Q = \\lambda m$' },
{ id:'p10', num:'\u00a7 10', name:'Испарение жидкостей. Факторы, влияющие на скорость испарения', sub:'Зависит от $T$, $S$' },
{ id:'p11', num:'\u00a7 11', name:'Кипение жидкостей. Удельная теплота парообразования', sub:'$Q = Lm$' },
{ id:'final1', num:'\u2605', name:'Финал главы', sub:'Итоги · 7 боссов', final:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
const ACH_LABELS = {
start:"Начало главы 1!",
p1_done:"Внутренняя энергия освоен!",
p2_done:"Способы изменения внутренней энергии освоен!",
p3_done:"Теплопроводность освоен!",
p4_done:"Конвекция освоен!",
p5_done:"Излучение освоен!",
p6_done:"Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость освоен!",
p7_done:"Горение. Удельная теплота сгорания топлива освоен!",
p8_done:"Плавление и кристаллизация освоен!",
p9_done:"Удельная теплота плавления и кристаллизации освоен!",
p10_done:"Испарение жидкостей. Факторы, влияющие на скорость испарения освоен!",
p11_done:"Кипение жидкостей. Удельная теплота парообразования освоен!",
ch1_done:"Глава 1 пройдена!"
};
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:[["В разработке","Phase 1 Wave 2"]]},
p4:{title:"Шпаргалка § 4",rows:[["В разработке","Phase 1 Wave 2"]]},
p5:{title:"Шпаргалка § 5",rows:[["В разработке","Phase 1 Wave 2"]]},
p6:{title:"Шпаргалка § 6",rows:[["В разработке","Phase 1 Wave 3"]]},
p7:{title:"Шпаргалка § 7",rows:[["В разработке","Phase 1 Wave 3"]]},
p8:{title:"Шпаргалка § 8",rows:[["В разработке","Phase 1 Wave 4"]]},
p9:{title:"Шпаргалка § 9",rows:[["В разработке","Phase 1 Wave 4"]]},
p10:{title:"Шпаргалка § 10",rows:[["В разработке","Phase 1 Wave 5"]]},
p11:{title:"Шпаргалка § 11",rows:[["В разработке","Phase 1 Wave 5"]]},
final1:{title:"Шпаргалка ★",rows:[["В разработке","Phase 1 Wave 5"]]}
};
const TIPS=[
{sec:'p1',html:"Тело состоит из молекул. Они движутся (есть $E_k$) и взаимодействуют (есть $E_p$). Их сумма — внутренняя энергия $U$. Главное: $U$ не зависит от того, движется ли тело и на какой высоте оно лежит."},
{sec:'p2',html:"Изменить $U$ можно двумя способами: совершить работу (трение, сжатие, удар) или передать тепло без работы (контакт, поток, излучение). Чай в стакане остывает — это теплопередача. Спички в коробке нагреваются от тряски — это работа."},
{sec:'p3',html:"Параграф § 3 будет реализован в Phase 1 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p4',html:"Параграф § 4 будет реализован в Phase 1 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p5',html:"Параграф § 5 будет реализован в Phase 1 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p6',html:"Параграф § 6 будет реализован в Phase 1 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p7',html:"Параграф § 7 будет реализован в Phase 1 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p8',html:"Параграф § 8 будет реализован в Phase 1 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p9',html:"Параграф § 9 будет реализован в Phase 1 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p10',html:"Параграф § 10 будет реализован в Phase 1 Wave 5. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p11',html:"Параграф § 11 будет реализован в Phase 1 Wave 5. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'final1',html:"Параграф ★ будет реализован в Phase 1 Wave 5. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."}
];
const BUILDERS = {
p1: ()=>{ build_p1(); },
p2: ()=>{ build_p2(); },
p3: ()=>{ const box=document.getElementById('p3-body'); box.innerHTML = buildStub('p3', 'Теплопроводность', 'Phase 1 Wave 2') + secNavFor('p3') + readButton('p3'); renderMath(box); wireReadBtn('p3'); },
p4: ()=>{ const box=document.getElementById('p4-body'); box.innerHTML = buildStub('p4', 'Конвекция', 'Phase 1 Wave 2') + secNavFor('p4') + readButton('p4'); renderMath(box); wireReadBtn('p4'); },
p5: ()=>{ const box=document.getElementById('p5-body'); box.innerHTML = buildStub('p5', 'Излучение', 'Phase 1 Wave 2') + secNavFor('p5') + readButton('p5'); renderMath(box); wireReadBtn('p5'); },
p6: ()=>{ const box=document.getElementById('p6-body'); box.innerHTML = buildStub('p6', 'Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость', 'Phase 1 Wave 3') + secNavFor('p6') + readButton('p6'); renderMath(box); wireReadBtn('p6'); },
p7: ()=>{ const box=document.getElementById('p7-body'); box.innerHTML = buildStub('p7', 'Горение. Удельная теплота сгорания топлива', 'Phase 1 Wave 3') + secNavFor('p7') + readButton('p7'); renderMath(box); wireReadBtn('p7'); },
p8: ()=>{ const box=document.getElementById('p8-body'); box.innerHTML = buildStub('p8', 'Плавление и кристаллизация', 'Phase 1 Wave 4') + secNavFor('p8') + readButton('p8'); renderMath(box); wireReadBtn('p8'); },
p9: ()=>{ const box=document.getElementById('p9-body'); box.innerHTML = buildStub('p9', 'Удельная теплота плавления и кристаллизации', 'Phase 1 Wave 4') + secNavFor('p9') + readButton('p9'); renderMath(box); wireReadBtn('p9'); },
p10: ()=>{ const box=document.getElementById('p10-body'); box.innerHTML = buildStub('p10', 'Испарение жидкостей. Факторы, влияющие на скорость испарения', 'Phase 1 Wave 5') + secNavFor('p10') + readButton('p10'); renderMath(box); wireReadBtn('p10'); },
p11: ()=>{ const box=document.getElementById('p11-body'); box.innerHTML = buildStub('p11', 'Кипение жидкостей. Удельная теплота парообразования', 'Phase 1 Wave 5') + secNavFor('p11') + readButton('p11'); renderMath(box); wireReadBtn('p11'); },
final1: ()=>{ const box=document.getElementById('final1-body'); box.innerHTML = buildStub('final1', 'Финал главы', 'Phase 1 Wave 5') + secNavFor('final1') + readButton('final1'); renderMath(box); wireReadBtn('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>';
box.innerHTML = h + secNavFor('p1') + readButton('p1');
renderMath(box);
wireReadBtn('p1');
_initP1_sim();
_initP1_quiz();
_initP1_dnd();
_initP1_mcq();
}
/* === §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>';
box.innerHTML = h + secNavFor('p2') + readButton('p2');
renderMath(box);
wireReadBtn('p2');
_initP2_sim();
_initP2_quiz();
_initP2_dnd();
_initP2_mcq();
}
/* === §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();
}
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>
</body>
</html>