Files
Learn_System/frontend/textbooks/physics_10_ch1.html
T
Maxim Dolgolyov 9d5a2959e1 fix(textbooks): кнопка «Шпаргалка» не открывала контент на desktop
На десктопе (>980px) .col-side уже видна как sticky-колонка справа в grid 1fr 280px.
Клик по кнопке #sidebar-btn добавлял .col-side-backdrop.show — backdrop с
z-index:9990 затемнял всю страницу, перекрывая sticky-aside. Со стороны
выглядело как «ничего не открылось» — на самом деле появлялась чёрная вуаль.

Фикс: @media(min-width:981px) скрывает #sidebar-btn и подавляет показ backdrop.
На мобайле (≤980px) кнопка и overlay работают как раньше.

Применено в 51 файле: physics 8/9/10 chN, algebra 7/9/10/11 chN + 8 ch2-3,
geometry 7/8/9/11 chN, geometry_10 r1-4.
2026-05-30 09:51:04 +03:00

4729 lines
312 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>Физика 10 · Глава 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>
<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:#fafafa; --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:#2563eb; --pri2:#1d4ed8; --pri-soft:#dbeafe;
--acc:#60a5fa; --acc2:#2563eb; --acc-soft:#dbeafe;
--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,#1e3a8a 0%,#2563eb 55%,#60a5fa 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 1';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.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::before{content:'T';position:absolute;right:0;top:-30px;font-size:clamp(2rem,12vw,8rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
.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:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p2"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p3"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p4"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p5"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p6"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p7"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p8"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p9"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p10"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-final1"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.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::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
.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)}
.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s;position:relative;overflow:hidden}
.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
.boss-card.pulse{animation:bossPulse .8s ease-out}
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-pool.col .dnd-chip{width:auto}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
.dnd-chip:active{cursor:grabbing}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(0,0,0,.10);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
.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}
/* === PHYS10 POLISH (visual + micro-interactions) === */
@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
.sec.active .wg:nth-of-type(1){animation-delay:.02s}
.sec.active .wg:nth-of-type(2){animation-delay:.08s}
.sec.active .wg:nth-of-type(3){animation-delay:.14s}
.sec.active .wg:nth-of-type(4){animation-delay:.20s}
.sec.active .wg:nth-of-type(5){animation-delay:.26s}
.sec.active .wg:nth-of-type(6){animation-delay:.32s}
.wg svg{transition:filter .25s ease}
.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
.wg input[type=range]{cursor:ew-resize}
.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
.score-display b.bump{transform:scale(1.28);color:var(--pri)}
.katex{transition:color .2s}
.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
.psel-card{position:relative}
.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.psel-card.done .psel-done{display:flex}
.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
.boss-card.pulse{animation:bossPulse .8s ease-out}
.sec{transition:opacity .25s}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 10 · Глава 1</h1>
<div class="hdr-sub">Молекулярно-кинетическая теория · идеальный газ · изопроцессы · влажность</div>
</div>
<div class="hdr-side">
<a href="/textbook/physics-10" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 10</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" data-watermark="МКТ"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">Основные положения МКТ</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="N_A"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Масса и размеры молекул. Количество вещества</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="pV"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Идеальный газ. Основное уравнение МКТ</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="T"><div class="sec-header"><span class="sec-num">§ 4</span><h2 class="sec-h">Температура. Тепловое равновесие</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="pV=&nu;RT"><div class="sec-header"><span class="sec-num">§ 5</span><h2 class="sec-h">Уравнение состояния идеального газа</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec" data-watermark="iso"><div class="sec-header"><span class="sec-num">§ 6</span><h2 class="sec-h">Изопроцессы</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec" data-watermark="cryst"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Строение и свойства твёрдых тел</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec" data-watermark="&sigma;"><div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Строение и свойства жидкостей</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec" data-watermark="пар"><div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Испарение и конденсация. Насыщенный пар</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec" data-watermark="&phi;"><div class="sec-header"><span class="sec-num">§ 10</span><h2 class="sec-h">Влажность воздуха</h2></div><div id="p10-body"></div></section>
<section id="sec-final1" class="sec" data-watermark="&#9733;"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#2563eb,#60a5fa)"></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">Интерактивный учебник «Физика 10» · Глава 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 = 11;
const _TB_SLUG = 'physics-10-ch1';
const PARAS = [
{ id:'p1', num:'\u00a7 1', name:"Основные положения МКТ", sub:'Положения МКТ' },
{ id:'p2', num:'\u00a7 2', name:"Масса и размеры молекул. Количество вещества", sub:'$N_A = 6{,}022 \\cdot 10^{23}$' },
{ id:'p3', num:'\u00a7 3', name:"Идеальный газ. Основное уравнение МКТ", sub:'$p = \\dfrac{1}{3}nm\\overline{v^2}$' },
{ id:'p4', num:'\u00a7 4', name:"Температура. Тепловое равновесие", sub:'$\\overline{E_k} = \\dfrac{3}{2}kT$' },
{ id:'p5', num:'\u00a7 5', name:"Уравнение состояния идеального газа", sub:'$pV = \\nu RT$' },
{ id:'p6', num:'\u00a7 6', name:"Изопроцессы", sub:'$pV/T = \\text{const}$' },
{ id:'p7', num:'\u00a7 7', name:"Строение и свойства твёрдых тел", sub:'Кристаллы' },
{ id:'p8', num:'\u00a7 8', name:"Строение и свойства жидкостей", sub:'Поверхностное натяжение' },
{ id:'p9', num:'\u00a7 9', name:"Испарение и конденсация. Насыщенный пар", sub:'Насыщенный пар' },
{ id:'p10', num:'\u00a7 10', name:"Влажность воздуха", sub:'$\\varphi = p/p_н$' },
{ id:'final1', num:'\u2605', name:'Финал главы', sub:'Итоги \u00b7 боссы главы 1', final:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
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:"Влажность воздуха освоен!",
ch1_done:"Глава 1 пройдена!"
};
function loadProgress(){
try{
const s=localStorage.getItem('physics10_ch1_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics10_ch1_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('physics10_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('physics10_ch1_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics10_ch1_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('physics10_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,'physics10-ch1-'+(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();
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(), final1:()=>build_final1() };
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);
}
const SIDEBARS = {
p1:{title:"Шпаргалка §1",rows:[["Положения","3 положения МКТ: все вещества из частиц, частицы движутся, взаимодействуют"],["Опыты","Броуновское движение, диффузия"],["Размер","$d \\sim 10^{-10}$ м"]]},
p2:{title:"Шпаргалка §2",rows:[["Авогадро","$N_A = 6{,}022 \\cdot 10^{23}$ 1/моль"],["Количество в-ва","$\\nu = N/N_A = m/M$"],["Молярная масса","$M$ — кг/моль"]]},
p3:{title:"Шпаргалка §3",rows:[["Идеальный газ","точечные частицы, упругие столкновения"],["Осн. ур-ие МКТ","$p = \\tfrac{1}{3}nm\\overline{v^2}$"],["Концентрация","$n = N/V$"]]},
p4:{title:"Шпаргалка §4",rows:[["Темпер.","$T$ — мера ср. кин. энергии"],["$\\overline{E_k}$","$\\overline{E_k} = \\tfrac{3}{2}kT$"],["Шкалы","$T_K = t + 273{,}15$"]]},
p5:{title:"Шпаргалка §5",rows:[["Менделеев-Клапейрон","$pV = \\nu RT$"],["Клапейрон","$\\frac{pV}{T} = \\text{const}$"],["$R$","$R = 8{,}314$ Дж/(моль·К)"]]},
p6:{title:"Шпаргалка §6",rows:[["Изотерма","$T = \\text{const}$: $pV = \\text{const}$ (Бойль-Мариотт)"],["Изобара","$p = \\text{const}$: $V/T = \\text{const}$ (Гей-Люссак)"],["Изохора","$V = \\text{const}$: $p/T = \\text{const}$ (Шарль)"]]},
p7:{title:"Шпаргалка §7",rows:[["Крист.","дальний порядок"],["Аморф.","ближний порядок, нет $T_{пл}$"],["Деформ.","упругая, пластическая"]]},
p8:{title:"Шпаргалка §8",rows:[["Жидкость","ближний порядок, текучесть"],["Поверх. натяж.","$\\sigma$ — Н/м"],["Капилляр","смачивание"]]},
p9:{title:"Шпаргалка §9",rows:[["Испар.","с поверхности"],["Кипение","$p_{нас} = p_{внеш}$"],["Нас. пар","динам. равновесие"]]},
p10:{title:"Шпаргалка §10",rows:[["Абс. вл.","$\\rho_{пара}$ — кг/м³"],["Отн. вл.","$\\varphi = p/p_{нас} \\cdot 100\\%$"],["Точка росы","$T$, при которой $\\varphi = 100\\%$"]]},
final1:{title:"Финал главы 1",rows:[["§§110","теория главы 1"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p1',html:"Положения МКТ: все вещества из частиц, частицы движутся, взаимодействуют. Доказательства — диффузия, броуновское движение."},
{sec:'p2',html:"$N_A = 6{,}022 \\cdot 10^{23}$ — число частиц в 1 моле. $\\nu = m/M = N/N_A$."},
{sec:'p3',html:"Идеальный газ: точечные молекулы, упругие столкновения. Основное ур-ие МКТ: $p = \\tfrac{1}{3}nm\\overline{v^2}$."},
{sec:'p4',html:"$T$ — мера ср. кин. энергии: $\\overline{E_k} = \\tfrac{3}{2}kT$. Абс. ноль $T = 0$ К $= -273{,}15$°C."},
{sec:'p5',html:"Уравнение Менделеева-Клапейрона: $pV = \\nu RT$, где $R = 8{,}314$ Дж/(моль·К)."},
{sec:'p6',html:"Изопроцессы: при фиксации одного параметра ($T$, $p$ или $V$). Бойль-Мариотт, Гей-Люссак, Шарль."},
{sec:'p7',html:"Кристалл — дальний порядок, $T_{пл}$ определена. Аморфные — ближний порядок, плавятся плавно."},
{sec:'p8',html:"Жидкость имеет $V$, но не имеет форму. Поверхностное натяжение $\\sigma$ создаёт «плёнку» на поверхности."},
{sec:'p9',html:"Насыщенный пар — пар в динамическом равновесии с жидкостью. $p_{нас}$ зависит только от $T$."},
{sec:'p10',html:"Отн. влажность: $\\varphi = p_{пара}/p_{нас} \\cdot 100\\%$. При $\\varphi = 100\\%$ — точка росы."},
{sec:'final1',html:"Финал главы 1 — интегрированные задачи по §§1–10. В разработке (Phase 1+)."}
];
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('physics10_ch1_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('physics10_ch1_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>';
}
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(); }};
}
/* === SVG-хелперы (axes2D, plotFunc, pointWithDrop, asymptote, snapToValue, геом.) === */
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){
const NAMES = {p1:'\xA71',p2:'\xA72',p3:'\xA73',p4:'\xA74',p5:'\xA75',p6:'\xA76',p7:'\xA77',p8:'\xA78',p9:'\xA79',p10:'\xA710',final1:'Финал'};
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> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[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 : '\xA7?');
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>'
+' Я прочитал — '+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);
});
}
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function build_p1(){
const box = document.getElementById('p1-body');
let html = '';
/* THEORY 1 — положения МКТ */
html += makeCard('theory', "Основные положения МКТ", "§1", `
<p><b>Молекулярно-кинетическая теория</b> (МКТ) объясняет строение и свойства веществ на основе движения и взаимодействия частиц (атомов, молекул, ионов).</p>
<p style="margin-top:10px"><b>Три основных положения МКТ:</b></p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li><b>Вещество состоит из частиц.</b> Молекулы и атомы имеют размер около $10^{-10}$ м.</li>
<li><b>Частицы движутся непрерывно и хаотически.</b> Это движение называется <b>тепловым</b>.</li>
<li><b>Частицы взаимодействуют между собой</b>: на малых расстояниях — отталкиваются, на больших — притягиваются.</li>
</ol>
<p>Все макроскопические свойства (температура, давление, плотность, теплопроводность и т. д.) объясняются микроскопическими процессами.</p>
`);
/* THEORY 2 — опытные подтверждения */
html += makeCard('rule', "Опытные подтверждения МКТ", "§1", `
<p><b>Броуновское движение</b> (Р. Броун, 1827): хаотическое движение мелких частиц (например, пыльцы в воде) под действием ударов молекул. Чем меньше частица и выше температура — тем интенсивнее движение.</p>
<p style="margin-top:8px"><b>Диффузия</b> — самопроизвольное перемешивание веществ за счёт движения молекул. Скорость диффузии возрастает с температурой.</p>
<p style="margin-top:8px"><b>Сжимаемость газов</b> и <b>тепловое расширение</b> тел — также прямое следствие движения и взаимодействия молекул.</p>
`);
/* THEORY 3 — размеры и числа */
html += makeCard('example', "Размеры и количество молекул", "§1", `
<ul style="margin:0 0 8px 22px;line-height:1.75">
<li>Размер молекулы: $\\sim 10^{-10}$ м (нанометр или ангстрем).</li>
<li>Размер атома водорода: $\\sim 0{,}5 \\cdot 10^{-10}$ м.</li>
<li>В $1 \\text{ см}^3$ воды примерно $3{,}3 \\cdot 10^{22}$ молекул.</li>
<li>В $1$ моль вещества — $N_A = 6{,}022 \\cdot 10^{23}$ молекул (число Авогадро).</li>
</ul>
<p><b>Пример:</b> если выпить стакан воды (200 мл) и подождать долгое время, в любом другом стакане воды через некоторое время будут несколько молекул из выпитого — настолько их много в любом веществе.</p>
`);
/* INTERACTIVE 1 — Симуляция броуновского движения */
html += `<div class="wg" id="p1-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симуляция броуновского движения</div></div>
<div class="wg-help">Оранжевая «броуновская» частица среди синих молекул газа. Меняй температуру — наблюдай хаотическое движение.</div>
<div class="sliders">
<label>Температура: <b id="p1-iv1-tL">300</b> К <input type="range" id="p1-iv1-T" min="100" max="500" value="300" step="10"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p1-iv1-svg" viewBox="0 0 420 320" width="100%" style="max-width:420px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p1-iv1-pause">Пауза</button>
<button class="btn" id="p1-iv1-reset">Сброс</button>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.88rem;line-height:1.5">
Чем выше $T$, тем сильнее удары молекул, тем хаотичнее движется броуновская частица. Жёлтый след показывает её траекторию.
</div>
</div>`;
/* INTERACTIVE 2 — DnD сортер: положение МКТ ↔ опыт */
html += `<div class="wg" id="p1-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Положение МКТ ↔ опыт</div></div>
<div class="wg-help">Перетащи каждое явление в соответствующее положение МКТ.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 6 явлений — 3 положения</div>
<div id="p1-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="c1">1. Вещество из частиц</h5><div class="drop-items" data-cat="c1"></div></div>
<div class="drop-box"><h5 data-cat="c2">2. Частицы движутся</h5><div class="drop-items" data-cat="c2"></div></div>
<div class="drop-box"><h5 data-cat="c3">3. Частицы взаимодействуют</h5><div class="drop-items" data-cat="c3"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p1-iv2-check">Проверить</button><button class="btn" id="p1-iv2-reset">Сначала</button></div>
<div class="feedback" id="p1-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Квикфайр: Верно/Неверно */
html += `<div class="wg" id="p1-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Проверь знания МКТ</div></div>
<div class="wg-help">8 утверждений. Выбери «Верно» или «Неверно». Допуск ошибок — 1.</div>
<div class="score-display"><span>Задача <b id="p1-iv3-i">1</b> / 8</span><span>Очки: <b id="p1-iv3-s">0</b> / 8</span></div>
<div id="p1-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p1-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p1-iv3-fb"></div>
<div class="actions"><button class="btn" id="p1-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр чисел */
html += `<div class="wg" id="p1-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: размеры и числа</div></div>
<div class="wg-help">5 задач на число Авогадро, размеры молекул, моли. Допуск $\\pm 5\\%$. Используй $N_A = 6 \\cdot 10^{23}$.</div>
<div class="score-display"><span>Задача <b id="p1-iv4-i">1</b> / 5</span><span>Очки: <b id="p1-iv4-s">0</b> / 5</span></div>
<div id="p1-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p1-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p1-iv4-go">Проверить</button>
<button class="btn" id="p1-iv4-start">Заново</button>
</div>
<div class="feedback" id="p1-iv4-fb"></div>
</div>`;
html += secNav(null, 'p2');
html += readButton('p1');
box.innerHTML = html;
renderMath(box);
/* IV1 — Броуновское движение */
(function(){
const svg = document.getElementById('p1-iv1-svg');
const tInp = document.getElementById('p1-iv1-T');
const tLab = document.getElementById('p1-iv1-tL');
const btnPause = document.getElementById('p1-iv1-pause');
const btnReset = document.getElementById('p1-iv1-reset');
const W = 420, H = 320;
const baseSpeed = 80;
const tempChanges = new Set();
let _xpDone = false;
let raf = null, lastT = 0, paused = false;
let sim = null;
let brown = null;
let trail = [];
function makeSim(){
sim = PHYS.createGasSim({W, H, N: 50, speed: baseSpeed, r: 3});
brown = { x: W/2, y: H/2, vx: 0, vy: 0, r: 12 };
trail = [];
// initial temp
applyTemp(+tInp.value);
}
function applyTemp(T){
// baseSpeed соответствует T0=300 К; scale = sqrt(T/T0)
const targetScale = Math.sqrt(T/300);
// компенсируем текущую среднюю скорость
let curAvg = 0;
for(const p of sim.particles){ curAvg += Math.hypot(p.vx, p.vy); }
curAvg /= sim.particles.length;
const targetAvg = baseSpeed * targetScale;
if(curAvg > 0.01) sim.setSpeed(targetAvg / curAvg);
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
sim.step(dt);
// обновляем броуновскую частицу: суммируем импульс от ближних молекул
let fx = 0, fy = 0;
const R = brown.r + 4;
for(const p of sim.particles){
const dx = brown.x - p.x, dy = brown.y - p.y;
const d2 = dx*dx + dy*dy;
if(d2 < R*R && d2 > 1){
const d = Math.sqrt(d2);
const f = (R - d) / R;
fx += dx/d * f * 60;
fy += dy/d * f * 60;
}
}
// демпфирование + импульс
brown.vx = brown.vx * 0.88 + fx * dt;
brown.vy = brown.vy * 0.88 + fy * dt;
brown.x += brown.vx * dt;
brown.y += brown.vy * dt;
// отражение от стенок
if(brown.x < brown.r){ brown.x = brown.r; brown.vx = -brown.vx*0.6; }
if(brown.x > W - brown.r){ brown.x = W - brown.r; brown.vx = -brown.vx*0.6; }
if(brown.y < brown.r){ brown.y = brown.r; brown.vy = -brown.vy*0.6; }
if(brown.y > H - brown.r){ brown.y = H - brown.r; brown.vy = -brown.vy*0.6; }
trail.push({x: brown.x, y: brown.y});
if(trail.length > 60) trail.shift();
render();
}
function render(){
let g = '';
// фон тёмный (уже в SVG), молекулы синие
g += sim.render('#60a5fa');
// след броуновской — polyline с убывающей opacity
if(trail.length > 1){
for(let i = 1; i < trail.length; i++){
const op = i / trail.length;
g += `<line x1="${trail[i-1].x.toFixed(1)}" y1="${trail[i-1].y.toFixed(1)}" x2="${trail[i].x.toFixed(1)}" y2="${trail[i].y.toFixed(1)}" stroke="#fbbf24" stroke-width="2" stroke-linecap="round" opacity="${op.toFixed(2)}"/>`;
}
}
// броуновская частица — оранжевая
g += `<circle cx="${brown.x.toFixed(1)}" cy="${brown.y.toFixed(1)}" r="${brown.r}" fill="#f97316" stroke="#fff" stroke-width="2"/>`;
svg.innerHTML = g;
}
makeSim();
raf = requestAnimationFrame(frame);
tInp.addEventListener('input', () => {
const T = +tInp.value;
tLab.textContent = T;
applyTemp(T);
tempChanges.add(T);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true;
addXp(10, 'p1-iv1');
bumpProgress('p1', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => {
makeSim();
trail = [];
});
// cleanup при потере фокуса (минимизируем нагрузку)
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — DnD: положение ↔ опыт */
(function(){
const items = [
{ id:'q1', cat:'c2', html:'Запах духов распространяется в комнате' },
{ id:'q2', cat:'c2', html:'Пыльца хаотично движется в воде' },
{ id:'q3', cat:'c1', html:'Воздух сжимается в насосе' },
{ id:'q4', cat:'c3', html:'Капля воды растекается по стеклу' },
{ id:'q5', cat:'c1', html:'Стальной шарик падает в воде медленнее, чем в воздухе' },
{ id:'q6', cat:'c2', html:'Газ заполняет весь сосуд' },
];
const sorter = setupSorter({
poolId:'p1-iv2-pool',
scopeSelector:'#p1-iv2',
items: items,
cats:['c1','c2','c3'],
columnLayout:false,
});
document.getElementById('p1-iv2-check').addEventListener('click', ()=>{
const fb = document.getElementById('p1-iv2-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 явлений.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 верно! +10 XP'); addXp(10,'p1-iv2'); bumpProgress('p1', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p1-iv2-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p1-iv2-fb').style.display = 'none'; });
})();
/* IV3 — квикфайр Верно/Неверно */
(function(){
const Q = [
{ q:'Молекулы вещества движутся хаотически.', ans:true, why:'Тепловое движение — это хаотическое движение молекул.' },
{ q:'При нагревании молекулы движутся медленнее.', ans:false, why:'Наоборот — при нагревании движение усиливается.' },
{ q:'Атомы можно увидеть в обычный световой микроскоп.', ans:false, why:'Размер атома $\\sim 10^{-10}$ м — нужен электронный микроскоп.' },
{ q:'Между молекулами действуют только силы притяжения.', ans:false, why:'На малых расстояниях — отталкивание, на больших — притяжение.' },
{ q:'Размер молекулы порядка $10^{-10}$ м.', ans:true, why:'Это типичный размер молекулы (≈ 1 ангстрем).' },
{ q:'Броуновское движение объясняется ударами молекул жидкости.', ans:true, why:'Хаотические удары молекул заставляют двигаться броуновские частицы.' },
{ q:'Диффузия в газах происходит быстрее, чем в жидкостях.', ans:true, why:'В газах молекулы движутся свободнее и быстрее.' },
{ q:'В 1 моль любого вещества одинаковое число молекул — $N_A$.', ans:true, why:'$N_A = 6{,}022 \\cdot 10^{23}$ — число Авогадро.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p1-iv3-q');
const oEl = document.getElementById('p1-iv3-opts');
const fb = document.getElementById('p1-iv3-fb');
const iEl = document.getElementById('p1-iv3-i');
const sEl = document.getElementById('p1-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p1-iv3'); bumpProgress('p1', 25); }
else if(score >= 5){ addXp(8, 'p1-iv3'); bumpProgress('p1', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="1">Верно</button><button class="btn primary" data-v="0" style="background:#ef4444;border-color:#ef4444">Неверно</button>';
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = b.dataset.v === '1';
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1400);
});
});
}
document.getElementById('p1-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр чисел */
(function(){
const Q = [
{ q:'В стакане воды $2{,}4 \\cdot 10^{24}$ молекул. Сколько это молей? ($N_A = 6 \\cdot 10^{23}$)', ans:4, hint:'$\\nu = N/N_A = 2{,}4 \\cdot 10^{24} / 6 \\cdot 10^{23} = 4$' },
{ q:'В 18 г воды ($M = 18$ г/моль) сколько моль?', ans:1, hint:'$\\nu = m/M = 18/18 = 1$ моль' },
{ q:'В 1 кг водорода ($M = 2$ г/моль) сколько моль?', ans:500, hint:'$\\nu = 1000/2 = 500$ моль' },
{ q:'В 3 моль газа сколько молекул? Введи число $X$ для ответа $X \\cdot 10^{23}$', ans:18, hint:'$N = 3 \\cdot 6 \\cdot 10^{23} = 18 \\cdot 10^{23}$' },
{ q:'Размер молекулы $\\sim 10^{-10}$ м. Сколько молекул в ряд поместится на $1$ мм? Введи порядок: $10^X$, $X = ?$', ans:7, hint:'$10^{-3}/10^{-10} = 10^{7}$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p1-iv4'); bumpProgress('p1', 25); }
else if(score >= 3){ addXp(8, 'p1-iv4'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv4-i').textContent = (i+1);
document.getElementById('p1-iv4-s').textContent = score;
document.getElementById('p1-iv4-q').innerHTML = Q[i].q;
document.getElementById('p1-iv4-ans').value = '';
renderMath(document.getElementById('p1-iv4-q'));
document.getElementById('p1-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv4-fb');
const raw = document.getElementById('p1-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p1-iv4-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p1-iv4-go').addEventListener('click', go);
document.getElementById('p1-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p1-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p1');
}
function build_p2(){
const box = document.getElementById('p2-body');
let html = '';
/* THEORY 1 — количество вещества */
html += makeCard('theory', "Количество вещества и моль", "§2", `
<p><b>Количество вещества</b> $\\nu$ (греч. «ню») — физическая величина, равная числу частиц вещества, делённому на число Авогадро:</p>
<p style="text-align:center;margin:10px 0">$$\\nu = \\dfrac{N}{N_A}$$</p>
<p>Единица: <b>моль</b>. Один моль — количество вещества, содержащее столько же частиц, сколько атомов в $12$ г углерода-12.</p>
<p style="margin-top:8px"><b>Число Авогадро:</b> $N_A = 6{,}022 \\cdot 10^{23}$ моль$^{-1}$.</p>
`);
/* THEORY 2 — молярная масса */
html += makeCard('rule', "Молярная масса. Связь с массой", "§2", `
<p><b>Молярная масса</b> $M$ — масса одного моля вещества:</p>
<p style="text-align:center;margin:10px 0">$$M = m_0 \\cdot N_A$$</p>
<p>где $m_0$ — масса одной молекулы. Единица СИ — <b>кг/моль</b>, привычная — <b>г/моль</b>.</p>
<p style="margin-top:8px"><b>Связь количества вещества с массой:</b></p>
<p style="text-align:center;margin:10px 0">$$\\nu = \\dfrac{m}{M} = \\dfrac{N}{N_A}$$</p>
<p><b>Относительная молекулярная масса</b> $M_r$ — безразмерна: $M_r = M$ (в г/моль). Например, $M_r(\\text{H}_2\\text{O}) = 18$, $M(\\text{H}_2\\text{O}) = 18$ г/моль $= 0{,}018$ кг/моль.</p>
`);
/* THEORY 3 — масса молекулы */
html += makeCard('example', "Масса одной молекулы и концентрация", "§2", `
<p>Зная молярную массу и число Авогадро, найдём массу одной молекулы:</p>
<p style="text-align:center;margin:10px 0">$$m_0 = \\dfrac{M}{N_A}$$</p>
<p><b>Примеры:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Молекула воды ($M = 18$ г/моль): $m_0 = \\dfrac{18 \\cdot 10^{-3}}{6{,}022 \\cdot 10^{23}} \\approx 3 \\cdot 10^{-26}$ кг.</li>
<li>Атом водорода ($M = 1$ г/моль): $m_0 \\approx 1{,}66 \\cdot 10^{-27}$ кг (= 1 а.е.м.).</li>
<li>Атом углерода ($M = 12$ г/моль): $m_0 \\approx 2 \\cdot 10^{-26}$ кг.</li>
</ul>
<p><b>Концентрация молекул</b> $n$ — число молекул в единице объёма:</p>
<p style="text-align:center;margin:10px 0">$$n = \\dfrac{N}{V}$$</p>
<p>Единица: $\\text{м}^{-3}$.</p>
`);
/* INTERACTIVE 1 — калькулятор Авогадро */
html += `<div class="wg" id="p2-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Калькулятор Авогадро</div></div>
<div class="wg-help">Введи массу $m$ (г) и молярную массу $M$ (г/моль) — получи количество вещества $\\nu$, число молекул $N$ и массу одной молекулы $m_0$.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Масса $m$, г <input type="number" id="p2-iv1-m" class="tinp" style="width:100%;margin-top:6px" value="18" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Молярная масса $M$, г/моль <input type="number" id="p2-iv1-M" class="tinp" style="width:100%;margin-top:6px" value="18" step="any"></label>
</div>
<div style="margin-bottom:10px">
<span style="font-size:.84rem;color:var(--muted);margin-right:6px">Эталон:</span>
<select id="p2-iv1-preset" class="tinp" style="padding:6px 10px">
<option value="">— выбери вещество —</option>
<option value="18">Вода H₂O (M = 18)</option>
<option value="44">Углекислый газ CO₂ (M = 44)</option>
<option value="32">Кислород O₂ (M = 32)</option>
<option value="28">Азот N₂ (M = 28)</option>
<option value="2">Водород H₂ (M = 2)</option>
<option value="56">Железо Fe (M = 56)</option>
<option value="12">Углерод C (M = 12)</option>
</select>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p2-iv1-go">Вычислить</button>
</div>
<div id="p2-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p2-iv1-fb"></div>
</div>`;
/* INTERACTIVE 2 — DnD сортер: вещество ↔ молярная масса */
html += `<div class="wg" id="p2-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сопоставь массу с веществом</div></div>
<div class="wg-help">Перетащи каждое вещество в ящик с его молярной массой.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 6 веществ — 6 значений</div>
<div id="p2-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="m2">2 г/моль</h5><div class="drop-items" data-cat="m2"></div></div>
<div class="drop-box"><h5 data-cat="m16">16 г/моль</h5><div class="drop-items" data-cat="m16"></div></div>
<div class="drop-box"><h5 data-cat="m18">18 г/моль</h5><div class="drop-items" data-cat="m18"></div></div>
<div class="drop-box"><h5 data-cat="m28">28 г/моль</h5><div class="drop-items" data-cat="m28"></div></div>
<div class="drop-box"><h5 data-cat="m32">32 г/моль</h5><div class="drop-items" data-cat="m32"></div></div>
<div class="drop-box"><h5 data-cat="m44">44 г/моль</h5><div class="drop-items" data-cat="m44"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p2-iv2-check">Проверить</button><button class="btn" id="p2-iv2-reset">Сначала</button></div>
<div class="feedback" id="p2-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр «Что больше?» */
html += `<div class="wg" id="p2-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Что больше?</div></div>
<div class="wg-help">6 вопросов «что больше» — выбери первое или второе.</div>
<div class="score-display"><span>Задача <b id="p2-iv3-i">1</b> / 6</span><span>Очки: <b id="p2-iv3-s">0</b> / 6</span></div>
<div id="p2-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p2-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p2-iv3-fb"></div>
<div class="actions"><button class="btn" id="p2-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр количества вещества */
html += `<div class="wg" id="p2-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: количество вещества</div></div>
<div class="wg-help">6 задач на $\\nu = m/M$ и $N = \\nu N_A$. Допуск $\\pm 5\\%$. Используй $N_A = 6 \\cdot 10^{23}$.</div>
<div class="score-display"><span>Задача <b id="p2-iv4-i">1</b> / 6</span><span>Очки: <b id="p2-iv4-s">0</b> / 6</span></div>
<div id="p2-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p2-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p2-iv4-go">Проверить</button>
<button class="btn" id="p2-iv4-start">Заново</button>
</div>
<div class="feedback" id="p2-iv4-fb"></div>
</div>`;
html += secNav('p1', 'p3');
html += readButton('p2');
box.innerHTML = html;
renderMath(box);
/* IV1 — калькулятор Авогадро */
(function(){
const mI = document.getElementById('p2-iv1-m');
const MI = document.getElementById('p2-iv1-M');
const sel = document.getElementById('p2-iv1-preset');
const out = document.getElementById('p2-iv1-out');
const fb = document.getElementById('p2-iv1-fb');
const go = document.getElementById('p2-iv1-go');
const used = new Set();
let _done = false;
const NA = 6e23;
function calc(){
const m = parseFloat(mI.value);
const M = parseFloat(MI.value);
if(!isFinite(m) || m <= 0){ feedback(fb, false, '&#10007; Введи положительную массу.'); return; }
if(!isFinite(M) || M <= 0){ feedback(fb, false, '&#10007; Введи положительную молярную массу.'); return; }
const nu = m / M;
const N = nu * NA;
// m0 в кг: m0 = M(кг/моль) / N_A = M*1e-3 / N_A
const m0 = (M * 1e-3) / NA;
const NstrExp = Math.floor(Math.log10(N));
const NstrCoef = N / Math.pow(10, NstrExp);
const m0Exp = Math.floor(Math.log10(m0));
const m0Coef = m0 / Math.pow(10, m0Exp);
out.innerHTML =
'<div style="margin-bottom:8px"><b>Количество вещества:</b> $\\nu = \\dfrac{m}{M} = \\dfrac{'+(+m.toFixed(3))+'}{'+(+M.toFixed(3))+'} \\approx '+(+nu.toFixed(4))+'$ моль</div>'
+ '<div style="margin-bottom:8px"><b>Число молекул:</b> $N = \\nu \\cdot N_A = '+(+nu.toFixed(4))+' \\cdot 6 \\cdot 10^{23} \\approx '+(+NstrCoef.toFixed(3))+' \\cdot 10^{'+NstrExp+'}$</div>'
+ '<div><b>Масса одной молекулы:</b> $m_0 = \\dfrac{M}{N_A} \\approx '+(+m0Coef.toFixed(3))+' \\cdot 10^{'+m0Exp+'}$ кг</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add((+M.toFixed(2))+':'+(+m.toFixed(2)));
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p2-iv1'); bumpProgress('p2', 15); }
}
sel.addEventListener('change', () => {
if(sel.value){ MI.value = sel.value; }
});
go.addEventListener('click', calc);
mI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
MI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
})();
/* IV2 — DnD: вещество ↔ молярная масса */
(function(){
const items = [
{ id:'w1', cat:'m18', html:'Вода H$_2$O' },
{ id:'w2', cat:'m44', html:'Углекислый газ CO$_2$' },
{ id:'w3', cat:'m32', html:'Кислород O$_2$' },
{ id:'w4', cat:'m28', html:'Азот N$_2$' },
{ id:'w5', cat:'m2', html:'Водород H$_2$' },
{ id:'w6', cat:'m16', html:'Метан CH$_4$' },
];
const sorter = setupSorter({
poolId:'p2-iv2-pool',
scopeSelector:'#p2-iv2',
items: items,
cats:['m2','m16','m18','m28','m32','m44'],
columnLayout:false,
});
document.getElementById('p2-iv2-check').addEventListener('click', ()=>{
const fb = document.getElementById('p2-iv2-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 веществ.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 верно! +10 XP'); addXp(10,'p2-iv2'); bumpProgress('p2', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p2-iv2-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv2-fb').style.display = 'none'; });
})();
/* IV3 — квикфайр «Что больше?» */
(function(){
const Q = [
{ q:'Что весит больше: $1$ моль воды (M=18) или $1$ моль водорода (M=2)?', a:'1 моль воды (18 г)', b:'1 моль водорода (2 г)', ans:0, why:'Молярная масса воды 18 г/моль > 2 г/моль.' },
{ q:'Что больше по массе: $36$ г воды или $1$ моль воды (M=18)?', a:'36 г воды', b:'1 моль воды (18 г)', ans:0, why:'36 г = 2 моль воды > 1 моль (18 г).' },
{ q:'Что весит больше: $N_A$ молекул кислорода (M=32) или $2 N_A$ молекул водорода (M=2)?', a:'$N_A$ молекул O$_2$ (32 г)', b:'$2 N_A$ молекул H$_2$ (4 г)', ans:0, why:'32 г > 4 г.' },
{ q:'Что больше по массе: $1$ моль CO$_2$ (M=44) или $1$ кг воды?', a:'1 моль CO$_2$ (44 г)', b:'1 кг воды (1000 г)', ans:1, why:'1000 г > 44 г.' },
{ q:'Что больше по числу частиц: $10^{23}$ молекул железа или $10^{22}$ молекул свинца?', a:'$10^{23}$ молекул Fe', b:'$10^{22}$ молекул Pb', ans:0, why:'$10^{23} > 10^{22}$ в 10 раз.' },
{ q:'Что больше по массе: $5$ моль H$_2$ (M=2) или $1$ моль O$_2$ (M=32)?', a:'5 моль H$_2$ (10 г)', b:'1 моль O$_2$ (32 г)', ans:1, why:'$5 \\cdot 2 = 10$ г против $32$ г.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p2-iv3-q');
const oEl = document.getElementById('p2-iv3-opts');
const fb = document.getElementById('p2-iv3-fb');
const iEl = document.getElementById('p2-iv3-i');
const sEl = document.getElementById('p2-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p2-iv3'); bumpProgress('p2', 25); }
else if(score >= 4){ addXp(8, 'p2-iv3'); bumpProgress('p2', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="0">Первое: '+item.a+'</button><button class="btn primary" data-v="1">Второе: '+item.b+'</button>';
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p2-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр количества вещества */
(function(){
const Q = [
{ q:'В 36 г воды (M=18 г/моль) сколько моль?', ans:2, hint:'$\\nu = m/M = 36/18 = 2$ моль' },
{ q:'В 88 г углекислого газа (M=44 г/моль) сколько моль?', ans:2, hint:'$\\nu = 88/44 = 2$ моль' },
{ q:'Сколько молекул в 1 моль вещества? Введи показатель $X$ для $6 \\cdot 10^X$', ans:23, hint:'$N_A = 6 \\cdot 10^{23}$' },
{ q:'M = 64 г/моль. Сколько моль в 32 г этого вещества?', ans:0.5, hint:'$\\nu = 32/64 = 0{,}5$ моль' },
{ q:'Сколько грамм в 3 моль воды (M=18 г/моль)?', ans:54, hint:'$m = \\nu M = 3 \\cdot 18 = 54$ г' },
{ q:'В 36 г воды сколько молекул? Введи $X$ для ответа $X \\cdot 10^{23}$', ans:12, hint:'$\\nu = 2$ моль, $N = 2 \\cdot 6 \\cdot 10^{23} = 12 \\cdot 10^{23}$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p2-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p2-iv4'); bumpProgress('p2', 25); }
else if(score >= 4){ addXp(8, 'p2-iv4'); bumpProgress('p2', 15); }
return;
}
document.getElementById('p2-iv4-i').textContent = (i+1);
document.getElementById('p2-iv4-s').textContent = score;
document.getElementById('p2-iv4-q').innerHTML = Q[i].q;
document.getElementById('p2-iv4-ans').value = '';
renderMath(document.getElementById('p2-iv4-q'));
document.getElementById('p2-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p2-iv4-fb');
const raw = document.getElementById('p2-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p2-iv4-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p2-iv4-go').addEventListener('click', go);
document.getElementById('p2-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p2-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p2');
}
function build_p3(){
const box = document.getElementById('p3-body');
let html = '';
/* THEORY 1 — модель идеального газа */
html += makeCard('theory', "Модель идеального газа", "§3", `
<p><b>Идеальный газ</b> — упрощённая модель реального газа, в которой:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li><b>Размеры молекул пренебрежимо малы</b> по сравнению с расстояниями между ними.</li>
<li><b>Молекулы взаимодействуют только при столкновениях</b> (как упругие шары).</li>
<li><b>Между столкновениями молекулы движутся равномерно и прямолинейно</b>.</li>
</ol>
<p>Реальные газы при <b>низком давлении</b> и <b>не очень низкой температуре</b> близки к идеальному.</p>
<p style="margin-top:10px"><b>Параметры газа:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Макропараметры</b> (описывают газ в целом): давление $p$, объём $V$, температура $T$.</li>
<li><b>Микропараметры</b> (описывают отдельные молекулы): масса молекулы $m_0$, концентрация $n = N/V$, скорость $v$.</li>
</ul>
`);
/* THEORY 2 — основное уравнение МКТ */
html += makeCard('rule', "Основное уравнение МКТ", "§3", `
<p>Давление идеального газа создаётся <b>ударами молекул о стенки сосуда</b>. Связь между макро- и микропараметрами:</p>
<p style="text-align:center;margin:10px 0">$$p = \\dfrac{1}{3} n m_0 \\overline{v^2}$$</p>
<p>где:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$n$ — концентрация молекул, м$^{-3}$;</li>
<li>$m_0$ — масса одной молекулы, кг;</li>
<li>$\\overline{v^2}$ — средний квадрат скорости молекул, м²/с².</li>
</ul>
<p>Это <b>основное уравнение МКТ идеального газа</b>.</p>
<p style="margin-top:8px">Через среднюю кинетическую энергию поступательного движения $\\overline{E_k} = \\dfrac{1}{2} m_0 \\overline{v^2}$:</p>
<p style="text-align:center;margin:10px 0">$$p = \\dfrac{2}{3} n \\overline{E_k}$$</p>
`);
/* THEORY 3 — средняя квадратичная скорость */
html += makeCard('example', "Средняя квадратичная скорость", "§3", `
<p><b>Средняя квадратичная скорость молекул</b>:</p>
<p style="text-align:center;margin:10px 0">$$v_{ср.кв.} = \\sqrt{\\overline{v^2}}$$</p>
<p>Из основного уравнения МКТ:</p>
<p style="text-align:center;margin:10px 0">$$\\overline{v^2} = \\dfrac{3p}{n m_0}$$</p>
<p><b>Примеры скоростей</b> (при $T = 300$ К):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Молекулы водорода ($M = 2$ г/моль): $v_{ср.кв.} \\approx 1900$ м/с.</li>
<li>Молекулы азота ($M = 28$ г/моль): $v_{ср.кв.} \\approx 510$ м/с.</li>
<li>Молекулы кислорода ($M = 32$ г/моль): $v_{ср.кв.} \\approx 480$ м/с.</li>
<li>Молекулы воды (пар, $M = 18$ г/моль): $v_{ср.кв.} \\approx 645$ м/с.</li>
</ul>
<p>Чем легче молекула — тем быстрее она движется при той же температуре.</p>
`);
/* INTERACTIVE 1 — Симуляция давления газа */
html += `<div class="wg" id="p3-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симуляция давления газа</div></div>
<div class="wg-help">Меняй число молекул и их скорость — наблюдай удары о правую стенку. Давление пропорционально частоте ударов и квадрату скорости: $p \\sim n \\cdot v^2$.</div>
<div class="sliders">
<label>Концентрация $n$: <b id="p3-iv1-nL">50</b> молекул <input type="range" id="p3-iv1-n" min="10" max="100" value="50" step="1"></label>
<label>Скорость $v$: <b id="p3-iv1-vL">80</b> px/s <input type="range" id="p3-iv1-v" min="30" max="200" value="80" step="5"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p3-iv1-svg" viewBox="0 0 420 280" width="100%" style="max-width:420px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p3-iv1-pause">Пауза</button>
<button class="btn" id="p3-iv1-reset">Сброс</button>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.9rem;line-height:1.7">
<div>Ударов в правую стенку за последнюю секунду: <b id="p3-iv1-hits">0</b></div>
<div><b>Давление</b> (усл. ед., $\\propto n v^2$): <b id="p3-iv1-pres">—</b></div>
</div>
</div>`;
/* INTERACTIVE 2 — Калькулятор основного уравнения МКТ */
html += `<div class="wg" id="p3-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор уравнения МКТ</div></div>
<div class="wg-help">$p = \\dfrac{1}{3} n m_0 \\overline{v^2}$. Введи параметры и получи давление и среднюю кинетическую энергию молекулы.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">$n$, $10^{25}$ м$^{-3}$ <input type="number" id="p3-iv2-n" class="tinp" style="width:100%;margin-top:6px" value="2.7" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">$m_0$, $10^{-26}$ кг <input type="number" id="p3-iv2-m" class="tinp" style="width:100%;margin-top:6px" value="4.65" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">$\\overline{v^2}$, $10^{6}$ м²/с² <input type="number" id="p3-iv2-v" class="tinp" style="width:100%;margin-top:6px" value="0.24" step="any"></label>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p3-iv2-go">Вычислить $p$</button>
</div>
<div id="p3-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p3-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр Идеальный/Реальный */
html += `<div class="wg" id="p3-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Идеальный или реальный газ?</div></div>
<div class="wg-help">6 ситуаций. Газ близок к идеальному при низком $p$ и не очень низком $T$. Жми соответствующую кнопку.</div>
<div class="score-display"><span>Задача <b id="p3-iv3-i">1</b> / 6</span><span>Очки: <b id="p3-iv3-s">0</b> / 6</span></div>
<div id="p3-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p3-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p3-iv3-fb"></div>
<div class="actions"><button class="btn" id="p3-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр МКТ */
html += `<div class="wg" id="p3-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр МКТ</div></div>
<div class="wg-help">5 задач на $p = \\frac{1}{3} n m_0 \\overline{v^2}$. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p3-iv4-i">1</b> / 5</span><span>Очки: <b id="p3-iv4-s">0</b> / 5</span></div>
<div id="p3-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p3-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p3-iv4-go">Проверить</button>
<button class="btn" id="p3-iv4-start">Заново</button>
</div>
<div class="feedback" id="p3-iv4-fb"></div>
</div>`;
html += secNav('p2', 'p4');
html += readButton('p3');
box.innerHTML = html;
renderMath(box);
/* IV1 — Симуляция давления газа */
(function(){
const svg = document.getElementById('p3-iv1-svg');
const nInp = document.getElementById('p3-iv1-n');
const vInp = document.getElementById('p3-iv1-v');
const nLab = document.getElementById('p3-iv1-nL');
const vLab = document.getElementById('p3-iv1-vL');
const hitsEl = document.getElementById('p3-iv1-hits');
const presEl = document.getElementById('p3-iv1-pres');
const btnPause = document.getElementById('p3-iv1-pause');
const btnReset = document.getElementById('p3-iv1-reset');
const W = 400, H = 220, OX = 10, OY = 30;
let raf = null, lastT = 0, paused = false;
let sim = null;
let hitTimes = []; // timestamps ударов о правую стенку (сек)
let lastUpdate = 0;
const tempChanges = new Set();
let _xpDone = false;
function makeSim(){
const N = +nInp.value;
const v = +vInp.value;
sim = PHYS.createGasSim({W, H, N, speed: v, r: 3});
hitTimes = [];
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
// подсчёт ударов о правую стенку (до step) — отслеживаем переход через правую границу
const R = sim.r;
const beforeX = sim.particles.map(p => p.x);
sim.step(dt);
for(let i = 0; i < sim.particles.length; i++){
// если после step координата x = W - r и vx стало отрицательным — был удар
if(beforeX[i] < W - R - 0.1 && sim.particles[i].x >= W - R - 0.5 && sim.particles[i].vx < 0){
hitTimes.push(t / 1000);
}
// более надёжный признак: за последний кадр частица отскочила от правой стенки (vx < 0 после step), а до этого vx > 0
// мы упрощаем: считаем удар, если частица сейчас касается правой стенки
}
// удаляем старые удары (>1 сек назад)
const tSec = t / 1000;
while(hitTimes.length && hitTimes[0] < tSec - 1) hitTimes.shift();
if(tSec - lastUpdate > 0.3){
lastUpdate = tSec;
const hits = hitTimes.length;
hitsEl.textContent = hits;
const n = +nInp.value;
const v = +vInp.value;
// условное «давление» — пропорционально n*v^2 / 1000
const p = (n * v * v) / 1000;
presEl.textContent = p.toFixed(1);
}
render();
}
function render(){
let g = '';
// рамка сосуда
g += `<rect x="${OX}" y="${OY}" width="${W}" height="${H}" fill="none" stroke="#475569" stroke-width="1"/>`;
// правая стенка — подсвечена
g += `<line x1="${OX+W}" y1="${OY}" x2="${OX+W}" y2="${OY+H}" stroke="#f97316" stroke-width="3"/>`;
g += `<text x="${OX+W-4}" y="${OY-8}" text-anchor="end" fill="#f97316" font-family="Inter,sans-serif" font-size="11" font-weight="600">стенка</text>`;
// молекулы — сдвигаем в (OX, OY)
for(const p of sim.particles){
g += `<circle cx="${(p.x+OX).toFixed(1)}" cy="${(p.y+OY).toFixed(1)}" r="${sim.r}" fill="#60a5fa" stroke="#0f172a" stroke-width="0.5"/>`;
}
svg.innerHTML = g;
}
makeSim();
raf = requestAnimationFrame(frame);
nInp.addEventListener('input', () => {
nLab.textContent = nInp.value;
makeSim();
tempChanges.add('n'+nInp.value+'v'+vInp.value);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true; addXp(10, 'p3-iv1'); bumpProgress('p3', 15);
}
});
vInp.addEventListener('input', () => {
vLab.textContent = vInp.value;
// не пересоздаём — масштабируем скорость
const cur = +vInp.value;
let avg = 0;
for(const p of sim.particles) avg += Math.hypot(p.vx, p.vy);
avg /= sim.particles.length;
if(avg > 0.01) sim.setSpeed(cur / avg);
tempChanges.add('n'+nInp.value+'v'+vInp.value);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true; addXp(10, 'p3-iv1'); bumpProgress('p3', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => { makeSim(); });
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — Калькулятор основного уравнения МКТ */
(function(){
const nI = document.getElementById('p3-iv2-n');
const mI = document.getElementById('p3-iv2-m');
const vI = document.getElementById('p3-iv2-v');
const out = document.getElementById('p3-iv2-out');
const fb = document.getElementById('p3-iv2-fb');
const go = document.getElementById('p3-iv2-go');
const used = new Set();
let _done = false;
function calc(){
const n = parseFloat(nI.value);
const m0 = parseFloat(mI.value);
const v2 = parseFloat(vI.value);
if(!isFinite(n) || !isFinite(m0) || !isFinite(v2) || n <= 0 || m0 <= 0 || v2 <= 0){
feedback(fb, false, '&#10007; Введи положительные значения во все поля.');
return;
}
// n × 10^25, m0 × 10^-26, v2 × 10^6 → p = 1/3 · n·m0·v2 × 10^(25-26+6) = × 10^5
const pCoef = (1/3) * n * m0 * v2;
const pPa = pCoef * 1e5; // Па
// средняя кинетическая энергия: Ek = (1/2) m0 v2 = (1/2) m0(10^-26) · v2(10^6) = (1/2) m0 v2 × 10^-20 Дж
const EkCoef = 0.5 * m0 * v2; // в 10^-20 Дж
const EkJ = EkCoef * 1e-20;
const EkEv = EkJ / 1.6e-19;
out.innerHTML =
'<div style="margin-bottom:8px"><b>Подставляем:</b></div>'
+ '<div style="margin-bottom:8px">$p = \\dfrac{1}{3} \\cdot ' + (+n.toFixed(3)) + ' \\cdot 10^{25} \\cdot ' + (+m0.toFixed(3)) + ' \\cdot 10^{-26} \\cdot ' + (+v2.toFixed(3)) + ' \\cdot 10^{6}$</div>'
+ '<div style="margin-bottom:8px"><b>Давление:</b> $p \\approx ' + (+pCoef.toFixed(3)) + ' \\cdot 10^{5}$ Па $\\approx ' + (+(pPa/1e5).toFixed(3)) + '$ атм</div>'
+ '<div><b>Средняя кинетическая энергия молекулы:</b> $\\overline{E_k} = \\dfrac{1}{2} m_0 \\overline{v^2} \\approx ' + (+EkCoef.toFixed(3)) + ' \\cdot 10^{-20}$ Дж $\\approx ' + (+EkEv.toFixed(4)) + '$ эВ</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add((+n.toFixed(2))+':'+(+m0.toFixed(2))+':'+(+v2.toFixed(2)));
if(!_done && used.size >= 3){ _done = true; addXp(10, 'p3-iv2'); bumpProgress('p3', 15); }
}
go.addEventListener('click', calc);
[nI, mI, vI].forEach(el => el.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }));
})();
/* IV3 — квикфайр Идеальный/Реальный */
(function(){
const Q = [
{ q:'Воздух в комнате (комнатная температура, атмосферное давление)', ans:0, why:'При обычных условиях воздух близок к идеальному.' },
{ q:'Водяной пар у поверхности кипящей воды (готов конденсироваться)', ans:1, why:'Близок к конденсации — взаимодействия молекул нельзя пренебречь.' },
{ q:'Гелий при $T = 4$ К (на грани ожижения)', ans:1, why:'При очень низкой $T$ молекулы сильно взаимодействуют.' },
{ q:'Кислород в баллоне под давлением 200 атм', ans:1, why:'При высоком давлении расстояния между молекулами малы.' },
{ q:'Воздух в верхних слоях атмосферы (низкое давление)', ans:0, why:'Чем ниже $p$ — тем ближе к идеальному газу.' },
{ q:'Разреженный газ в плазме разряда (низкое $p$, очень высокая $T$)', ans:0, why:'Низкое давление и высокая $T$ — идеальные условия для модели.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p3-iv3-q');
const oEl = document.getElementById('p3-iv3-opts');
const fb = document.getElementById('p3-iv3-fb');
const iEl = document.getElementById('p3-iv3-i');
const sEl = document.getElementById('p3-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p3-iv3'); bumpProgress('p3', 25); }
else if(score >= 4){ addXp(8, 'p3-iv3'); bumpProgress('p3', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="0">Близко к идеальному</button><button class="btn primary" data-v="1" style="background:#ef4444;border-color:#ef4444">Сильно отличается</button>';
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p3-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр МКТ */
(function(){
const Q = [
{ q:'$n = 3 \\cdot 10^{25}$ м$^{-3}$, $m_0 = 5 \\cdot 10^{-26}$ кг, $\\overline{v^2} = 4 \\cdot 10^{5}$ м²/с². Введи коэффициент при $10^5$ Па.', ans:2, hint:'$p = \\frac{1}{3} \\cdot 3 \\cdot 5 \\cdot 4 \\cdot 10^{25-26+5} = 20 \\cdot 10^{4} = 2 \\cdot 10^{5}$' },
{ q:'$\\overline{E_k} = 6 \\cdot 10^{-21}$ Дж, $n = 5 \\cdot 10^{25}$ м$^{-3}$. Введи коэффициент при $10^5$ Па.', ans:2, hint:'$p = \\frac{2}{3} n \\overline{E_k} = \\frac{2}{3} \\cdot 5 \\cdot 10^{25} \\cdot 6 \\cdot 10^{-21} = 2 \\cdot 10^{5}$' },
{ q:'Если давление и концентрацию увеличить вдвое, во сколько раз изменится $\\overline{v^2}$?', ans:1, hint:'$p = \\frac{1}{3} n m_0 \\overline{v^2}$: если $p$ и $n$ ×2, то $\\overline{v^2}$ не меняется.' },
{ q:'Если массу молекулы $m_0$ увеличить в 4 раза при том же $p$ и $n$, во сколько раз изменится $\\overline{v^2}$?', ans:0.25, hint:'$\\overline{v^2} \\sim 1/m_0$ при фиксированных $p, n$ — уменьшится в 4 раза.' },
{ q:'Концентрация газа $n = 2{,}5 \\cdot 10^{25}$ м$^{-3}$. Сколько молекул в $1$ м³? Введи коэффициент при $10^{25}$.', ans:2.5, hint:'$N = n \\cdot V = 2{,}5 \\cdot 10^{25}$.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p3-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p3-iv4'); bumpProgress('p3', 25); }
else if(score >= 3){ addXp(8, 'p3-iv4'); bumpProgress('p3', 15); }
return;
}
document.getElementById('p3-iv4-i').textContent = (i+1);
document.getElementById('p3-iv4-s').textContent = score;
document.getElementById('p3-iv4-q').innerHTML = Q[i].q;
document.getElementById('p3-iv4-ans').value = '';
renderMath(document.getElementById('p3-iv4-q'));
document.getElementById('p3-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p3-iv4-fb');
const raw = document.getElementById('p3-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p3-iv4-s').textContent = score;
i++;
setTimeout(show, 1700);
}
document.getElementById('p3-iv4-go').addEventListener('click', go);
document.getElementById('p3-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p3-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p3');
}
function build_p4(){
const box = document.getElementById('p4-body');
let html = '';
/* THEORY 1 — тепловое равновесие и температура */
html += makeCard('theory', "Тепловое равновесие и температура", "§4", `
<p><b>Тепловое равновесие</b> — состояние термодинамической системы, в котором её макроскопические параметры (температура, давление, плотность) не меняются со временем.</p>
<p style="margin-top:8px"><b>Температура</b> — физическая величина, характеризующая состояние теплового равновесия. Если два тела находятся в тепловом равновесии — их температуры равны.</p>
<p style="margin-top:10px"><b>Шкалы температуры:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Цельсия</b> ($t$, °C): $0°$C — таяние льда, $100°$C — кипение воды (при $p = 101{,}3$ кПа).</li>
<li><b>Кельвина</b> ($T$, К): абсолютная шкала. $T = 0$ K — абсолютный нуль.</li>
</ul>
<p><b>Связь шкал:</b></p>
<p style="text-align:center;margin:10px 0">$$T = t + 273{,}15 \\approx t + 273$$</p>
<p>Абсолютный нуль ($0$ К $= -273{,}15°$C) — теоретический предел: невозможно охладить вещество ниже.</p>
`);
/* THEORY 2 — температура и средняя кинетическая энергия */
html += makeCard('rule', "Температура и средняя кинетическая энергия", "§4", `
<p><b>Постоянная Больцмана:</b> $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К.</p>
<p style="margin-top:8px"><b>Главная формула</b> — связь температуры и средней кинетической энергии молекул:</p>
<p style="text-align:center;margin:10px 0">$$\\overline{E_k} = \\dfrac{3}{2} k_B T$$</p>
<p>Из этой формулы и основного уравнения МКТ ($p = \\frac{2}{3} n \\overline{E_k}$) получаем уравнение состояния:</p>
<p style="text-align:center;margin:10px 0">$$p = n k_B T$$</p>
<p><b>Температура — мера средней кинетической энергии хаотического теплового движения молекул.</b></p>
`);
/* THEORY 3 — средняя квадратичная скорость */
html += makeCard('example', "Средняя квадратичная скорость молекул", "§4", `
<p>Из $\\overline{E_k} = \\dfrac{1}{2} m_0 \\overline{v^2} = \\dfrac{3}{2} k_B T$ выводим:</p>
<p style="text-align:center;margin:10px 0">$$\\overline{v^2} = \\dfrac{3 k_B T}{m_0}$$</p>
<p><b>Средняя квадратичная скорость:</b></p>
<p style="text-align:center;margin:10px 0">$$v_{ср.кв.} = \\sqrt{\\dfrac{3 k_B T}{m_0}} = \\sqrt{\\dfrac{3 R T}{M}}$$</p>
<p>где $R = N_A \\cdot k_B = 8{,}314$ Дж/(моль·К) — универсальная газовая постоянная.</p>
<p style="margin-top:8px"><b>Примеры</b> (при $T = 300$ К):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Водород ($M = 2$ г/моль): $v \\approx 1934$ м/с.</li>
<li>Азот ($M = 28$ г/моль): $v \\approx 517$ м/с.</li>
<li>Кислород ($M = 32$ г/моль): $v \\approx 484$ м/с.</li>
<li>Углекислый газ ($M = 44$ г/моль): $v \\approx 412$ м/с.</li>
</ul>
<p>Чем выше $T$ и легче молекула — тем больше скорость.</p>
`);
/* INTERACTIVE 1 — Температурный визуализатор */
html += `<div class="wg" id="p4-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Температурный визуализатор</div></div>
<div class="wg-help">Меняй $T$ — наблюдай, как меняется скорость молекул ($\\sim \\sqrt{T}$) и цвет (от холодного к горячему).</div>
<div class="sliders">
<label>Температура $T$: <b id="p4-iv1-tL">300</b> К <input type="range" id="p4-iv1-T" min="100" max="1000" value="300" step="10"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p4-iv1-svg" viewBox="0 0 420 280" width="100%" style="max-width:420px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p4-iv1-pause">Пауза</button>
<button class="btn" id="p4-iv1-reset">Сброс</button>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.9rem;line-height:1.75">
<div>$T = $ <b id="p4-iv1-T2">300</b> К ($t = $ <b id="p4-iv1-tc">27</b> °C)</div>
<div>$\\overline{E_k} = \\dfrac{3}{2} k_B T \\approx$ <b id="p4-iv1-Ek">6.21</b> $\\cdot 10^{-21}$ Дж</div>
<div>$v_{ср.кв.}$ (для азота, $m_0 = 4{,}65 \\cdot 10^{-26}$ кг) $\\approx$ <b id="p4-iv1-v">517</b> м/с</div>
</div>
</div>`;
/* INTERACTIVE 2 — Калькулятор температуры и скорости */
html += `<div class="wg" id="p4-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $T$, $\\overline{E_k}$ и $v_{ср.кв.}$</div></div>
<div class="wg-help">Введи $T$ и выбери газ — получи среднюю кинетическую энергию и среднюю квадратичную скорость.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Температура $T$, К <input type="number" id="p4-iv2-T" class="tinp" style="width:100%;margin-top:6px" value="300" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Газ ($M$, г/моль)
<select id="p4-iv2-M" class="tinp" style="width:100%;margin-top:6px">
<option value="2">Водород H₂ (M = 2)</option>
<option value="4">Гелий He (M = 4)</option>
<option value="28" selected>Азот N₂ (M = 28)</option>
<option value="32">Кислород O₂ (M = 32)</option>
<option value="44">Углекислый газ CO₂ (M = 44)</option>
</select>
</label>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p4-iv2-go">Вычислить</button>
</div>
<div id="p4-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p4-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Конвертер шкал */
html += `<div class="wg" id="p4-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Конвертер шкал и быстрая проверка</div></div>
<div class="wg-help">6 вопросов: переведи температуру по шкалам или узнай типичное значение. Выбери правильный ответ.</div>
<div class="score-display"><span>Задача <b id="p4-iv3-i">1</b> / 6</span><span>Очки: <b id="p4-iv3-s">0</b> / 6</span></div>
<div id="p4-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p4-iv3-opts" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px"></div>
<div class="feedback" id="p4-iv3-fb"></div>
<div class="actions"><button class="btn" id="p4-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр температуры */
html += `<div class="wg" id="p4-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр температуры</div></div>
<div class="wg-help">5 задач. Используй $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К, $R = 8{,}3$ Дж/(моль·К).</div>
<div class="score-display"><span>Задача <b id="p4-iv4-i">1</b> / 5</span><span>Очки: <b id="p4-iv4-s">0</b> / 5</span></div>
<div id="p4-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p4-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p4-iv4-go">Проверить</button>
<button class="btn" id="p4-iv4-start">Заново</button>
</div>
<div class="feedback" id="p4-iv4-fb"></div>
</div>`;
html += secNav('p3', 'p5');
html += readButton('p4');
box.innerHTML = html;
renderMath(box);
/* IV1 — Температурный визуализатор */
(function(){
const svg = document.getElementById('p4-iv1-svg');
const tInp = document.getElementById('p4-iv1-T');
const tLab = document.getElementById('p4-iv1-tL');
const T2 = document.getElementById('p4-iv1-T2');
const tc = document.getElementById('p4-iv1-tc');
const EkEl = document.getElementById('p4-iv1-Ek');
const vEl = document.getElementById('p4-iv1-v');
const btnPause = document.getElementById('p4-iv1-pause');
const btnReset = document.getElementById('p4-iv1-reset');
const W = 400, H = 220, OX = 10, OY = 30;
const baseSpeed = 80; // соответствует T0 = 300 К
let raf = null, lastT = 0, paused = false;
let sim = null;
const tempChanges = new Set();
let _xpDone = false;
function tempColor(T){
// 100 К → синий (#60a5fa), 300 К → бирюзовый, 600 К → жёлтый, 1000 К → красный
const t = Math.max(0, Math.min(1, (T - 100) / 900));
// интерполяция: синий (0.16, 0.5, 0.96) → красный (0.94, 0.27, 0.21)
const r = Math.round(255 * (0.16 + t * (0.94 - 0.16)));
const g = Math.round(255 * (0.5 + t * (0.27 - 0.5)));
const b = Math.round(255 * (0.96 + t * (0.21 - 0.96)));
return `rgb(${r},${g},${b})`;
}
function makeSim(){
sim = PHYS.createGasSim({W, H, N: 40, speed: baseSpeed, r: 4});
applyTemp(+tInp.value);
}
function applyTemp(T){
const targetScale = Math.sqrt(T/300);
let curAvg = 0;
for(const p of sim.particles) curAvg += Math.hypot(p.vx, p.vy);
curAvg /= sim.particles.length;
const targetAvg = baseSpeed * targetScale;
if(curAvg > 0.01) sim.setSpeed(targetAvg / curAvg);
updateLabels(T);
}
function updateLabels(T){
const kB = PHYS.CONST.kB;
const Ek = 1.5 * kB * T; // Дж
// m0 для азота: M = 0.028 кг/моль / NA = 4.65e-26 кг
const m0 = 0.028 / PHYS.CONST.NA;
const v = Math.sqrt(3 * kB * T / m0);
T2.textContent = T;
tc.textContent = (T - 273.15).toFixed(1);
EkEl.textContent = (Ek / 1e-21).toFixed(2);
vEl.textContent = Math.round(v);
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
sim.step(dt);
render();
}
function render(){
const T = +tInp.value;
const col = tempColor(T);
let g = '';
g += `<rect x="${OX}" y="${OY}" width="${W}" height="${H}" fill="none" stroke="#475569" stroke-width="1"/>`;
// лейбл температуры
g += `<text x="${OX+W/2}" y="${OY-10}" text-anchor="middle" fill="${col}" font-family="Inter,sans-serif" font-size="14" font-weight="700">T = ${T} K</text>`;
for(const p of sim.particles){
g += `<circle cx="${(p.x+OX).toFixed(1)}" cy="${(p.y+OY).toFixed(1)}" r="${sim.r}" fill="${col}" stroke="#0f172a" stroke-width="0.5"/>`;
}
svg.innerHTML = g;
}
makeSim();
raf = requestAnimationFrame(frame);
tInp.addEventListener('input', () => {
const T = +tInp.value;
tLab.textContent = T;
applyTemp(T);
// snap к ключевым точкам
tempChanges.add(T);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true; addXp(10, 'p4-iv1'); bumpProgress('p4', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => { makeSim(); });
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — Калькулятор T, Ek, v */
(function(){
const TI = document.getElementById('p4-iv2-T');
const MI = document.getElementById('p4-iv2-M');
const out = document.getElementById('p4-iv2-out');
const fb = document.getElementById('p4-iv2-fb');
const go = document.getElementById('p4-iv2-go');
const used = new Set();
let _done = false;
function calc(){
const T = parseFloat(TI.value);
const Mg = parseFloat(MI.value); // г/моль
if(!isFinite(T) || T <= 0){ feedback(fb, false, '&#10007; Введи положительную температуру (К).'); return; }
const kB = PHYS.CONST.kB;
const R = PHYS.CONST.R;
const M = Mg * 1e-3; // кг/моль
const Ek = 1.5 * kB * T; // Дж
const v = Math.sqrt(3 * R * T / M);
const m0 = M / PHYS.CONST.NA;
out.innerHTML =
'<div style="margin-bottom:8px"><b>Подставляем:</b></div>'
+ '<div style="margin-bottom:8px">$\\overline{E_k} = \\dfrac{3}{2} k_B T = \\dfrac{3}{2} \\cdot 1{,}38 \\cdot 10^{-23} \\cdot ' + (+T.toFixed(2)) + ' \\approx ' + (+(Ek/1e-21).toFixed(2)) + ' \\cdot 10^{-21}$ Дж</div>'
+ '<div style="margin-bottom:8px">$v_{ср.кв.} = \\sqrt{\\dfrac{3 R T}{M}} = \\sqrt{\\dfrac{3 \\cdot 8{,}314 \\cdot ' + (+T.toFixed(2)) + '}{' + Mg + ' \\cdot 10^{-3}}} \\approx ' + Math.round(v) + '$ м/с</div>'
+ '<div style="font-size:.88rem;color:var(--muted)">Масса одной молекулы: $m_0 = M/N_A \\approx ' + (+(m0/1e-26).toFixed(3)) + ' \\cdot 10^{-26}$ кг</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add(T+':'+Mg);
if(!_done && used.size >= 3){ _done = true; addXp(10, 'p4-iv2'); bumpProgress('p4', 15); }
}
go.addEventListener('click', calc);
TI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
})();
/* IV3 — Конвертер шкал */
(function(){
const opts = ['0 К', '273 К', '300 К', '373 К'];
const Q = [
{ q:'$t = 0°$C — это сколько К?', ans:1, why:'$T = 0 + 273 = 273$ К.' },
{ q:'$t = -273°$C — это сколько К? (округлим)', ans:0, why:'$T = -273 + 273 = 0$ К — абсолютный нуль.' },
{ q:'$t = 100°$C — это сколько К?', ans:3, why:'$T = 100 + 273 = 373$ К.' },
{ q:'$t = 27°$C (комнатная) — это сколько К?', ans:2, why:'$T = 27 + 273 = 300$ К.' },
{ q:'Абсолютный нуль — это какая температура?', ans:0, why:'Абсолютный нуль = $0$ К $= -273{,}15°$C.' },
{ q:'Тёплая комната ($t \\approx 27°$C) — это какая температура $T$?', ans:2, why:'Комнатная $T \\approx 300$ К.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p4-iv3-q');
const oEl = document.getElementById('p4-iv3-opts');
const fb = document.getElementById('p4-iv3-fb');
const iEl = document.getElementById('p4-iv3-i');
const sEl = document.getElementById('p4-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p4-iv3'); bumpProgress('p4', 25); }
else if(score >= 4){ addXp(8, 'p4-iv3'); bumpProgress('p4', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = opts.map((o, j) => '<button class="btn primary" data-v="'+j+'">'+o+'</button>').join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p4-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр температуры */
(function(){
const Q = [
{ q:'При $T = 300$ К средняя кинетическая энергия молекулы. Введи мантиссу для $a \\cdot 10^{-21}$ Дж.', ans:6.2, hint:'$E_k = \\frac{3}{2} k_B T = 1{,}5 \\cdot 1{,}38 \\cdot 10^{-23} \\cdot 300 \\approx 6{,}21 \\cdot 10^{-21}$ Дж' },
{ q:'$t = 27°$C — это сколько К?', ans:300, hint:'$T = t + 273 = 300$' },
{ q:'При какой температуре $\\overline{E_k}$ удвоится по сравнению с $T = 300$ К?', ans:600, hint:'$E_k \\sim T$, значит $T = 2 \\cdot 300 = 600$ К' },
{ q:'$v_{ср.кв.}$ молекул азота ($M = 28$ г/моль) при $T = 300$ К, в м/с (допуск $\\pm 20$).', ans:516, hint:'$v = \\sqrt{3 R T / M} = \\sqrt{3 \\cdot 8{,}3 \\cdot 300 / 0{,}028} \\approx 516$ м/с' },
{ q:'При $T = 0$ К средняя кинетическая энергия молекулы (Дж)?', ans:0, hint:'$E_k = \\frac{3}{2} k_B \\cdot 0 = 0$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p4-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p4-iv4'); bumpProgress('p4', 25); }
else if(score >= 3){ addXp(8, 'p4-iv4'); bumpProgress('p4', 15); }
return;
}
document.getElementById('p4-iv4-i').textContent = (i+1);
document.getElementById('p4-iv4-s').textContent = score;
document.getElementById('p4-iv4-q').innerHTML = Q[i].q;
document.getElementById('p4-iv4-ans').value = '';
renderMath(document.getElementById('p4-iv4-q'));
document.getElementById('p4-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p4-iv4-fb');
const raw = document.getElementById('p4-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
let tol;
if(Q[i].ans === 0) tol = 0.05;
else if(Q[i].ans === 516) tol = 20;
else tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p4-iv4-s').textContent = score;
i++;
setTimeout(show, 1700);
}
document.getElementById('p4-iv4-go').addEventListener('click', go);
document.getElementById('p4-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p4-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p4');
}
function build_p5(){
const box = document.getElementById('p5-body');
let html = '';
/* THEORY 1 — Уравнение Менделеева–Клапейрона */
html += makeCard('theory', "Уравнение Менделеева–Клапейрона", "§5", `
<p><b>Уравнение состояния идеального газа</b> связывает три макропараметра — давление $p$, объём $V$ и температуру $T$. Для $\\nu$ молей газа:</p>
<p style="text-align:center;margin:10px 0">$$pV = \\nu R T$$</p>
<p>где:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$p$ — давление (Па);</li>
<li>$V$ — объём (м³);</li>
<li>$T$ — абсолютная температура (К);</li>
<li>$\\nu$ — количество вещества (моль);</li>
<li>$R = 8{,}314$ Дж/(моль·К) — универсальная газовая постоянная.</li>
</ul>
<p style="margin-top:8px"><b>Через массу газа</b> ($\\nu = m/M$):</p>
<p style="text-align:center;margin:10px 0">$$pV = \\dfrac{m}{M} R T$$</p>
<p><b>Через концентрацию</b> ($n = N/V$, $\\nu = N/N_A$, $R = N_A k_B$):</p>
<p style="text-align:center;margin:10px 0">$$p = n k_B T$$</p>
<p>Это разные формы одного и того же уравнения состояния.</p>
`);
/* THEORY 2 — Объединённый газовый закон */
html += makeCard('rule', "Объединённый газовый закон", "§5", `
<p>Для <b>постоянной массы</b> идеального газа при переходе из состояния 1 в состояние 2:</p>
<p style="text-align:center;margin:10px 0">$$\\dfrac{p V}{T} = \\text{const} \\quad \\Rightarrow \\quad \\dfrac{p_1 V_1}{T_1} = \\dfrac{p_2 V_2}{T_2}$$</p>
<p>Это позволяет связать два состояния газа без знания $\\nu$ или $M$.</p>
<p style="margin-top:10px"><b>Пример.</b> Газ при $T_1 = 300$ К, $p_1 = 1$ атм, $V_1 = 10$ л нагрели до $T_2 = 450$ К, объём стал $V_2 = 12$ л. Найти $p_2$.</p>
<p style="text-align:center;margin:10px 0">$$p_2 = \\dfrac{p_1 V_1 T_2}{T_1 V_2} = \\dfrac{1 \\cdot 10 \\cdot 450}{300 \\cdot 12} = 1{,}25 \\text{ атм}$$</p>
`);
/* THEORY 3 — Нормальные условия и молярный объём */
html += makeCard('example', "Нормальные условия и молярный объём", "§5", `
<p><b>Нормальные условия</b> (н.у.):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$T_0 = 273$ К ($0°$C);</li>
<li>$p_0 = 101{,}3$ кПа $\\approx 1$ атм.</li>
</ul>
<p>При нормальных условиях <b>молярный объём</b> любого идеального газа одинаков:</p>
<p style="text-align:center;margin:10px 0">$$V_M = \\dfrac{R T_0}{p_0} = \\dfrac{8{,}314 \\cdot 273}{101300} \\approx 22{,}4 \\text{ л/моль}$$</p>
<p>1 моль <b>любого</b> идеального газа (водорода, азота, кислорода, углекислого газа…) при $0°$C и 1 атм занимает $22{,}4$ литра.</p>
<p style="margin-top:8px"><b>Закон Авогадро:</b> в равных объёмах разных газов при одинаковых $p$ и $T$ содержится одинаковое число молекул.</p>
`);
/* INTERACTIVE 1 — Визуализатор уравнения состояния */
html += `<div class="wg" id="p5-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Визуализатор уравнения состояния</div></div>
<div class="wg-help">Меняй $\\nu$, $T$, $V$ — получай давление по формуле $p = \\nu R T / V$. Цвет «контейнера» — по $T$, число точек — по $\\nu$.</div>
<div class="sliders">
<label>Кол-во вещества $\\nu$: <b id="p5-iv1-nL">1.0</b> моль <input type="range" id="p5-iv1-n" min="0.1" max="5" value="1" step="0.1"></label>
<label>Температура $T$: <b id="p5-iv1-tL">300</b> К <input type="range" id="p5-iv1-t" min="100" max="800" value="300" step="10"></label>
<label>Объём $V$: <b id="p5-iv1-vL">10</b> л <input type="range" id="p5-iv1-v" min="1" max="50" value="10" step="0.5"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p5-iv1-svg" viewBox="0 0 380 200" width="100%" style="max-width:380px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem;line-height:1.85">
<div>$pV = \\nu R T = $ <b id="p5-iv1-rhs">—</b> Дж</div>
<div>Давление: $p = \\dfrac{\\nu R T}{V} \\approx $ <b id="p5-iv1-pPa">—</b> Па $\\approx$ <b id="p5-iv1-pAtm">—</b> атм</div>
</div>
</div>`;
/* INTERACTIVE 2 — Калькулятор Менделеева–Клапейрона */
html += `<div class="wg" id="p5-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор Менделеева–Клапейрона</div></div>
<div class="wg-help">Выбери искомую величину — введи остальные параметры. Используется $pV = (m/M) R T$.</div>
<div style="display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p5-iv2-mode" value="p" checked> Найти $p$</label>
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p5-iv2-mode" value="V"> Найти $V$</label>
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p5-iv2-mode" value="T"> Найти $T$</label>
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p5-iv2-mode" value="m"> Найти $m$</label>
</div>
<div id="p5-iv2-inputs" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin-bottom:10px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p5-iv2-go">Вычислить</button>
</div>
<div id="p5-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p5-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD: Какое отношение работает? */
html += `<div class="wg" id="p5-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какое отношение работает?</div></div>
<div class="wg-help">Перетащи 6 формул в 3 ящика — какой раздел теории газа описывает каждая.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 6 формул — 3 категории</div>
<div id="p5-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="eq">Уравнение состояния</h5><div class="drop-items" data-cat="eq"></div></div>
<div class="drop-box"><h5 data-cat="comb">Объединённый газовый закон</h5><div class="drop-items" data-cat="comb"></div></div>
<div class="drop-box"><h5 data-cat="sp">Частный случай / константа</h5><div class="drop-items" data-cat="sp"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p5-iv3-check">Проверить</button><button class="btn" id="p5-iv3-reset">Сначала</button></div>
<div class="feedback" id="p5-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр уравнения состояния */
html += `<div class="wg" id="p5-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр уравнения состояния</div></div>
<div class="wg-help">6 задач. Используй $R = 8{,}3$ Дж/(моль·К), $1$ атм $= 10^5$ Па, $V_M = 22{,}4$ л/моль.</div>
<div class="score-display"><span>Задача <b id="p5-iv4-i">1</b> / 6</span><span>Очки: <b id="p5-iv4-s">0</b> / 6</span></div>
<div id="p5-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p5-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p5-iv4-go">Проверить</button>
<button class="btn" id="p5-iv4-start">Заново</button>
</div>
<div class="feedback" id="p5-iv4-fb"></div>
</div>`;
html += secNav('p4', 'p6');
html += readButton('p5');
box.innerHTML = html;
renderMath(box);
/* IV1 — Визуализатор уравнения состояния */
(function(){
const svg = document.getElementById('p5-iv1-svg');
const nInp = document.getElementById('p5-iv1-n');
const tInp = document.getElementById('p5-iv1-t');
const vInp = document.getElementById('p5-iv1-v');
const nLab = document.getElementById('p5-iv1-nL');
const tLab = document.getElementById('p5-iv1-tL');
const vLab = document.getElementById('p5-iv1-vL');
const rhsEl = document.getElementById('p5-iv1-rhs');
const pPaEl = document.getElementById('p5-iv1-pPa');
const pAtmEl = document.getElementById('p5-iv1-pAtm');
const R = 8.314;
const seen = new Set();
let _xpDone = false;
function tColor(T){
const t = Math.max(0, Math.min(1, (T - 100) / 700));
const r = Math.round(255 * (0.16 + t * (0.94 - 0.16)));
const g = Math.round(255 * (0.5 + t * (0.27 - 0.5)));
const b = Math.round(255 * (0.96 + t * (0.21 - 0.96)));
return 'rgb('+r+','+g+','+b+')';
}
function fmtSci(x){
if(x === 0) return '0';
const a = Math.abs(x);
if(a >= 1e4 || a < 1e-2){
const exp = Math.floor(Math.log10(a));
const m = x / Math.pow(10, exp);
return m.toFixed(2) + '·10^{' + exp + '}';
}
return (+x.toFixed(2)).toString();
}
function render(){
const nu = +nInp.value;
const T = +tInp.value;
const Vl = +vInp.value;
nLab.textContent = nu.toFixed(1);
tLab.textContent = T;
vLab.textContent = Vl.toFixed(1);
const Vm3 = Vl * 1e-3;
const pPa = nu * R * T / Vm3;
const pAtm = pPa / 1e5;
const rhs = nu * R * T;
rhsEl.textContent = rhs.toFixed(1);
pPaEl.textContent = (pPa / 1e5).toFixed(2) + '·10⁵';
pAtmEl.textContent = pAtm.toFixed(2);
// SVG: прямоугольник, ширина по V (1..50 л → 60..360 px)
const W = 380, H = 200, pad = 10;
const boxW = 60 + (Vl - 1) * (300 / 49);
const boxH = 130;
const bx = (W - boxW) / 2, by = pad + 30;
const col = tColor(T);
let g = '';
g += '<rect x="'+bx+'" y="'+by+'" width="'+boxW+'" height="'+boxH+'" rx="6" fill="'+col+'" fill-opacity="0.18" stroke="'+col+'" stroke-width="2"/>';
// молекулы — пропорционально nu (1..30 точек)
const Nshow = Math.max(3, Math.round(nu * 8));
for(let i = 0; i < Nshow; i++){
const px = bx + 6 + Math.random() * (boxW - 12);
const py = by + 6 + Math.random() * (boxH - 12);
g += '<circle cx="'+px.toFixed(1)+'" cy="'+py.toFixed(1)+'" r="2.8" fill="#60a5fa" opacity="0.9"/>';
}
g += '<text x="'+(W/2)+'" y="20" text-anchor="middle" fill="'+col+'" font-family="Inter,sans-serif" font-size="13" font-weight="700">T = '+T+' K · V = '+Vl.toFixed(1)+' л · ν = '+nu.toFixed(1)+' моль</text>';
g += '<text x="'+(W/2)+'" y="'+(by + boxH + 18)+'" text-anchor="middle" fill="#cbd5e1" font-family="JetBrains Mono,monospace" font-size="12">p ≈ '+pAtm.toFixed(2)+' атм</text>';
svg.innerHTML = g;
seen.add(Math.round(nu*10)+':'+T+':'+Math.round(Vl*2));
if(!_xpDone && seen.size >= 4){ _xpDone = true; addXp(10, 'p5-iv1'); bumpProgress('p5', 15); }
}
nInp.addEventListener('input', render);
tInp.addEventListener('input', render);
vInp.addEventListener('input', render);
render();
})();
/* IV2 — Калькулятор Менделеева–Клапейрона */
(function(){
const inpsBox = document.getElementById('p5-iv2-inputs');
const out = document.getElementById('p5-iv2-out');
const fb = document.getElementById('p5-iv2-fb');
const go = document.getElementById('p5-iv2-go');
const R = 8.314;
const calcCount = new Set();
let _done = false;
function fieldHTML(id, label, val){
return '<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">'+label+' <input type="number" id="'+id+'" class="tinp" style="width:100%;margin-top:6px" value="'+val+'" step="any"></label>';
}
function build(mode){
let h = '';
// Всегда показываем M (молярную массу)
h += fieldHTML('p5-iv2-M', '$M$, г/моль', '32');
if(mode !== 'p') h += fieldHTML('p5-iv2-p', '$p$, атм', '1');
if(mode !== 'V') h += fieldHTML('p5-iv2-V', '$V$, л', '22.4');
if(mode !== 'T') h += fieldHTML('p5-iv2-T', '$T$, К', '273');
if(mode !== 'm') h += fieldHTML('p5-iv2-m', '$m$, г', '32');
inpsBox.innerHTML = h;
renderMath(inpsBox);
}
function num(id){ const el = document.getElementById(id); return el ? parseFloat((el.value||'').replace(',','.')) : NaN; }
function getMode(){
const r = document.querySelector('input[name="p5-iv2-mode"]:checked');
return r ? r.value : 'p';
}
function calc(){
const mode = getMode();
const Mg = num('p5-iv2-M');
if(!isFinite(Mg) || Mg <= 0){ feedback(fb, false, '&#10007; Введи положительную $M$ (г/моль).'); return; }
const M = Mg * 1e-3;
let pPa, Vm3, T, mKg;
if(mode !== 'p'){ const pa = num('p5-iv2-p'); if(!isFinite(pa)||pa<=0){feedback(fb,false,'&#10007; Введи положительное $p$.');return;} pPa = pa * 1e5; }
if(mode !== 'V'){ const vl = num('p5-iv2-V'); if(!isFinite(vl)||vl<=0){feedback(fb,false,'&#10007; Введи положительный $V$.');return;} Vm3 = vl * 1e-3; }
if(mode !== 'T'){ T = num('p5-iv2-T'); if(!isFinite(T)||T<=0){feedback(fb,false,'&#10007; Введи положительную $T$.');return;} }
if(mode !== 'm'){ const mg = num('p5-iv2-m'); if(!isFinite(mg)||mg<=0){feedback(fb,false,'&#10007; Введи положительную $m$.');return;} mKg = mg * 1e-3; }
let res = '', resVal = 0, unit = '';
if(mode === 'p'){
// p = (m/M) R T / V
pPa = (mKg / M) * R * T / Vm3;
resVal = pPa / 1e5; unit = 'атм';
res = '$p = \\dfrac{m R T}{M V} \\approx '+(pPa/1e5).toFixed(3)+' \\cdot 10^5$ Па $\\approx '+resVal.toFixed(3)+'$ атм';
} else if(mode === 'V'){
Vm3 = (mKg / M) * R * T / pPa;
resVal = Vm3 * 1000; unit = 'л';
res = '$V = \\dfrac{m R T}{M p} \\approx '+(Vm3*1000).toFixed(3)+'$ л';
} else if(mode === 'T'){
T = pPa * Vm3 * M / (mKg * R);
resVal = T; unit = 'К';
res = '$T = \\dfrac{p V M}{m R} \\approx '+T.toFixed(1)+'$ К';
} else { // m
mKg = pPa * Vm3 * M / (R * T);
resVal = mKg * 1000; unit = 'г';
res = '$m = \\dfrac{p V M}{R T} \\approx '+(mKg*1000).toFixed(3)+'$ г';
}
out.innerHTML = '<div style="margin-bottom:8px"><b>Уравнение:</b> $pV = \\dfrac{m}{M} R T$</div>'
+ '<div style="margin-bottom:8px"><b>Решение:</b> '+res+'</div>'
+ '<div><b>Ответ:</b> <span style="font-weight:700;color:var(--pri2)">'+(+resVal.toFixed(3))+' '+unit+'</span></div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
calcCount.add(mode + ':' + Math.round(resVal*100));
if(!_done && calcCount.size >= 3){ _done = true; addXp(10, 'p5-iv2'); bumpProgress('p5', 15); }
}
document.querySelectorAll('input[name="p5-iv2-mode"]').forEach(r => r.addEventListener('change', () => build(getMode())));
go.addEventListener('click', calc);
build('p');
})();
/* IV3 — DnD сортер формул */
(function(){
const items = [
{ id:'f1', cat:'eq', html:'$pV = \\nu R T$' },
{ id:'f2', cat:'comb', html:'$\\dfrac{p_1 V_1}{T_1} = \\dfrac{p_2 V_2}{T_2}$' },
{ id:'f3', cat:'eq', html:'$p = n k_B T$' },
{ id:'f4', cat:'eq', html:'$pV = \\dfrac{m}{M} R T$' },
{ id:'f5', cat:'sp', html:'$V_M = 22{,}4$ л/моль' },
{ id:'f6', cat:'comb', html:'$\\dfrac{V_1}{T_1} = \\dfrac{V_2}{T_2}$ (при $p$=const)' },
];
const sorter = setupSorter({
poolId:'p5-iv3-pool',
scopeSelector:'#p5-iv3',
items: items,
cats:['eq','comb','sp'],
columnLayout:false,
});
document.getElementById('p5-iv3-check').addEventListener('click', () => {
const fb = document.getElementById('p5-iv3-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 формул.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 верно! +10 XP'); addXp(10,'p5-iv3'); bumpProgress('p5', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p5-iv3-reset').addEventListener('click', () => { sorter.reset(); document.getElementById('p5-iv3-fb').style.display = 'none'; });
})();
/* IV4 — Тренажёр уравнения состояния */
(function(){
const Q = [
{ q:'1 моль газа при $T = 300$ К, $p = 1$ атм. Найти $V$ в литрах. ($R = 8{,}3$)', ans:24.9, hint:'$V = \\nu R T / p = 1 \\cdot 8{,}3 \\cdot 300 / 10^5 = 0{,}0249$ м³ = 24,9 л' },
{ q:'2 моль газа при $T = 273$ К, $V = 44{,}8$ л. Найти $p$ в атм.', ans:1, hint:'$p = \\nu R T / V = 2 \\cdot 8{,}3 \\cdot 273 / 0{,}0448 \\approx 10^5$ Па = 1 атм' },
{ q:'При нагревании от $T_1 = 300$ К до $T_2 = 600$ К при $V$ = const, во сколько раз изменится $p$?', ans:2, hint:'$p \\sim T$ при $V$=const, $p_2/p_1 = 600/300 = 2$' },
{ q:'4 г водорода ($M = 2$ г/моль) при $T = 273$ К, $p = 1$ атм. $V$ в литрах?', ans:44.8, hint:'$\\nu = m/M = 4/2 = 2$ моль. $V = 2 \\cdot V_M = 2 \\cdot 22{,}4 = 44{,}8$ л' },
{ q:'$V_1 = 10$ л при $T_1 = 300$ К, $p_1 = 1$ атм. Нагрев до $T_2 = 360$ К при $p$ = const. $V_2 = ?$ л', ans:12, hint:'$V_2 = V_1 T_2/T_1 = 10 \\cdot 360/300 = 12$' },
{ q:'6 г кислорода ($M = 32$ г/моль). Сколько это моль? (до 0,01)', ans:0.19, hint:'$\\nu = m/M = 6/32 = 0{,}1875 \\approx 0{,}19$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p5-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p5-iv4'); bumpProgress('p5', 25); }
else if(score >= 4){ addXp(8, 'p5-iv4'); bumpProgress('p5', 15); }
return;
}
document.getElementById('p5-iv4-i').textContent = (i+1);
document.getElementById('p5-iv4-s').textContent = score;
document.getElementById('p5-iv4-q').innerHTML = Q[i].q;
document.getElementById('p5-iv4-ans').value = '';
renderMath(document.getElementById('p5-iv4-q'));
document.getElementById('p5-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p5-iv4-fb');
const raw = document.getElementById('p5-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
let tol;
if(Q[i].ans === 0.19) tol = 0.015;
else if(Q[i].ans === 24.9 || Q[i].ans === 44.8) tol = 0.6;
else tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p5-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p5-iv4-go').addEventListener('click', go);
document.getElementById('p5-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p5-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p5');
}
function build_p6(){
const box = document.getElementById('p6-body');
let html = '';
/* THEORY 1 — Три изопроцесса */
html += makeCard('theory', "Три изопроцесса", "§6", `
<p><b>Изопроцесс</b> — процесс, при котором один из параметров идеального газа остаётся постоянным (при <b>постоянной массе</b> газа).</p>
<p style="margin-top:10px"><b>1) Изотермический</b> ($T = \\text{const}$) — <b>закон Бойля–Мариотта</b>:</p>
<p style="text-align:center;margin:10px 0">$$pV = \\text{const} \\quad \\Leftrightarrow \\quad p_1 V_1 = p_2 V_2$$</p>
<p><b>2) Изобарный</b> ($p = \\text{const}$) — <b>закон Гей-Люссака</b>:</p>
<p style="text-align:center;margin:10px 0">$$\\dfrac{V}{T} = \\text{const} \\quad \\Leftrightarrow \\quad \\dfrac{V_1}{T_1} = \\dfrac{V_2}{T_2}$$</p>
<p><b>3) Изохорный</b> ($V = \\text{const}$) — <b>закон Шарля</b>:</p>
<p style="text-align:center;margin:10px 0">$$\\dfrac{p}{T} = \\text{const} \\quad \\Leftrightarrow \\quad \\dfrac{p_1}{T_1} = \\dfrac{p_2}{T_2}$$</p>
<p style="margin-top:8px">Все три закона — частные случаи объединённого газового закона $pV/T = \\text{const}$.</p>
`);
/* THEORY 2 — Графики */
html += makeCard('rule', "Графики изопроцессов", "§6", `
<p>В трёх системах координат изопроцессы выглядят так:</p>
<table style="width:100%;border-collapse:collapse;margin:10px 0;font-size:.92rem">
<thead>
<tr style="background:var(--sec-acc-soft)">
<th style="padding:8px;border:1px solid var(--border);text-align:left">Процесс</th>
<th style="padding:8px;border:1px solid var(--border)">$p$$V$</th>
<th style="padding:8px;border:1px solid var(--border)">$V$$T$</th>
<th style="padding:8px;border:1px solid var(--border)">$p$$T$</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding:8px;border:1px solid var(--border)"><b style="color:#ea580c">Изотерма</b> ($T$=const)</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">гипербола</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">прямая через 0</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">прямая через 0</td>
</tr>
<tr>
<td style="padding:8px;border:1px solid var(--border)"><b style="color:#2563eb">Изобара</b> ($p$=const)</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">горизонт. прямая</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">прямая через 0</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">горизонт. прямая</td>
</tr>
<tr>
<td style="padding:8px;border:1px solid var(--border)"><b style="color:#10b981">Изохора</b> ($V$=const)</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">вертик. прямая</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">горизонт. прямая</td>
<td style="padding:8px;border:1px solid var(--border);text-align:center">прямая через 0</td>
</tr>
</tbody>
</table>
<p>Главные характеристики:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Изотерма</b> на $p$$V$ — гипербола: $p = \\nu R T / V$.</li>
<li>Изобара и изохора на $V$–$T$ и $p$–$T$ — прямые, проходящие <b>через начало координат</b>.</li>
<li>Чем больше $T$ у изотермы, тем дальше гипербола от осей.</li>
</ul>
`);
/* THEORY 3 — Примеры */
html += makeCard('example', "Применение и примеры", "§6", `
<p><b>Изотермический процесс:</b> воздушный шарик медленно сжимают руками — температура почти не меняется (теплообмен со средой). $p_1 V_1 = p_2 V_2$.</p>
<p style="margin-top:8px"><b>Изобарный процесс:</b> воздух в открытом цилиндре с поршнем нагревают — давление равно атмосферному (= const), объём растёт пропорционально $T$. $V_1 / T_1 = V_2 / T_2$.</p>
<p style="margin-top:8px"><b>Изохорный процесс:</b> газ в герметичном сосуде нагревают — объём фиксирован, давление растёт пропорционально $T$. $p_1 / T_1 = p_2 / T_2$.</p>
<p style="margin-top:10px"><b>Пример.</b> Газ в шине автомобиля изохорно нагревают летом от $T_1 = 290$ К до $T_2 = 320$ К. Если $p_1 = 2$ атм:</p>
<p style="text-align:center;margin:10px 0">$$p_2 = p_1 \\dfrac{T_2}{T_1} = 2 \\cdot \\dfrac{320}{290} \\approx 2{,}21 \\text{ атм}$$</p>
`);
/* INTERACTIVE 1 — Графики изопроцессов (главный визуал) */
html += `<div class="wg" id="p6-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Графики изопроцессов: $p$$V$, $V$$T$, $p$$T$</div></div>
<div class="wg-help">Переключай тип процесса и параметр — смотри, как меняются 3 диаграммы. Текущая кривая ярче, две альтернативные — бледнее.</div>
<div style="display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p6-iv1-proc" value="iso" checked> <span style="color:#ea580c;font-weight:700">Изотерма</span></label>
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p6-iv1-proc" value="bar"> <span style="color:#2563eb;font-weight:700">Изобара</span></label>
<label style="display:inline-flex;align-items:center;gap:6px;font-size:.92rem"><input type="radio" name="p6-iv1-proc" value="hor"> <span style="color:#10b981;font-weight:700">Изохора</span></label>
</div>
<div class="sliders">
<label id="p6-iv1-paramLbl">Температура $T$: <b id="p6-iv1-pL">300</b> К <input type="range" id="p6-iv1-param" min="200" max="600" value="300" step="10"></label>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:10px;margin-top:10px">
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px"><div style="text-align:center;font-weight:700;font-size:.86rem;margin-bottom:4px">$p$$V$ диаграмма</div><svg id="p6-iv1-pv" viewBox="0 0 380 260" width="100%" style="height:auto"></svg></div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px"><div style="text-align:center;font-weight:700;font-size:.86rem;margin-bottom:4px">$V$$T$ диаграмма</div><svg id="p6-iv1-vt" viewBox="0 0 380 260" width="100%" style="height:auto"></svg></div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px"><div style="text-align:center;font-weight:700;font-size:.86rem;margin-bottom:4px">$p$$T$ диаграмма</div><svg id="p6-iv1-pt" viewBox="0 0 380 260" width="100%" style="height:auto"></svg></div>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.93rem;line-height:1.85" id="p6-iv1-info"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор изопроцессов */
html += `<div class="wg" id="p6-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор изопроцессов</div></div>
<div class="wg-help">Выбери тип процесса, введи $p_1, V_1, T_1$ и одну величину состояния 2 — получи недостающие.</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-bottom:10px" id="p6-iv2-tabs">
<button class="btn primary" data-mode="iso" style="background:#ea580c;border-color:#ea580c">Изотермический</button>
<button class="btn" data-mode="bar">Изобарный</button>
<button class="btn" data-mode="hor">Изохорный</button>
</div>
<div id="p6-iv2-inputs" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:10px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p6-iv2-go">Вычислить</button>
</div>
<div id="p6-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p6-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD: какой процесс */
html += `<div class="wg" id="p6-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой процесс?</div></div>
<div class="wg-help">Перетащи 6 ситуаций в нужный процесс.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 6 ситуаций — 3 процесса</div>
<div id="p6-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="iso" style="color:#ea580c">Изотермический</h5><div class="drop-items" data-cat="iso"></div></div>
<div class="drop-box"><h5 data-cat="bar" style="color:#2563eb">Изобарный</h5><div class="drop-items" data-cat="bar"></div></div>
<div class="drop-box"><h5 data-cat="hor" style="color:#10b981">Изохорный</h5><div class="drop-items" data-cat="hor"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p6-iv3-check">Проверить</button><button class="btn" id="p6-iv3-reset">Сначала</button></div>
<div class="feedback" id="p6-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр изопроцессов */
html += `<div class="wg" id="p6-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр изопроцессов</div></div>
<div class="wg-help">6 задач. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p6-iv4-i">1</b> / 6</span><span>Очки: <b id="p6-iv4-s">0</b> / 6</span></div>
<div id="p6-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p6-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p6-iv4-go">Проверить</button>
<button class="btn" id="p6-iv4-start">Заново</button>
</div>
<div class="feedback" id="p6-iv4-fb"></div>
</div>`;
html += secNav('p5', 'p7');
html += readButton('p6');
box.innerHTML = html;
renderMath(box);
/* IV1 — Графики изопроцессов */
(function(){
const R = 8.314;
const NU = 1; // 1 моль для расчётов
const pvSvg = document.getElementById('p6-iv1-pv');
const vtSvg = document.getElementById('p6-iv1-vt');
const ptSvg = document.getElementById('p6-iv1-pt');
let paramInpRef = document.getElementById('p6-iv1-param');
let paramLabRef = document.getElementById('p6-iv1-pL');
const paramLbl = document.getElementById('p6-iv1-paramLbl');
const info = document.getElementById('p6-iv1-info');
const COL = { iso:'#ea580c', bar:'#2563eb', hor:'#10b981' };
const seen = new Set();
let _xpDone = false;
function getMode(){ const r = document.querySelector('input[name="p6-iv1-proc"]:checked'); return r ? r.value : 'iso'; }
function paramConfig(mode){
if(mode === 'iso') return { label:'Температура $T$', unit:'К', min:200, max:600, step:10, val:300, set:[250, 400, 550] };
if(mode === 'bar') return { label:'Давление $p$', unit:'атм', min:0.5, max:3, step:0.1, val:1, set:[0.7, 1.5, 2.5] };
return { label:'Объём $V$', unit:'л', min:5, max:20, step:0.5, val:10, set:[6, 12, 18] };
}
function applyParam(mode){
const cfg = paramConfig(mode);
// Перестраиваем label целиком, чтобы корректно работал и до, и после KaTeX-рендера
paramLbl.innerHTML = cfg.label + ': <b id="p6-iv1-pL">'+cfg.val+'</b> '+cfg.unit+' <input type="range" id="p6-iv1-param" min="'+cfg.min+'" max="'+cfg.max+'" value="'+cfg.val+'" step="'+cfg.step+'">';
renderMath(paramLbl);
// Обновляем ссылки на пересозданные элементы
paramInpRef = document.getElementById('p6-iv1-param');
paramLabRef = document.getElementById('p6-iv1-pL');
paramInpRef.addEventListener('input', render);
}
// Диаграмма p-V: x ∈ [1, 25] л, y ∈ [0, 4] атм
function plotPV(active, others, activeVal){
const W=380, H=260, pad=34;
const a = axes2D(W, H, pad, 0, 25, 0, 4);
let g = a.content;
g += '<text x="'+(W-12)+'" y="'+(H-pad+14)+'" font-size="10" fill="#0f172a">V, л</text>';
g += '<text x="'+(pad-10)+'" y="'+(pad-6)+'" font-size="10" fill="#0f172a">p, атм</text>';
function drawIsotherm(T, color, opacity){
// p (атм) = ν R T / V(м³) / 10^5; для 1 моль: p = 8.314*T / (V_л * 10^-3 * 10^5) = 8.314*T/(V_л*100)
const f = V => (NU * R * T) / (V * 1e-3) / 1e5;
return '<g opacity="'+opacity+'">'+plotFunc(f, 0.5, 25, a.toX, a.toY, color, 220)+'</g>';
}
function drawIsobar(p, color, opacity){
// горизонтальная прямая p = const
return '<g opacity="'+opacity+'"><line x1="'+a.toX(0)+'" y1="'+a.toY(p)+'" x2="'+a.toX(25)+'" y2="'+a.toY(p)+'" stroke="'+color+'" stroke-width="2"/></g>';
}
function drawIsohor(V, color, opacity){
// вертикальная прямая V = const
return '<g opacity="'+opacity+'"><line x1="'+a.toX(V)+'" y1="'+a.toY(0)+'" x2="'+a.toX(V)+'" y2="'+a.toY(4)+'" stroke="'+color+'" stroke-width="2"/></g>';
}
others.forEach(v => {
if(active === 'iso') g += drawIsotherm(v, COL.iso, 0.25);
else if(active === 'bar') g += drawIsobar(v, COL.bar, 0.25);
else g += drawIsohor(v, COL.hor, 0.25);
});
if(active === 'iso') g += drawIsotherm(activeVal, COL.iso, 1);
else if(active === 'bar') g += drawIsobar(activeVal, COL.bar, 1);
else g += drawIsohor(activeVal, COL.hor, 1);
pvSvg.innerHTML = g;
}
// Диаграмма V-T: x ∈ [0, 600] К, y ∈ [0, 25] л
function plotVT(active, others, activeVal){
const W=380, H=260, pad=34;
const a = axes2D(W, H, pad, 0, 600, 0, 25);
let g = a.content;
g += '<text x="'+(W-12)+'" y="'+(H-pad+14)+'" font-size="10" fill="#0f172a">T, К</text>';
g += '<text x="'+(pad-10)+'" y="'+(pad-6)+'" font-size="10" fill="#0f172a">V, л</text>';
function drawIsotherm(T, color, opacity){
// изотерма на V-T — вертикальная прямая T = const
return '<g opacity="'+opacity+'"><line x1="'+a.toX(T)+'" y1="'+a.toY(0)+'" x2="'+a.toX(T)+'" y2="'+a.toY(25)+'" stroke="'+color+'" stroke-width="2"/></g>';
}
function drawIsobar(p, color, opacity){
// V = (ν R / p) T, p в Па; V в л = ν R T / p_Па * 1000
const pPa = p * 1e5;
const k = NU * R / pPa * 1000; // V_л = k * T
return '<g opacity="'+opacity+'">'+plotFunc(T => k * T, 0, 600, a.toX, a.toY, color, 50)+'</g>';
}
function drawIsohor(V, color, opacity){
// изохора на V-T — горизонтальная прямая V = const
return '<g opacity="'+opacity+'"><line x1="'+a.toX(0)+'" y1="'+a.toY(V)+'" x2="'+a.toX(600)+'" y2="'+a.toY(V)+'" stroke="'+color+'" stroke-width="2"/></g>';
}
others.forEach(v => {
if(active === 'iso') g += drawIsotherm(v, COL.iso, 0.25);
else if(active === 'bar') g += drawIsobar(v, COL.bar, 0.25);
else g += drawIsohor(v, COL.hor, 0.25);
});
if(active === 'iso') g += drawIsotherm(activeVal, COL.iso, 1);
else if(active === 'bar') g += drawIsobar(activeVal, COL.bar, 1);
else g += drawIsohor(activeVal, COL.hor, 1);
vtSvg.innerHTML = g;
}
// Диаграмма p-T: x ∈ [0, 600] К, y ∈ [0, 4] атм
function plotPT(active, others, activeVal){
const W=380, H=260, pad=34;
const a = axes2D(W, H, pad, 0, 600, 0, 4);
let g = a.content;
g += '<text x="'+(W-12)+'" y="'+(H-pad+14)+'" font-size="10" fill="#0f172a">T, К</text>';
g += '<text x="'+(pad-10)+'" y="'+(pad-6)+'" font-size="10" fill="#0f172a">p, атм</text>';
function drawIsotherm(T, color, opacity){
// на p-T — вертикальная прямая T = const
return '<g opacity="'+opacity+'"><line x1="'+a.toX(T)+'" y1="'+a.toY(0)+'" x2="'+a.toX(T)+'" y2="'+a.toY(4)+'" stroke="'+color+'" stroke-width="2"/></g>';
}
function drawIsobar(p, color, opacity){
// на p-T — горизонтальная прямая p = const
return '<g opacity="'+opacity+'"><line x1="'+a.toX(0)+'" y1="'+a.toY(p)+'" x2="'+a.toX(600)+'" y2="'+a.toY(p)+'" stroke="'+color+'" stroke-width="2"/></g>';
}
function drawIsohor(V, color, opacity){
// p = (ν R / V) T, V в м³; p в атм = ν R T / V_м³ / 10^5
const Vm3 = V * 1e-3;
const k = NU * R / Vm3 / 1e5; // p_атм = k * T
return '<g opacity="'+opacity+'">'+plotFunc(T => k * T, 0, 600, a.toX, a.toY, color, 50)+'</g>';
}
others.forEach(v => {
if(active === 'iso') g += drawIsotherm(v, COL.iso, 0.25);
else if(active === 'bar') g += drawIsobar(v, COL.bar, 0.25);
else g += drawIsohor(v, COL.hor, 0.25);
});
if(active === 'iso') g += drawIsotherm(activeVal, COL.iso, 1);
else if(active === 'bar') g += drawIsobar(activeVal, COL.bar, 1);
else g += drawIsohor(activeVal, COL.hor, 1);
ptSvg.innerHTML = g;
}
function render(){
const mode = getMode();
const cfg = paramConfig(mode);
const v = +paramInpRef.value;
paramLabRef.textContent = (cfg.step < 1) ? v.toFixed(1) : v.toString();
plotPV(mode, cfg.set, v);
plotVT(mode, cfg.set, v);
plotPT(mode, cfg.set, v);
let infoHtml = '';
if(mode === 'iso'){
infoHtml = '<div><b style="color:#ea580c">Изотерма</b> ($T = '+v+'$ К): $pV = \\nu R T = '+(NU*R*v).toFixed(1)+'$ Дж. На $p$$V$ — гипербола.</div>';
} else if(mode === 'bar'){
infoHtml = '<div><b style="color:#2563eb">Изобара</b> ($p = '+v.toFixed(1)+'$ атм): $V/T = \\nu R / p = $ const. На $V$–$T$ — прямая через 0.</div>';
} else {
infoHtml = '<div><b style="color:#10b981">Изохора</b> ($V = '+v.toFixed(1)+'$ л): $p/T = \\nu R / V = $ const. На $p$–$T$ — прямая через 0.</div>';
}
info.innerHTML = infoHtml;
renderMath(info);
seen.add(mode + ':' + Math.round(v*10));
if(!_xpDone && seen.size >= 5){ _xpDone = true; addXp(10, 'p6-iv1'); bumpProgress('p6', 15); }
}
document.querySelectorAll('input[name="p6-iv1-proc"]').forEach(r => r.addEventListener('change', () => { applyParam(getMode()); render(); }));
applyParam('iso');
render();
})();
/* IV2 — Калькулятор изопроцессов */
(function(){
const tabs = document.getElementById('p6-iv2-tabs');
const inpsBox = document.getElementById('p6-iv2-inputs');
const out = document.getElementById('p6-iv2-out');
const fb = document.getElementById('p6-iv2-fb');
const go = document.getElementById('p6-iv2-go');
const used = new Set();
let _done = false;
let mode = 'iso';
function fieldHTML(id, label, val){
return '<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">'+label+' <input type="number" id="'+id+'" class="tinp" style="width:100%;margin-top:6px" value="'+val+'" step="any"></label>';
}
function build(){
let h = '';
if(mode === 'iso'){
h += fieldHTML('p6-iv2-p1', '$p_1$, атм', '1');
h += fieldHTML('p6-iv2-V1', '$V_1$, л', '10');
h += fieldHTML('p6-iv2-p2', '$p_2$, атм', '2');
} else if(mode === 'bar'){
h += fieldHTML('p6-iv2-V1', '$V_1$, л', '5');
h += fieldHTML('p6-iv2-T1', '$T_1$, К', '250');
h += fieldHTML('p6-iv2-T2', '$T_2$, К', '400');
} else {
h += fieldHTML('p6-iv2-p1', '$p_1$, атм', '2');
h += fieldHTML('p6-iv2-T1', '$T_1$, К', '290');
h += fieldHTML('p6-iv2-T2', '$T_2$, К', '348');
}
inpsBox.innerHTML = h;
renderMath(inpsBox);
out.innerHTML = '';
fb.style.display = 'none';
}
function num(id){ const el = document.getElementById(id); return el ? parseFloat((el.value||'').replace(',','.')) : NaN; }
function calc(){
let res = '', val = 0, unit = '';
if(mode === 'iso'){
const p1 = num('p6-iv2-p1'), V1 = num('p6-iv2-V1'), p2 = num('p6-iv2-p2');
if(![p1,V1,p2].every(x => isFinite(x) && x>0)){ feedback(fb,false,'&#10007; Все значения должны быть положительными.'); return; }
val = p1 * V1 / p2; unit = 'л';
res = '$p_1 V_1 = p_2 V_2 \\Rightarrow V_2 = \\dfrac{p_1 V_1}{p_2} = \\dfrac{'+p1+' \\cdot '+V1+'}{'+p2+'} \\approx '+val.toFixed(3)+'$ л';
} else if(mode === 'bar'){
const V1 = num('p6-iv2-V1'), T1 = num('p6-iv2-T1'), T2 = num('p6-iv2-T2');
if(![V1,T1,T2].every(x => isFinite(x) && x>0)){ feedback(fb,false,'&#10007; Все значения должны быть положительными.'); return; }
val = V1 * T2 / T1; unit = 'л';
res = '$\\dfrac{V_1}{T_1} = \\dfrac{V_2}{T_2} \\Rightarrow V_2 = \\dfrac{V_1 T_2}{T_1} = \\dfrac{'+V1+' \\cdot '+T2+'}{'+T1+'} \\approx '+val.toFixed(3)+'$ л';
} else {
const p1 = num('p6-iv2-p1'), T1 = num('p6-iv2-T1'), T2 = num('p6-iv2-T2');
if(![p1,T1,T2].every(x => isFinite(x) && x>0)){ feedback(fb,false,'&#10007; Все значения должны быть положительными.'); return; }
val = p1 * T2 / T1; unit = 'атм';
res = '$\\dfrac{p_1}{T_1} = \\dfrac{p_2}{T_2} \\Rightarrow p_2 = \\dfrac{p_1 T_2}{T_1} = \\dfrac{'+p1+' \\cdot '+T2+'}{'+T1+'} \\approx '+val.toFixed(3)+'$ атм';
}
const lawName = mode==='iso' ? 'Бойля–Мариотта' : (mode==='bar' ? 'Гей-Люссака' : 'Шарля');
out.innerHTML = '<div style="margin-bottom:8px"><b>Закон '+lawName+':</b></div>'
+ '<div style="margin-bottom:8px">'+res+'</div>'
+ '<div><b>Ответ:</b> <span style="font-weight:700;color:var(--pri2)">'+(+val.toFixed(3))+' '+unit+'</span></div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add(mode);
if(!_done && used.size === 3){ _done = true; addXp(10, 'p6-iv2'); bumpProgress('p6', 15); }
}
tabs.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
mode = b.dataset.mode;
tabs.querySelectorAll('button').forEach(x => { x.className = 'btn'; x.style.background=''; x.style.borderColor=''; });
b.className = 'btn primary';
const colMap = { iso:'#ea580c', bar:'#2563eb', hor:'#10b981' };
b.style.background = colMap[mode]; b.style.borderColor = colMap[mode];
build();
});
});
go.addEventListener('click', calc);
build();
})();
/* IV3 — DnD: какой процесс */
(function(){
const items = [
{ id:'s1', cat:'iso', html:'Газ медленно сжимают в баллоне при контакте с термостатом' },
{ id:'s2', cat:'bar', html:'Газ нагревают в открытом цилиндре с подвижным поршнем' },
{ id:'s3', cat:'hor', html:'Газ в герметичном баллоне нагревают электронагревателем' },
{ id:'s4', cat:'hor', html:'Шину автомобиля надули зимой — летом давление в ней растёт' },
{ id:'s5', cat:'iso', html:'Воздушный шарик медленно сжимают рукой' },
{ id:'s6', cat:'bar', html:'В цилиндре с поршнем над газом — атмосфера; газ нагревают, поршень выдвигается' },
];
const sorter = setupSorter({
poolId:'p6-iv3-pool',
scopeSelector:'#p6-iv3',
items: items,
cats:['iso','bar','hor'],
columnLayout:false,
});
document.getElementById('p6-iv3-check').addEventListener('click', () => {
const fb = document.getElementById('p6-iv3-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 ситуаций.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 верно! +10 XP'); addXp(10,'p6-iv3'); bumpProgress('p6', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p6-iv3-reset').addEventListener('click', () => { sorter.reset(); document.getElementById('p6-iv3-fb').style.display = 'none'; });
})();
/* IV4 — Тренажёр изопроцессов */
(function(){
const Q = [
{ q:'Изотермически газ сжали от $V_1 = 10$ л до $V_2 = 4$ л. Во сколько раз изменилось давление?', ans:2.5, hint:'$p_2/p_1 = V_1/V_2 = 10/4 = 2{,}5$' },
{ q:'Изобарно газ нагрели от $T_1 = 200$ К до $T_2 = 500$ К. Во сколько раз изменился объём?', ans:2.5, hint:'$V_2/V_1 = T_2/T_1 = 500/200 = 2{,}5$' },
{ q:'Изохорно газ нагрели от $T_1 = 300$ К до $T_2 = 600$ К. Во сколько раз изменилось $p$?', ans:2, hint:'$p_2/p_1 = T_2/T_1 = 600/300 = 2$' },
{ q:'В шине $p_1 = 2$ атм при $T_1 = 290$ К. Летом $T_2 = 348$ К. Найти $p_2$ в атм.', ans:2.4, hint:'$p_2 = p_1 T_2/T_1 = 2 \\cdot 348/290 \\approx 2{,}4$' },
{ q:'В цилиндре с поршнем $V_1 = 5$ л при $T_1 = 250$ К. Изобарный нагрев до $T_2 = 400$ К. $V_2 = ?$ л', ans:8, hint:'$V_2 = V_1 T_2/T_1 = 5 \\cdot 400/250 = 8$' },
{ q:'$p_1 = 4$ атм. Изотермически давление уменьшили до $p_2 = 1$ атм. Во сколько раз увеличился $V$?', ans:4, hint:'$V_2/V_1 = p_1/p_2 = 4/1 = 4$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p6-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p6-iv4'); bumpProgress('p6', 25); }
else if(score >= 4){ addXp(8, 'p6-iv4'); bumpProgress('p6', 15); }
return;
}
document.getElementById('p6-iv4-i').textContent = (i+1);
document.getElementById('p6-iv4-s').textContent = score;
document.getElementById('p6-iv4-q').innerHTML = Q[i].q;
document.getElementById('p6-iv4-ans').value = '';
renderMath(document.getElementById('p6-iv4-q'));
document.getElementById('p6-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p6-iv4-fb');
const raw = document.getElementById('p6-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p6-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p6-iv4-go').addEventListener('click', go);
document.getElementById('p6-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p6-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p6');
}
function build_p7(){
const box = document.getElementById('p7-body');
let html = '';
/* THEORY 1 — Кристаллические и аморфные тела */
html += makeCard('theory', "Кристаллические и аморфные тела", "§7", `
<p><b>Кристаллические тела</b> имеют упорядоченное расположение частиц — <b>кристаллическую решётку</b>.</p>
<p style="margin-top:8px">Виды кристаллических тел:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Монокристалл</b> — единая решётка во всём объёме (кварц, алмаз, поваренная соль, сахар).</li>
<li><b>Поликристалл</b> — много мелких кристалликов, ориентированных хаотически (металлы, лёд, керамика).</li>
</ul>
<p>У кристаллических тел есть <b>точная температура плавления</b>: при $t_{\\text{пл}}$ решётка разрушается, и тело переходит в жидкое состояние.</p>
<p style="margin-top:10px"><b>Аморфные тела</b> (стекло, смола, пластмассы, янтарь, воск) не имеют упорядоченной решётки. Их называют «переохлаждёнными жидкостями». При нагревании они <b>размягчаются плавно</b>, без чёткой температуры плавления.</p>
`);
/* THEORY 2 — Анизотропия и изотропия */
html += makeCard('rule', "Анизотропия и изотропия", "§7", `
<p><b>Анизотропия</b> — зависимость физических свойств от направления (твёрдость, теплопроводность, скорость распространения света). <b>Характерна для монокристаллов.</b></p>
<p style="margin-top:8px">Примеры:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Алмаз в одном направлении режется легче, чем в другом.</li>
<li>Графит легко расщепляется на тонкие чешуйки (вдоль слоёв).</li>
<li>В кварце скорость звука зависит от оси кристалла.</li>
</ul>
<p><b>Изотропия</b> — свойства одинаковы во всех направлениях. <b>Характерна для поликристаллов и аморфных тел.</b></p>
<p style="margin-top:8px">Поликристаллы состоят из множества мелких разно ориентированных кристалликов, поэтому в среднем свойства одинаковы по всем направлениям.</p>
`);
/* THEORY 3 — Типы кристаллических решёток */
html += makeCard('example', "Типы кристаллических решёток", "§7", `
<p>По типу связи частиц различают четыре основных типа решёток:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.85">
<li><b style="color:#e11d48">Ионная</b> (NaCl, KCl, CaF$_2$): чередование «+» и «−» ионов. Очень прочные, твёрдые, высокая $t_{\\text{пл}}$.</li>
<li><b style="color:#475569">Атомная</b> (алмаз, графит, кремний, германий): атомы связаны ковалентными связями. Очень твёрдые, тугоплавкие.</li>
<li><b style="color:#0ea5e9">Молекулярная</b> (лёд, нафталин, парафин, сахар, йод): связи между молекулами слабые. Мягкие, легкоплавкие.</li>
<li><b style="color:#f59e0b">Металлическая</b> (медь, железо, алюминий): положительные ионы в «море» свободных электронов. Пластичные, хорошо проводят ток и тепло.</li>
</ul>
<p style="margin-top:8px">Один и тот же элемент может образовывать разные решётки: <b>алмаз</b> (атомная решётка, очень твёрдый) и <b>графит</b> (слоистая, мягкий) — оба состоят из углерода.</p>
`);
/* INTERACTIVE 1 — Визуализатор кристаллической решётки */
html += `<div class="wg" id="p7-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Визуализатор кристаллической решётки</div></div>
<div class="wg-help">Переключай тип решётки и наблюдай расположение частиц. Просмотри все 4 типа.</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-bottom:10px" id="p7-iv1-tabs">
<button class="btn primary" data-mode="ion" style="background:#e11d48;border-color:#e11d48">Ионная</button>
<button class="btn" data-mode="atom">Атомная</button>
<button class="btn" data-mode="mol">Молекулярная</button>
<button class="btn" data-mode="met">Металлическая</button>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px;display:flex;justify-content:center">
<svg id="p7-iv1-svg" viewBox="0 0 380 280" width="100%" style="max-width:520px;height:auto"></svg>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75" id="p7-iv1-info"></div>
</div>`;
/* INTERACTIVE 2 — Кристалл или аморфное (DnD) */
html += `<div class="wg" id="p7-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Кристалл или аморфное?</div></div>
<div class="wg-help">Перетащи 8 веществ в нужный ящик.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 8 веществ — 2 группы</div>
<div id="p7-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="cryst" style="color:#2563eb">Кристаллическое</h5><div class="drop-items" data-cat="cryst"></div></div>
<div class="drop-box"><h5 data-cat="amorph" style="color:#10b981">Аморфное</h5><div class="drop-items" data-cat="amorph"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p7-iv2-check">Проверить</button><button class="btn" id="p7-iv2-reset">Сначала</button></div>
<div class="feedback" id="p7-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр: свойство → тип решётки */
html += `<div class="wg" id="p7-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой тип решётки?</div></div>
<div class="wg-help">6 веществ. Жми кнопку с типом решётки.</div>
<div class="score-display"><span>Задача <b id="p7-iv3-i">1</b> / 6</span><span>Очки: <b id="p7-iv3-s">0</b> / 6</span></div>
<div id="p7-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p7-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p7-iv3-fb"></div>
<div class="actions"><button class="btn" id="p7-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр свойств твёрдых тел */
html += `<div class="wg" id="p7-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр свойств твёрдых тел</div></div>
<div class="wg-help">5 задач. Допуск для чисел $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p7-iv4-i">1</b> / 5</span><span>Очки: <b id="p7-iv4-s">0</b> / 5</span></div>
<div id="p7-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p7-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p7-iv4-go">Проверить</button>
<button class="btn" id="p7-iv4-start">Заново</button>
</div>
<div class="feedback" id="p7-iv4-fb"></div>
</div>`;
html += secNav('p6', 'p8');
html += readButton('p7');
box.innerHTML = html;
renderMath(box);
/* IV1 — Визуализатор решётки */
(function(){
const tabs = document.getElementById('p7-iv1-tabs');
const svg = document.getElementById('p7-iv1-svg');
const info = document.getElementById('p7-iv1-info');
const seen = new Set();
let _done = false;
let mode = 'ion';
function drawIon(){
// 5×5 сетка чередующихся Na+/Cl−
let g = '<rect x="0" y="0" width="380" height="280" fill="#fafafa"/>';
const cols = 6, rows = 5, sx = 50, sy = 30, dx = 56, dy = 48;
for(let j = 0; j < rows; j++){
for(let i = 0; i < cols; i++){
const cx = sx + i * dx, cy = sy + j * dy;
const isPlus = ((i + j) % 2 === 0);
// соединительные линии (соседи справа/снизу)
if(i < cols-1) g += '<line x1="'+cx+'" y1="'+cy+'" x2="'+(cx+dx)+'" y2="'+cy+'" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 3"/>';
if(j < rows-1) g += '<line x1="'+cx+'" y1="'+cy+'" x2="'+cx+'" y2="'+(cy+dy)+'" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 3"/>';
}
}
for(let j = 0; j < rows; j++){
for(let i = 0; i < cols; i++){
const cx = sx + i * dx, cy = sy + j * dy;
const isPlus = ((i + j) % 2 === 0);
const col = isPlus ? '#e11d48' : '#2563eb';
const r = isPlus ? 13 : 17;
g += '<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="'+col+'" stroke="#0f172a" stroke-width="1.2"/>';
g += '<text x="'+cx+'" y="'+(cy+4)+'" text-anchor="middle" font-size="11" font-weight="700" fill="#fff">'+(isPlus?'Na⁺':'Cl⁻')+'</text>';
}
}
svg.innerHTML = g;
}
function drawAtom(){
// Тетраэдрическая сетка алмаза — упрощённая 2D-проекция
let g = '<rect x="0" y="0" width="380" height="280" fill="#fafafa"/>';
const sx = 50, sy = 40, dx = 70, dy = 60;
const nodes = [];
for(let j = 0; j < 4; j++){
for(let i = 0; i < 5; i++){
const off = (j % 2) * dx / 2;
nodes.push({x: sx + i*dx + off, y: sy + j*dy, i, j});
}
}
// соединения — ковалентные связи (жирные линии к ближайшим)
for(const a of nodes){
for(const b of nodes){
if(a === b) continue;
const d = Math.hypot(a.x - b.x, a.y - b.y);
if(d < dx * 1.05 && d > 0.1){
g += '<line x1="'+a.x+'" y1="'+a.y+'" x2="'+b.x+'" y2="'+b.y+'" stroke="#475569" stroke-width="3" opacity="0.7"/>';
}
}
}
for(const n of nodes){
g += '<circle cx="'+n.x+'" cy="'+n.y+'" r="11" fill="#64748b" stroke="#0f172a" stroke-width="1.2"/>';
g += '<text x="'+n.x+'" y="'+(n.y+4)+'" text-anchor="middle" font-size="10" font-weight="700" fill="#fff">C</text>';
}
svg.innerHTML = g;
}
function drawMol(){
// Молекулы H₂O в упорядоченной сетке + водородные связи (пунктир)
let g = '<rect x="0" y="0" width="380" height="280" fill="#fafafa"/>';
const cols = 4, rows = 3, sx = 60, sy = 50, dx = 90, dy = 80;
const centers = [];
for(let j = 0; j < rows; j++){
for(let i = 0; i < cols; i++){
centers.push({x: sx + i*dx, y: sy + j*dy});
}
}
// водородные связи (пунктир) между соседями
for(let i = 0; i < centers.length; i++){
for(let k = i+1; k < centers.length; k++){
const d = Math.hypot(centers[i].x - centers[k].x, centers[i].y - centers[k].y);
if(d < dx * 1.05){
g += '<line x1="'+centers[i].x+'" y1="'+centers[i].y+'" x2="'+centers[k].x+'" y2="'+centers[k].y+'" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 4"/>';
}
}
}
// молекулы H₂O: 1 кислород (синий) + 2 водорода (белые)
for(const c of centers){
const ox = c.x, oy = c.y;
const ang1 = -Math.PI/2 - 0.5, ang2 = -Math.PI/2 + 0.5;
const hr = 18;
const h1x = ox + hr*Math.cos(ang1), h1y = oy + hr*Math.sin(ang1);
const h2x = ox + hr*Math.cos(ang2), h2y = oy + hr*Math.sin(ang2);
g += '<line x1="'+ox+'" y1="'+oy+'" x2="'+h1x+'" y2="'+h1y+'" stroke="#0f172a" stroke-width="2"/>';
g += '<line x1="'+ox+'" y1="'+oy+'" x2="'+h2x+'" y2="'+h2y+'" stroke="#0f172a" stroke-width="2"/>';
g += '<circle cx="'+ox+'" cy="'+oy+'" r="11" fill="#0ea5e9" stroke="#0f172a" stroke-width="1.2"/>';
g += '<text x="'+ox+'" y="'+(oy+4)+'" text-anchor="middle" font-size="10" font-weight="700" fill="#fff">O</text>';
g += '<circle cx="'+h1x+'" cy="'+h1y+'" r="7" fill="#fff" stroke="#0f172a" stroke-width="1"/>';
g += '<text x="'+h1x+'" y="'+(h1y+3)+'" text-anchor="middle" font-size="9" font-weight="700" fill="#0f172a">H</text>';
g += '<circle cx="'+h2x+'" cy="'+h2y+'" r="7" fill="#fff" stroke="#0f172a" stroke-width="1"/>';
g += '<text x="'+h2x+'" y="'+(h2y+3)+'" text-anchor="middle" font-size="9" font-weight="700" fill="#0f172a">H</text>';
}
svg.innerHTML = g;
}
function drawMet(){
// Положительные ионы (большие синие круги) + свободные электроны (точки)
let g = '<rect x="0" y="0" width="380" height="280" fill="#fafafa"/>';
const cols = 5, rows = 4, sx = 50, sy = 40, dx = 70, dy = 60;
// электронное «море» — фоновая дымка
g += '<rect x="20" y="20" width="340" height="240" fill="#fde68a" opacity="0.3" rx="8"/>';
// свободные электроны (50 хаотичных точек)
for(let k = 0; k < 70; k++){
const ex = 30 + Math.random() * 320;
const ey = 30 + Math.random() * 220;
g += '<circle cx="'+ex.toFixed(1)+'" cy="'+ey.toFixed(1)+'" r="2.5" fill="#e11d48" opacity="0.85"/>';
g += '<text x="'+(ex+4).toFixed(1)+'" y="'+(ey+2).toFixed(1)+'" font-size="8" fill="#e11d48" font-weight="700"></text>';
}
// положительные ионы
for(let j = 0; j < rows; j++){
for(let i = 0; i < cols; i++){
const cx = sx + i*dx, cy = sy + j*dy;
g += '<circle cx="'+cx+'" cy="'+cy+'" r="15" fill="#f59e0b" stroke="#0f172a" stroke-width="1.4"/>';
g += '<text x="'+cx+'" y="'+(cy+4)+'" text-anchor="middle" font-size="11" font-weight="700" fill="#fff">+</text>';
}
}
svg.innerHTML = g;
}
const INFO = {
ion: '<b style="color:#e11d48">Ионная решётка</b> (пример: NaCl, поваренная соль). Связь: электростатическая между «+» и «−» ионами. Свойства: твёрдые, $t_{\\text{пл}}$ высокая (≈ 800°C), хрупкие, в расплаве проводят ток.',
atom: '<b style="color:#475569">Атомная решётка</b> (пример: алмаз, кремний). Связь: ковалентная между атомами. Свойства: очень твёрдые, тугоплавкие ($t_{\\text{пл}}$ > 1000°C), плохо проводят ток.',
mol: '<b style="color:#0ea5e9">Молекулярная решётка</b> (пример: лёд H$_2$O, нафталин, йод). Связь: слабая (Ван-дер-Ваальса, водородные связи). Свойства: мягкие, легкоплавкие, не проводят ток.',
met: '<b style="color:#f59e0b">Металлическая решётка</b> (пример: Cu, Fe, Al). «+» ионы в «море» свободных электронов. Свойства: пластичные, отлично проводят ток и тепло, имеют металлический блеск.'
};
function render(){
if(mode === 'ion') drawIon();
else if(mode === 'atom') drawAtom();
else if(mode === 'mol') drawMol();
else drawMet();
info.innerHTML = INFO[mode];
renderMath(info);
seen.add(mode);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p7-iv1'); bumpProgress('p7', 15); }
}
tabs.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
mode = b.dataset.mode;
const colMap = { ion:'#e11d48', atom:'#475569', mol:'#0ea5e9', met:'#f59e0b' };
tabs.querySelectorAll('button').forEach(x => { x.className = 'btn'; x.style.background=''; x.style.borderColor=''; });
b.className = 'btn primary';
b.style.background = colMap[mode]; b.style.borderColor = colMap[mode];
render();
});
});
render();
})();
/* IV2 — DnD: кристалл/аморф */
(function(){
const items = [
{ id:'s1', cat:'cryst', html:'Поваренная соль (NaCl)' },
{ id:'s2', cat:'amorph', html:'Стекло' },
{ id:'s3', cat:'cryst', html:'Алмаз' },
{ id:'s4', cat:'amorph', html:'Янтарь' },
{ id:'s5', cat:'cryst', html:'Лёд' },
{ id:'s6', cat:'amorph', html:'Парафин' },
{ id:'s7', cat:'cryst', html:'Железо' },
{ id:'s8', cat:'amorph', html:'Резина' },
];
const sorter = setupSorter({
poolId:'p7-iv2-pool',
scopeSelector:'#p7-iv2',
items: items,
cats:['cryst','amorph'],
columnLayout:false,
});
document.getElementById('p7-iv2-check').addEventListener('click', () => {
const fb = document.getElementById('p7-iv2-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 8 веществ.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 8 верно! +10 XP'); addXp(10,'p7-iv2'); bumpProgress('p7', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 8. Попробуй ещё.');
});
document.getElementById('p7-iv2-reset').addEventListener('click', () => { sorter.reset(); document.getElementById('p7-iv2-fb').style.display = 'none'; });
})();
/* IV3 — квикфайр: тип решётки */
(function(){
// ans: 0=ион, 1=атом, 2=мол, 3=метал
const Q = [
{ q:'NaCl — поваренная соль', ans:0, why:'Чередование Na⁺ и Cl⁻ — ионная решётка.' },
{ q:'Алмаз', ans:1, why:'Атомы углерода связаны ковалентными связями — атомная решётка.' },
{ q:'Лёд (H$_2$O в твёрдом состоянии)', ans:2, why:'Между молекулами H$_2$O слабые водородные связи — молекулярная решётка.' },
{ q:'Медь, железо, алюминий', ans:3, why:'Положительные ионы в «море» свободных электронов — металлическая.' },
{ q:'Кремний (полупроводник)', ans:1, why:'Атомная решётка (как у алмаза), ковалентные связи.' },
{ q:'Йод (I$_2$)', ans:2, why:'Молекулы I$_2$ связаны слабыми силами — молекулярная.' },
];
const LABELS = ['Ионная','Атомная','Молекулярная','Металлическая'];
const COLS = ['#e11d48','#475569','#0ea5e9','#f59e0b'];
let i = 0, score = 0;
const qEl = document.getElementById('p7-iv3-q');
const oEl = document.getElementById('p7-iv3-opts');
const fb = document.getElementById('p7-iv3-fb');
const iEl = document.getElementById('p7-iv3-i');
const sEl = document.getElementById('p7-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p7-iv3'); bumpProgress('p7', 25); }
else if(score >= 4){ addXp(8, 'p7-iv3'); bumpProgress('p7', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
qEl.innerHTML = Q[i].q;
let opts = '';
for(let k = 0; k < 4; k++){
opts += '<button class="btn primary" data-v="'+k+'" style="background:'+COLS[k]+';border-color:'+COLS[k]+'">'+LABELS[k]+'</button>';
}
oEl.innerHTML = opts;
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: '+LABELS[Q[i].ans]+'. '+Q[i].why+' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p7-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр свойств твёрдых тел */
(function(){
const Q = [
{ q:'В монокристалле скорость звука вдоль оси $X$ равна $5000$ м/с, вдоль $Y$ — $3000$ м/с. Найди разность в м/с (это пример анизотропии).', ans:2000, hint:'$5000 - 3000 = 2000$ м/с — разные направления, разные скорости.' },
{ q:'Стекло — это кристаллическое (1) или аморфное (2) тело?', ans:2, hint:'Стекло не имеет упорядоченной решётки — это «переохлаждённая жидкость».' },
{ q:'Алмаз и графит — оба состоят из углерода. У какого больше твёрдость? Введи: алмаз = 1, графит = 2.', ans:1, hint:'У алмаза 3D-решётка ковалентных связей — он твёрже. Графит — слоистый.' },
{ q:'Сколько типов кристаллических решёток рассматривается в §7?', ans:4, hint:'Ионная, атомная, молекулярная, металлическая.' },
{ q:'Какой тип решётки у меди? Введи цифру: 1 = ионная, 2 = атомная, 3 = молекулярная, 4 = металлическая.', ans:4, hint:'Cu — металл, значит решётка металлическая («+» ионы в «море» электронов).' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p7-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p7-iv4'); bumpProgress('p7', 25); }
else if(score >= 3){ addXp(8, 'p7-iv4'); bumpProgress('p7', 15); }
return;
}
document.getElementById('p7-iv4-i').textContent = (i+1);
document.getElementById('p7-iv4-s').textContent = score;
document.getElementById('p7-iv4-q').innerHTML = Q[i].q;
document.getElementById('p7-iv4-ans').value = '';
renderMath(document.getElementById('p7-iv4-q'));
document.getElementById('p7-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p7-iv4-fb');
const raw = document.getElementById('p7-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.5);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p7-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p7-iv4-go').addEventListener('click', go);
document.getElementById('p7-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p7-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p7');
}
function build_p8(){
const box = document.getElementById('p8-body');
let html = '';
/* THEORY 1 — Свойства и строение жидкостей */
html += makeCard('theory', "Свойства и строение жидкостей", "§8", `
<p>Жидкости занимают <b>промежуточное положение</b> между газами и твёрдыми телами:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>От <b>твёрдых тел</b> жидкость унаследовала: <b>сохранение объёма</b> (несжимаемость).</li>
<li>От <b>газов</b>: <b>отсутствие собственной формы</b> — жидкость принимает форму сосуда.</li>
</ul>
<p>Молекулы жидкости расположены тесно (как в твёрдом теле), но могут перемещаться (как в газе).</p>
<p style="margin-top:8px"><b>Ближний порядок</b> — упорядоченность молекул в области порядка нескольких диаметров молекулы. На больших расстояниях упорядоченности нет (в отличие от кристалла).</p>
<p style="margin-top:8px">При охлаждении жидкость может <b>кристаллизоваться</b> (вода → лёд) или, при особых условиях, остаться аморфной (стекло — переохлаждённая жидкость).</p>
`);
/* THEORY 2 — Поверхностное натяжение */
html += makeCard('rule', "Поверхностное натяжение", "§8", `
<p>На поверхности жидкости молекулы испытывают силу, направленную <b>внутрь жидкости</b>. Это объясняется тем, что молекула в объёме окружена молекулами со всех сторон, а молекула на поверхности — только снизу и с боков.</p>
<p style="margin-top:8px">В результате жидкость стремится <b>минимизировать площадь поверхности</b>. Поэтому маленькая капля принимает почти сферическую форму.</p>
<p style="margin-top:10px"><b>Сила поверхностного натяжения</b> на границе пленки длиной $L$:</p>
<p style="text-align:center;margin:10px 0">$$F = \\sigma L$$</p>
<p>где $\\sigma$ — <b>коэффициент поверхностного натяжения</b> (Н/м).</p>
<p style="margin-top:8px">Значения $\\sigma$ при $20°$C:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.7">
<li>вода: $\\sigma_{\\text{в}} \\approx 0{,}073$ Н/м;</li>
<li>спирт: $\\sigma_{\\text{сп}} \\approx 0{,}022$ Н/м;</li>
<li>ртуть: $\\sigma_{\\text{Hg}} \\approx 0{,}465$ Н/м.</li>
</ul>
`);
/* THEORY 3 — Смачивание и капиллярность */
html += makeCard('example', "Смачивание и капиллярность", "§8", `
<p>Когда жидкость соприкасается с твёрдой поверхностью, поверхность жидкости образует <b>краевой угол</b> $\\theta$ с твёрдым телом:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$\\theta < 90°$ — <b>смачивание</b> (вода на чистом стекле: $\\theta \\approx 0°$).</li>
<li>$\\theta > 90°$ — <b>несмачивание</b> (ртуть на стекле: $\\theta \\approx 140°$).</li>
</ul>
<p><b>Капиллярные явления</b>: в тонкой трубке (капилляре) смачивающая жидкость <b>поднимается</b>, несмачивающая — <b>опускается</b> ниже общего уровня.</p>
<p style="margin-top:8px">Высота поднятия в капилляре радиуса $r$:</p>
<p style="text-align:center;margin:10px 0">$$h = \\dfrac{2 \\sigma \\cos\\theta}{\\rho g r}$$</p>
<p>где $\\rho$ — плотность жидкости, $g$ — ускорение свободного падения.</p>
<p style="margin-top:8px"><b>Пример.</b> Вода в капилляре $r = 1$ мм: $h = \\dfrac{2 \\cdot 0{,}073}{1000 \\cdot 10 \\cdot 10^{-3}} \\approx 0{,}015$ м $= 15$ мм.</p>
<p style="margin-top:8px">Капиллярность важна в природе: вода поднимается по корням растений, по тонким сосудам в почве, по фитилю свечи.</p>
`);
/* INTERACTIVE 1 — Симуляция жидкости */
html += `<div class="wg" id="p8-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симуляция жидкости: молекулы плотно упакованы</div></div>
<div class="wg-help">Молекулы расположены тесно и колеблются. Меняй $t$: при росте температуры амплитуда колебаний растёт, при $t \\to 100°$C — молекулы начинают «вырываться» (испарение → §9).</div>
<div class="sliders">
<label>Температура $t$: <b id="p8-iv1-tL">20</b> °C <input type="range" id="p8-iv1-t" min="0" max="100" value="20" step="5"></label>
</div>
<div style="background:#1e293b;border:1px solid var(--border);border-radius:10px;padding:6px;margin-top:8px">
<svg id="p8-iv1-svg" viewBox="0 0 360 240" width="100%" style="max-width:520px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn" id="p8-iv1-pause">Пауза</button>
<button class="btn" id="p8-iv1-reset">Сначала</button>
</div>
<div style="margin-top:8px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.93rem;line-height:1.75" id="p8-iv1-info"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор поверхностного натяжения */
html += `<div class="wg" id="p8-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор поверхностного натяжения</div></div>
<div class="wg-help">Выбери жидкость, введи длину $L$ — получишь силу $F = \\sigma L$. Дополнительно — высота поднятия в капилляре $r = 1$ мм.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Жидкость
<select id="p8-iv2-liq" class="tinp" style="width:100%;margin-top:6px">
<option value="water">Вода (σ = 0,073 Н/м, ρ = 1000)</option>
<option value="alc">Спирт (σ = 0,022 Н/м, ρ = 790)</option>
<option value="hg">Ртуть (σ = 0,465 Н/м, ρ = 13600)</option>
</select>
</label>
<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Длина $L$, м
<input type="number" id="p8-iv2-L" class="tinp" style="width:100%;margin-top:6px" value="0.2" step="any">
</label>
<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Радиус капилляра $r$, мм
<input type="number" id="p8-iv2-r" class="tinp" style="width:100%;margin-top:6px" value="1" step="any">
</label>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p8-iv2-go">Вычислить</button>
</div>
<div id="p8-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p8-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр: смачивает или нет */
html += `<div class="wg" id="p8-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Смачивает или нет?</div></div>
<div class="wg-help">6 ситуаций. Жми соответствующую кнопку.</div>
<div class="score-display"><span>Задача <b id="p8-iv3-i">1</b> / 6</span><span>Очки: <b id="p8-iv3-s">0</b> / 6</span></div>
<div id="p8-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p8-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p8-iv3-fb"></div>
<div class="actions"><button class="btn" id="p8-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр жидкости */
html += `<div class="wg" id="p8-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр жидкости</div></div>
<div class="wg-help">5 задач. Допуск ±5%.</div>
<div class="score-display"><span>Задача <b id="p8-iv4-i">1</b> / 5</span><span>Очки: <b id="p8-iv4-s">0</b> / 5</span></div>
<div id="p8-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p8-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p8-iv4-go">Проверить</button>
<button class="btn" id="p8-iv4-start">Заново</button>
</div>
<div class="feedback" id="p8-iv4-fb"></div>
</div>`;
html += secNav('p7', 'p9');
html += readButton('p8');
box.innerHTML = html;
renderMath(box);
/* IV1 — Симуляция жидкости */
(function(){
const svg = document.getElementById('p8-iv1-svg');
const tInp = document.getElementById('p8-iv1-t');
const tLab = document.getElementById('p8-iv1-tL');
const btnPause = document.getElementById('p8-iv1-pause');
const btnReset = document.getElementById('p8-iv1-reset');
const info = document.getElementById('p8-iv1-info');
const W = 360, H = 240;
const N = 60; // число молекул
let raf = null, lastT = 0, paused = false;
let parts = [];
const tempChanges = new Set();
let _xpDone = false;
function makeParts(){
parts = [];
const cols = 10, rows = 6, sx = 30, sy = 80, dx = 30, dy = 24;
for(let j = 0; j < rows; j++){
for(let i = 0; i < cols; i++){
parts.push({
x0: sx + i*dx + (j%2)*dx/2,
y0: sy + j*dy,
x: sx + i*dx + (j%2)*dx/2,
y: sy + j*dy,
vx: 0, vy: 0,
escaped: false
});
}
}
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
const T = +tInp.value;
// амплитуда колебаний ~ T (px)
const amp = 1 + T * 0.12;
// вероятность испарения ~ при T → 100
const escapeProb = Math.max(0, (T - 75)) / 25 * 0.0005;
for(const p of parts){
if(p.escaped){
// улетает вверх
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy -= 30 * dt; // тянет вверх (отрицательное y)
if(p.y < -10){
// респаун обратно в массу
p.escaped = false;
p.vx = 0; p.vy = 0;
p.x = p.x0; p.y = p.y0;
}
} else {
// случайные толчки + возврат к равновесному положению
p.vx += (Math.random() - 0.5) * amp * 20 * dt;
p.vy += (Math.random() - 0.5) * amp * 20 * dt;
// пружина к (x0, y0)
p.vx += (p.x0 - p.x) * 8 * dt;
p.vy += (p.y0 - p.y) * 8 * dt;
// демпфирование
p.vx *= 0.92;
p.vy *= 0.92;
p.x += p.vx * dt;
p.y += p.vy * dt;
// шанс испариться (для верхнего ряда)
if(p.y0 < 100 && Math.random() < escapeProb){
p.escaped = true;
p.vx = (Math.random() - 0.5) * 40;
p.vy = -60 - Math.random() * 40;
}
}
}
render();
}
function render(){
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#1e293b"/>';
// «поверхность» воды
g += '<line x1="0" y1="70" x2="'+W+'" y2="70" stroke="#0ea5e9" stroke-width="1.2" stroke-dasharray="4 4" opacity="0.5"/>';
g += '<text x="6" y="64" font-size="10" fill="#7dd3fc">поверхность</text>';
// дно
g += '<line x1="0" y1="'+(H-10)+'" x2="'+W+'" y2="'+(H-10)+'" stroke="#94a3b8" stroke-width="1"/>';
for(const p of parts){
const col = p.escaped ? '#fbbf24' : '#60a5fa';
g += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="5" fill="'+col+'" stroke="#0f172a" stroke-width="0.6"/>';
}
svg.innerHTML = g;
}
function updateInfo(T){
let txt;
if(T < 30){
txt = '<b>Низкая $t$.</b> Молекулы колеблются слабо, плотно упакованы. Ближний порядок чётко виден.';
} else if(T < 70){
txt = '<b>Комнатная $t$.</b> Амплитуда колебаний растёт, отдельные молекулы могут менять соседей.';
} else if(T < 95){
txt = '<b>Высокая $t$.</b> Колебания сильные, ближний порядок размывается. Возможно появление пузырьков пара.';
} else {
txt = '<b>$t \\to 100°$C.</b> Молекулы у поверхности «вырываются» наружу — <b>испарение</b> (см. §9).';
}
info.innerHTML = txt;
renderMath(info);
}
makeParts();
raf = requestAnimationFrame(frame);
updateInfo(+tInp.value);
tInp.addEventListener('input', () => {
const T = +tInp.value;
tLab.textContent = T;
updateInfo(T);
tempChanges.add(Math.round(T/20));
if(!_xpDone && tempChanges.size >= 3){
_xpDone = true;
addXp(10, 'p8-iv1');
bumpProgress('p8', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => { makeParts(); });
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — Калькулятор поверхностного натяжения */
(function(){
const liqSel = document.getElementById('p8-iv2-liq');
const lInp = document.getElementById('p8-iv2-L');
const rInp = document.getElementById('p8-iv2-r');
const out = document.getElementById('p8-iv2-out');
const fb = document.getElementById('p8-iv2-fb');
const go = document.getElementById('p8-iv2-go');
const DATA = {
water: { sigma: 0.073, rho: 1000, name: 'Вода' },
alc: { sigma: 0.022, rho: 790, name: 'Спирт' },
hg: { sigma: 0.465, rho: 13600, name: 'Ртуть' },
};
const used = new Set();
let _done = false;
function calc(){
const liq = liqSel.value;
const d = DATA[liq];
const L = parseFloat((lInp.value||'').replace(',','.'));
const r_mm = parseFloat((rInp.value||'').replace(',','.'));
if(!isFinite(L) || L <= 0 || !isFinite(r_mm) || r_mm <= 0){
feedback(fb, false, '&#10007; $L$ и $r$ должны быть положительными.');
return;
}
const F = d.sigma * L;
const r = r_mm / 1000;
const g = 9.8;
// для смачивающей (вода/спирт) θ ≈ 0, cosθ = 1; для ртути — несмачивает, θ > 90 → h отрицательное
let h_m, hLabel;
if(liq === 'hg'){
// ртуть на стекле: θ ≈ 140°, cos ≈ 0.766
h_m = 2 * d.sigma * Math.cos(140 * Math.PI / 180) / (d.rho * g * r);
hLabel = 'опускается на $|h| \\approx '+(Math.abs(h_m)*1000).toFixed(2)+'$ мм (несмачивание)';
} else {
h_m = 2 * d.sigma / (d.rho * g * r);
hLabel = 'поднимается на $h \\approx '+(h_m*1000).toFixed(2)+'$ мм';
}
out.innerHTML =
'<div><b>'+d.name+':</b> $\\sigma = '+d.sigma+'$ Н/м, $\\rho = '+d.rho+'$ кг/м³</div>'
+ '<div style="margin-top:6px"><b>Сила натяжения:</b> $F = \\sigma L = '+d.sigma+' \\cdot '+L+' \\approx '+(+F.toFixed(5))+'$ Н</div>'
+ '<div style="margin-top:6px"><b>В капилляре $r = '+r_mm+'$ мм:</b> жидкость '+hLabel+'</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add(liq);
if(!_done && used.size >= 2){ _done = true; addXp(10, 'p8-iv2'); bumpProgress('p8', 15); }
}
go.addEventListener('click', calc);
})();
/* IV3 — квикфайр: смачивает или нет */
(function(){
const Q = [
{ q:'Вода на чистом стекле', ans:1, why:'Стекло хорошо смачивается водой ($\\theta \\approx 0°$).' },
{ q:'Ртуть на стекле', ans:0, why:'Ртуть не смачивает стекло, $\\theta \\approx 140°$, образует выпуклый мениск.' },
{ q:'Вода на парафине (свечном воске)', ans:0, why:'Парафин гидрофобен — вода не смачивает, скатывается каплями.' },
{ q:'Спирт на стекле', ans:1, why:'Спирт смачивает стекло ещё лучше воды.' },
{ q:'Жидкое мыло на тарелке', ans:1, why:'Мыло — поверхностно-активное вещество, отлично смачивает.' },
{ q:'Капля воды на листе лотоса', ans:0, why:'Лист лотоса имеет микрорельеф и воск — вода скатывается шариками.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p8-iv3-q');
const oEl = document.getElementById('p8-iv3-opts');
const fb = document.getElementById('p8-iv3-fb');
const iEl = document.getElementById('p8-iv3-i');
const sEl = document.getElementById('p8-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p8-iv3'); bumpProgress('p8', 25); }
else if(score >= 4){ addXp(8, 'p8-iv3'); bumpProgress('p8', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
qEl.innerHTML = Q[i].q;
oEl.innerHTML = '<button class="btn primary" data-v="1">Смачивает</button><button class="btn primary" data-v="0" style="background:#ef4444;border-color:#ef4444">Не смачивает</button>';
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. '+Q[i].why+' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p8-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр жидкости */
(function(){
const Q = [
{ q:'$\\sigma = 0{,}073$ Н/м, $L = 0{,}2$ м. Найди силу $F$ в Н (с точностью до 3 знаков).', ans:0.0146, hint:'$F = \\sigma L = 0{,}073 \\cdot 0{,}2 = 0{,}0146$ Н.', tol:0.002 },
{ q:'При одинаковой длине $L = 1$ м что больше: $F$ для воды или для ртути? Введи 1 = вода, 2 = ртуть.', ans:2, hint:'$\\sigma_{\\text{Hg}} = 0{,}465 > \\sigma_{\\text{в}} = 0{,}073$ — больше у ртути.', tol:0.1 },
{ q:'Высота поднятия воды в капилляре $r = 0{,}5$ мм. Введи в мм (целое). $\\sigma = 0{,}073$, $\\rho = 1000$, $g = 10$, $\\cos\\theta = 1$.', ans:29, hint:'$h = \\dfrac{2\\sigma}{\\rho g r} = \\dfrac{2 \\cdot 0{,}073}{1000 \\cdot 10 \\cdot 0{,}0005} \\approx 0{,}029$ м $\\approx 29$ мм.', tol:3 },
{ q:'Жидкость, у которой $\\theta < 90°$ на твёрдой поверхности — смачивает (1) или нет (2)?', ans:1, hint:'По определению, $\\theta < 90°$ — это смачивание.', tol:0.1 },
{ q:'В капилляре жидкость поднимается выше при меньшем (1) или большем (2) радиусе?', ans:1, hint:'$h \\sim 1/r$ — чем тоньше капилляр, тем выше поднятие.', tol:0.1 },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p8-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p8-iv4'); bumpProgress('p8', 25); }
else if(score >= 3){ addXp(8, 'p8-iv4'); bumpProgress('p8', 15); }
return;
}
document.getElementById('p8-iv4-i').textContent = (i+1);
document.getElementById('p8-iv4-s').textContent = score;
document.getElementById('p8-iv4-q').innerHTML = Q[i].q;
document.getElementById('p8-iv4-ans').value = '';
renderMath(document.getElementById('p8-iv4-q'));
document.getElementById('p8-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p8-iv4-fb');
const raw = document.getElementById('p8-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Q[i].tol || Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p8-iv4-s').textContent = score;
i++;
setTimeout(show, 1900);
}
document.getElementById('p8-iv4-go').addEventListener('click', go);
document.getElementById('p8-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p8-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p8');
}
/* ========== §9 ИСПАРЕНИЕ И КОНДЕНСАЦИЯ. НАСЫЩЕННЫЙ ПАР ========== */
/* Табличные значения давления насыщенного пара воды (кПа) при t в °C — используется в §9 и §10. */
const P9_PNTABLE = [
{ t: 0, p: 0.61 },
{ t: 10, p: 1.23 },
{ t: 20, p: 2.34 },
{ t: 30, p: 4.24 },
{ t: 40, p: 7.38 },
{ t: 50, p: 12.3 },
{ t: 60, p: 19.9 },
{ t: 70, p: 31.2 },
{ t: 80, p: 47.4 },
{ t: 90, p: 70.1 },
{ t: 100, p: 101.3 },
];
function pSatWater(t){
if(t <= P9_PNTABLE[0].t) return P9_PNTABLE[0].p;
if(t >= P9_PNTABLE[P9_PNTABLE.length-1].t) return P9_PNTABLE[P9_PNTABLE.length-1].p;
for(let i = 0; i < P9_PNTABLE.length - 1; i++){
const a = P9_PNTABLE[i], b = P9_PNTABLE[i+1];
if(t >= a.t && t <= b.t){
const k = (t - a.t) / (b.t - a.t);
return a.p + k * (b.p - a.p);
}
}
return 0;
}
function dewPointWater(p){
// обратная интерполяция: при каком t выполняется p_н(t) = p?
if(p <= P9_PNTABLE[0].p) return P9_PNTABLE[0].t;
if(p >= P9_PNTABLE[P9_PNTABLE.length-1].p) return P9_PNTABLE[P9_PNTABLE.length-1].t;
for(let i = 0; i < P9_PNTABLE.length - 1; i++){
const a = P9_PNTABLE[i], b = P9_PNTABLE[i+1];
if(p >= a.p && p <= b.p){
const k = (p - a.p) / (b.p - a.p);
return a.t + k * (b.t - a.t);
}
}
return 0;
}
function build_p9(){
const box = document.getElementById('p9-body');
let html = '';
/* THEORY 1 — Испарение и конденсация */
html += makeCard('theory', "Испарение и конденсация", "§9", `
<p><b>Испарение</b> — переход жидкости в пар <b>с её поверхности</b>. Происходит при <b>любой температуре</b> (даже при $0°$C — мокрое бельё высыхает зимой).</p>
<p style="margin-top:8px">Молекулы с наибольшей кинетической энергией вылетают с поверхности, преодолевая силы притяжения остальных. При этом средняя энергия оставшихся молекул уменьшается — <b>жидкость охлаждается</b> при испарении (поэтому мокрое полотенце холодит кожу).</p>
<p style="margin-top:8px"><b>Скорость испарения</b> растёт при:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>повышении температуры $T$ (растёт доля «быстрых» молекул);</li>
<li>увеличении площади свободной поверхности;</li>
<li>удалении паров над поверхностью (например, ветер);</li>
<li>понижении внешнего давления над поверхностью.</li>
</ul>
<p><b>Конденсация</b> — обратный процесс: <b>пар → жидкость</b>. При конденсации выделяется <b>то же количество энергии</b>, которое было затрачено на испарение.</p>
`);
/* THEORY 2 — Насыщенный пар */
html += makeCard('rule', "Насыщенный пар", "§9", `
<p><b>Насыщенный пар</b> — пар, находящийся в <b>динамическом равновесии</b> с жидкостью: число молекул, испаряющихся в единицу времени, равно числу молекул, конденсирующихся обратно.</p>
<p style="margin-top:8px"><b>Главное свойство:</b> давление насыщенного пара $p_н$ <b>зависит только от температуры</b> и не зависит от объёма!</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>При <b>повышении $T$</b>: $p_н$ растёт (примерно экспоненциально).</li>
<li>При <b>сжатии</b> сосуда: часть пара конденсируется, давление остаётся $p = p_н(T)$.</li>
<li>При <b>расширении</b>: жидкость испаряется, давление снова $p = p_н(T)$.</li>
</ul>
<p style="margin-top:8px"><b>Кипение</b> наступает при такой температуре $T_{\\text{кип}}$, когда $p_н(T_{\\text{кип}}) = p_{\\text{атм}}$. Для воды при 1 атм ($\\approx 101{,}3$ кПа) это $T_{\\text{кип}} = 100°$C.</p>
<p style="margin-top:8px;font-size:.92rem;color:var(--muted)">Значения $p_н$ для воды (кПа): при $20°$C — $2{,}34$; при $50°$C — $12{,}3$; при $80°$C — $47{,}4$; при $100°$C — $101{,}3$.</p>
`);
/* THEORY 3 — Виды пара */
html += makeCard('example', "Три вида пара", "§9", `
<p><b>Насыщенный пар.</b> Пар в равновесии с жидкостью. $p = p_н(T)$. Подчиняется законам идеального газа только приближённо.</p>
<p style="margin-top:6px"><b>Ненасыщенный пар.</b> $p < p_н$. «Обычный» пар, без жидкости поблизости. Хорошо описывается законами идеального газа и уравнением Менделеева–Клапейрона.</p>
<p style="margin-top:6px"><b>Перенасыщенный пар.</b> $p > p_н$. Нестабильное состояние — пар спонтанно конденсируется. Возникает при резком охлаждении чистого пара (без центров конденсации).</p>
<p style="margin-top:10px"><b>Примеры в природе и технике:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Туман</b> — водяной пар, охладившийся ниже точки росы → конденсация в капельки.</li>
<li><b>Запотевание окон зимой</b> — конденсация пара из тёплого воздуха на холодном стекле.</li>
<li><b>Облака</b> — конденсат водяного пара в верхних слоях атмосферы.</li>
<li><b>Камера Вильсона</b> — детектор частиц на перенасыщенном паре: пролетающая частица оставляет цепочку капелек.</li>
</ul>
`);
/* INTERACTIVE 1 — Симуляция испарения и конденсации */
html += `<div class="wg" id="p9-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симуляция: испарение и конденсация</div></div>
<div class="wg-help">Нижняя половина — жидкость (синие молекулы), верхняя — пар (голубые). Двигай ползунок $T$ — при росте температуры всё больше молекул переходит в пар.</div>
<div class="sliders">
<label>Температура $T$: <b id="p9-iv1-tL">300</b> К <input type="range" id="p9-iv1-t" min="273" max="400" value="300" step="5"></label>
</div>
<div style="background:#0f172a;border:1px solid var(--border);border-radius:10px;padding:6px;margin-top:8px">
<svg id="p9-iv1-svg" viewBox="0 0 380 260" width="100%" style="max-width:540px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn" id="p9-iv1-pause">Пауза</button>
<button class="btn" id="p9-iv1-reset">Сброс</button>
</div>
<div style="margin-top:8px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.93rem;line-height:1.7" id="p9-iv1-info"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор p_н(T) */
html += `<div class="wg" id="p9-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор давления насыщенного пара воды</div></div>
<div class="wg-help">Введи температуру $t$ от $0$ до $100°$C — получишь $p_н(t)$ по табличным данным (интерполяция между опорными точками).</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<label style="font-size:.9rem;color:var(--muted)">$t$, °C:
<input type="number" id="p9-iv2-t" class="tinp" style="width:120px;margin-left:6px" value="20" min="0" max="100" step="any">
</label>
<button class="btn primary" id="p9-iv2-go">Найти $p_н$</button>
</div>
<div id="p9-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:70px;line-height:1.85"></div>
<div class="feedback" id="p9-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Какой пар? (квикфайр) */
html += `<div class="wg" id="p9-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой это пар?</div></div>
<div class="wg-help">6 ситуаций. Определи: насыщенный, ненасыщенный или перенасыщенный.</div>
<div class="score-display"><span>Задача <b id="p9-iv3-i">1</b> / 6</span><span>Очки: <b id="p9-iv3-s">0</b> / 6</span></div>
<div id="p9-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:56px"></div>
<div id="p9-iv3-opts" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px"></div>
<div class="feedback" id="p9-iv3-fb"></div>
<div class="actions"><button class="btn" id="p9-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр пара */
html += `<div class="wg" id="p9-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр пара</div></div>
<div class="wg-help">5 задач. Числовые ответы, допуск ±5%.</div>
<div class="score-display"><span>Задача <b id="p9-iv4-i">1</b> / 5</span><span>Очки: <b id="p9-iv4-s">0</b> / 5</span></div>
<div id="p9-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p9-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p9-iv4-go">Проверить</button>
<button class="btn" id="p9-iv4-start">Заново</button>
</div>
<div class="feedback" id="p9-iv4-fb"></div>
</div>`;
html += secNav('p8', 'p10');
html += readButton('p9');
box.innerHTML = html;
renderMath(box);
/* IV1 — Симуляция испарения */
(function(){
const svg = document.getElementById('p9-iv1-svg');
const tInp = document.getElementById('p9-iv1-t');
const tLab = document.getElementById('p9-iv1-tL');
const btnPause = document.getElementById('p9-iv1-pause');
const btnReset = document.getElementById('p9-iv1-reset');
const info = document.getElementById('p9-iv1-info');
const W = 380, H = 260;
const SURFACE_Y = 130;
const N_LIQUID = 80; // максимум молекул в жидкости
let parts = []; // {x,y,vx,vy,phase: 'liquid'|'vapor'}
let raf = null, lastT = 0, paused = false;
const tempChanges = new Set();
let _xpDone = false;
function init(){
parts = [];
const cols = 10, rows = 8, sx = 30, sy = SURFACE_Y + 10, dx = 32, dy = 14;
for(let j = 0; j < rows; j++){
for(let i = 0; i < cols; i++){
parts.push({
x0: sx + i*dx + (j%2)*dx/2,
y0: sy + j*dy,
x: sx + i*dx + (j%2)*dx/2,
y: sy + j*dy,
vx: 0, vy: 0,
phase: 'liquid'
});
}
}
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
const T = +tInp.value;
const Tnorm = Math.max(0, (T - 273) / 127); // 0..1
const amp = 1.5 + Tnorm * 5;
// вероятность испарения с поверхности (верхний ряд)
const escapeProb = Math.pow(Tnorm, 1.4) * 0.018;
// вероятность конденсации обратно (если касается поверхности)
const condenseProb = 0.5;
for(const p of parts){
if(p.phase === 'liquid'){
p.vx += (Math.random() - 0.5) * amp * 30 * dt;
p.vy += (Math.random() - 0.5) * amp * 30 * dt;
p.vx += (p.x0 - p.x) * 9 * dt;
p.vy += (p.y0 - p.y) * 9 * dt;
p.vx *= 0.9; p.vy *= 0.9;
p.x += p.vx * dt; p.y += p.vy * dt;
// шанс испариться (только если близко к поверхности)
if(p.y < SURFACE_Y + 25 && Math.random() < escapeProb){
p.phase = 'vapor';
p.vx = (Math.random() - 0.5) * 80;
p.vy = -80 - Math.random() * 60;
}
} else {
// парная фаза — свободное движение
p.x += p.vx * dt;
p.y += p.vy * dt;
// отражение от стен
if(p.x < 8){ p.x = 8; p.vx = -p.vx; }
if(p.x > W-8){ p.x = W-8; p.vx = -p.vx; }
if(p.y < 8){ p.y = 8; p.vy = -p.vy; }
// касание поверхности — шанс сконденсироваться
if(p.y > SURFACE_Y - 2){
if(Math.random() < condenseProb){
p.phase = 'liquid';
p.vx = 0; p.vy = 0;
p.x = p.x0; p.y = p.y0;
} else {
p.y = SURFACE_Y - 2; p.vy = -Math.abs(p.vy);
}
}
}
}
render();
}
function render(){
const T = +tInp.value;
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#0f172a"/>';
// зона пара
g += '<rect x="0" y="0" width="'+W+'" height="'+SURFACE_Y+'" fill="#1e293b"/>';
// зона жидкости
g += '<rect x="0" y="'+SURFACE_Y+'" width="'+W+'" height="'+(H-SURFACE_Y)+'" fill="#0c4a6e"/>';
// поверхность
g += '<line x1="0" y1="'+SURFACE_Y+'" x2="'+W+'" y2="'+SURFACE_Y+'" stroke="#7dd3fc" stroke-width="1.4" stroke-dasharray="4 3" opacity="0.85"/>';
g += '<text x="6" y="'+(SURFACE_Y-6)+'" font-size="10" fill="#94a3b8">пар</text>';
g += '<text x="6" y="'+(SURFACE_Y+14)+'" font-size="10" fill="#bae6fd">жидкость</text>';
let nLiq = 0, nVap = 0;
for(const p of parts){
if(p.phase === 'liquid'){
g += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="4.2" fill="#60a5fa" stroke="#0f172a" stroke-width="0.6"/>';
nLiq++;
} else {
g += '<circle cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" r="3.4" fill="#7dd3fc" stroke="#0f172a" stroke-width="0.5" opacity="0.9"/>';
nVap++;
}
}
// счётчики
g += '<text x="'+(W-8)+'" y="14" font-size="10" fill="#7dd3fc" text-anchor="end">пар: '+nVap+'</text>';
g += '<text x="'+(W-8)+'" y="'+(SURFACE_Y+14)+'" font-size="10" fill="#bae6fd" text-anchor="end">жидкость: '+nLiq+'</text>';
g += '<text x="'+(W/2)+'" y="14" font-size="11" fill="#fbbf24" text-anchor="middle">T = '+T+' К</text>';
svg.innerHTML = g;
}
function updateInfo(T){
let txt;
if(T < 290){
txt = '<b>$T \\approx '+T+'$ К.</b> Низкая температура: испарение медленное, в паре мало молекул, равновесие достигается быстро.';
} else if(T < 330){
txt = '<b>$T \\approx '+T+'$ К (комнатная).</b> Появляется ощутимый поток молекул в пар. $p_н$ невелико, но не нулевое.';
} else if(T < 370){
txt = '<b>$T \\approx '+T+'$ К.</b> Сильное испарение, давление насыщенного пара $p_н$ растёт почти экспоненциально.';
} else {
txt = '<b>$T \\approx '+T+'$ К (около кипения).</b> Пар почти заполняет весь объём, $p_н \\to p_{\\text{атм}}$.';
}
info.innerHTML = txt;
renderMath(info);
}
init();
raf = requestAnimationFrame(frame);
updateInfo(+tInp.value);
tInp.addEventListener('input', () => {
const T = +tInp.value;
tLab.textContent = T;
updateInfo(T);
tempChanges.add(Math.round(T/20));
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true;
addXp(10, 'p9-iv1');
bumpProgress('p9', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => { init(); });
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — Калькулятор p_н(T) */
(function(){
const tInp = document.getElementById('p9-iv2-t');
const out = document.getElementById('p9-iv2-out');
const fb = document.getElementById('p9-iv2-fb');
const go = document.getElementById('p9-iv2-go');
const used = new Set();
let _done = false;
function calc(){
const t = parseFloat((tInp.value||'').replace(',','.'));
if(!isFinite(t) || t < 0 || t > 100){
feedback(fb, false, '&#10007; Введи $t$ от 0 до 100 °C.');
return;
}
const pn = pSatWater(t);
const T = t + 273;
let comment;
if(pn >= 100){
comment = '<b>Кипение!</b> $p_н \\approx p_{\\text{атм}}$ — вода кипит при 1 атм.';
} else if(pn >= 30){
comment = 'При этой $t$ вода в открытом сосуде испаряется очень быстро.';
} else if(pn >= 5){
comment = 'Умеренное испарение; типично для тёплой комнаты или подогретой воды.';
} else {
comment = 'Испарение медленное; типично для прохладного воздуха.';
}
out.innerHTML =
'<div><b>$T$:</b> $t = '+t+'$ °C $= '+T.toFixed(0)+'$ К</div>'
+ '<div style="margin-top:6px"><b>Давление насыщенного пара:</b> $p_н(t) \\approx '+(+pn.toFixed(2))+'$ кПа</div>'
+ '<div style="margin-top:6px;color:var(--muted);font-size:.92rem">'+comment+'</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add(Math.round(t/15));
if(!_done && used.size >= 3){ _done = true; addXp(10, 'p9-iv2'); bumpProgress('p9', 15); }
}
go.addEventListener('click', calc);
tInp.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
})();
/* IV3 — Какой пар? */
(function(){
// 0 = насыщенный, 1 = ненасыщенный, 2 = перенасыщенный
const Q = [
{ q:'Пар в закрытом сосуде над водой в равновесии при $T = 300$ К', ans:0, why:'Динамическое равновесие — это и есть насыщенный пар.' },
{ q:'Воздух комнаты при $\\varphi = 30\\%$, далеко от точки росы', ans:1, why:'$p < p_н$ — ненасыщенный пар.' },
{ q:'Чистый пар охладили в камере Вильсона ниже точки росы (без центров конденсации)', ans:2, why:'Перенасыщенный пар — нестабильное состояние, $p > p_н$.' },
{ q:'Пар над кипящим чайником, который только что закрыли крышкой', ans:0, why:'В замкнутом объёме над водой быстро устанавливается насыщенный пар.' },
{ q:'Утренний туман: пар охладился, образовались капельки', ans:0, why:'В тумане устанавливается насыщенный пар над поверхностью капель.' },
{ q:'Воздух в комнате с $\\varphi = 95\\%$ при $t = 20°$C', ans:1, why:'Пока $\\varphi < 100\\%$, пар остаётся ненасыщенным (хоть и близко к насыщению).' },
];
const LABELS = ['Насыщенный', 'Ненасыщенный', 'Перенасыщенный'];
const COLORS = ['#10b981', '#0ea5e9', '#f59e0b'];
let i = 0, score = 0;
const qEl = document.getElementById('p9-iv3-q');
const oEl = document.getElementById('p9-iv3-opts');
const fb = document.getElementById('p9-iv3-fb');
const iEl = document.getElementById('p9-iv3-i');
const sEl = document.getElementById('p9-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p9-iv3'); bumpProgress('p9', 25); }
else if(score >= 4){ addXp(8, 'p9-iv3'); bumpProgress('p9', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
qEl.innerHTML = Q[i].q;
oEl.innerHTML = LABELS.map((l, k) =>
'<button class="btn primary" data-v="'+k+'" style="background:'+COLORS[k]+';border-color:'+COLORS[k]+'">'+l+'</button>'
).join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Это <b>'+LABELS[Q[i].ans]+'</b>. '+Q[i].why+' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1900);
});
});
}
document.getElementById('p9-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр пара */
(function(){
const Q = [
{ q:'При $t = 100°$C давление насыщенного пара воды равно ... кПа?', ans:101.3, tol:1, hint:'$p_н(100°) = 101{,}3$ кПа — это и есть атмосферное.' },
{ q:'При какой температуре (в °C) начинается кипение воды при атмосферном давлении 1 атм?', ans:100, tol:1, hint:'Кипение наступает при $p_н = p_{\\text{атм}}$ — для 1 атм это $100°$C.' },
{ q:'В закрытом сосуде с водой нагрели содержимое с $20°$C до $50°$C. Во сколько раз вырастет $p_н$? ($p_н(20°) = 2{,}34$ кПа, $p_н(50°) = 12{,}3$ кПа)', ans:5.26, tol:0.5, hint:'$\\dfrac{12{,}3}{2{,}34} \\approx 5{,}26$.' },
{ q:'Зависит ли давление насыщенного пара от объёма сосуда? Введи 1 — да, 2 — нет.', ans:2, tol:0.1, hint:'$p_н$ зависит <b>только</b> от $T$.' },
{ q:'Что забирает больше энергии на 1 г: испарение воды ($r \\approx 2260$ кДж/кг) или плавление льда ($\\lambda \\approx 334$ кДж/кг)? Введи 1 — испарение, 2 — плавление.', ans:1, tol:0.1, hint:'$r \\gg \\lambda$ — испарение «дороже» в 6–7 раз.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p9-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p9-iv4'); bumpProgress('p9', 25); }
else if(score >= 3){ addXp(8, 'p9-iv4'); bumpProgress('p9', 15); }
return;
}
document.getElementById('p9-iv4-i').textContent = (i+1);
document.getElementById('p9-iv4-s').textContent = score;
document.getElementById('p9-iv4-q').innerHTML = Q[i].q;
document.getElementById('p9-iv4-ans').value = '';
renderMath(document.getElementById('p9-iv4-q'));
document.getElementById('p9-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p9-iv4-fb');
const raw = document.getElementById('p9-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Q[i].tol || Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p9-iv4-s').textContent = score;
i++;
setTimeout(show, 1900);
}
document.getElementById('p9-iv4-go').addEventListener('click', go);
document.getElementById('p9-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p9-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p9');
}
/* ========== §10 ВЛАЖНОСТЬ ВОЗДУХА ========== */
function build_p10(){
const box = document.getElementById('p10-body');
let html = '';
/* THEORY 1 — Абсолютная и относительная влажность */
html += makeCard('theory', "Абсолютная и относительная влажность", "§10", `
<p><b>Абсолютная влажность</b> $\\rho_a$ — масса водяного пара в $1$ м³ воздуха. Единица: г/м³.</p>
<p style="margin-top:8px"><b>Относительная влажность</b> $\\varphi$ — характеризует, насколько воздух близок к насыщению:</p>
<p style="text-align:center;margin:10px 0">$$\\varphi = \\dfrac{p}{p_н(T)} \\cdot 100\\% = \\dfrac{\\rho_a}{\\rho_н(T)} \\cdot 100\\%$$</p>
<p>где $p$, $\\rho_a$ — реальные значения, $p_н$, $\\rho_н$ — для насыщенного пара при той же $T$.</p>
<ul style="margin:8px 0 8px 22px;line-height:1.75">
<li>$\\varphi = 100\\%$ — воздух полностью насыщен; начинается конденсация.</li>
<li>$\\varphi < 100\\%$ — пар ненасыщенный, испарение продолжается.</li>
<li>$\\varphi = 0\\%$ — абсолютно сухой воздух.</li>
</ul>
`);
/* THEORY 2 — Точка росы */
html += makeCard('rule', "Точка росы", "§10", `
<p><b>Точка росы</b> $T_{\\text{росы}}$ — температура, до которой нужно охладить воздух при неизменном давлении, чтобы пар стал насыщенным ($\\varphi = 100\\%$).</p>
<p style="margin-top:8px">При охлаждении ниже точки росы начинается <b>конденсация</b>: образуются роса, туман, иней, капли на стекле.</p>
<p style="margin-top:8px">Условие точки росы: $p_н(T_{\\text{росы}}) = p$ — то есть при $T = T_{\\text{росы}}$ табличное давление насыщенного пара равно реальному давлению пара $p$ в воздухе.</p>
<p style="margin-top:8px"><b>Пример.</b> При $t = 25°$C и $\\varphi = 50\\%$:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$p_н(25°) \\approx 3{,}17$ кПа, $p = 0{,}5 \\cdot 3{,}17 \\approx 1{,}58$ кПа;</li>
<li>ищем $t$, при котором $p_н(t) = 1{,}58$ → $T_{\\text{росы}} \\approx 14°$C.</li>
</ul>
<p style="margin-top:8px">Чем выше $\\varphi$, тем ближе точка росы к текущей температуре.</p>
`);
/* THEORY 3 — Психрометр и нормы */
html += makeCard('example', "Психрометр и гигиенические нормы", "§10", `
<p><b>Психрометр</b> — прибор из двух термометров:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Сухой</b> показывает температуру воздуха $t$.</li>
<li><b>Влажный</b> (обёрнут влажной тканью) — показывает $t_{\\text{вл}} < t$ из-за испарения.</li>
</ul>
<p>По разности $\\Delta t = t - t_{\\text{вл}}$ и психрометрической таблице определяют $\\varphi$. Чем больше разность — тем суше воздух.</p>
<p style="margin-top:10px"><b>Гигиенические нормы влажности:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Комфорт:</b> $\\varphi = 40$$60\\%$ — оптимально для дыхания и кожи.</li>
<li><b>Слишком сухо</b> ($\\varphi < 30\\%$): пересыхают слизистые, кожа теряет влагу.</li>
<li><b>Слишком влажно</b> ($\\varphi > 70\\%$): пот плохо испаряется, ощущение духоты.</li>
</ul>
<p style="margin-top:8px">Гигрометры применяются в музеях (защита картин), больницах, серверных и оранжереях.</p>
`);
/* INTERACTIVE 1 — Психрометрический визуализатор */
html += `<div class="wg" id="p10-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Психрометрический визуализатор</div></div>
<div class="wg-help">Двигай $t$ и $p$ — увидишь $\\varphi$ на шкале комфорта и точку росы. Цветовые зоны: красная — сухо, зелёная — комфорт, синяя — влажно.</div>
<div class="sliders" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:8px">
<label>Температура $t$: <b id="p10-iv1-tL">25</b> °C <input type="range" id="p10-iv1-t" min="0" max="40" value="25" step="1"></label>
<label>Давление пара $p$: <b id="p10-iv1-pL">1.6</b> кПа <input type="range" id="p10-iv1-p" min="0.1" max="8" value="1.6" step="0.1"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px;margin-top:8px">
<svg id="p10-iv1-svg" viewBox="0 0 380 280" width="100%" style="max-width:540px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div style="margin-top:8px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75" id="p10-iv1-info"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор влажности */
html += `<div class="wg" id="p10-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор относительной влажности</div></div>
<div class="wg-help">Введи температуру $t$ и реальное давление пара $p$ — получишь $\\varphi$ и точку росы $T_{\\text{росы}}$.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Температура $t$, °C
<input type="number" id="p10-iv2-t" class="tinp" style="width:100%;margin-top:6px" value="20" step="any">
</label>
<label style="display:block;font-size:.9rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Давление пара $p$, кПа
<input type="number" id="p10-iv2-p" class="tinp" style="width:100%;margin-top:6px" value="1.17" step="any">
</label>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p10-iv2-go">Найти $\\varphi$</button>
</div>
<div id="p10-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p10-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Какая влажность? */
html += `<div class="wg" id="p10-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какая влажность: сухо / комфорт / влажно?</div></div>
<div class="wg-help">6 значений $\\varphi$. Определи зону комфорта по гигиеническим нормам.</div>
<div class="score-display"><span>Задача <b id="p10-iv3-i">1</b> / 6</span><span>Очки: <b id="p10-iv3-s">0</b> / 6</span></div>
<div id="p10-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p10-iv3-opts" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px"></div>
<div class="feedback" id="p10-iv3-fb"></div>
<div class="actions"><button class="btn" id="p10-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр влажности */
html += `<div class="wg" id="p10-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр влажности</div></div>
<div class="wg-help">5 задач. Допуск ±5% (для $\\varphi$ ±2%).</div>
<div class="score-display"><span>Задача <b id="p10-iv4-i">1</b> / 5</span><span>Очки: <b id="p10-iv4-s">0</b> / 5</span></div>
<div id="p10-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p10-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p10-iv4-go">Проверить</button>
<button class="btn" id="p10-iv4-start">Заново</button>
</div>
<div class="feedback" id="p10-iv4-fb"></div>
</div>`;
html += secNav('p9', 'final1');
html += readButton('p10');
box.innerHTML = html;
renderMath(box);
/* IV1 — Психрометрический визуализатор */
(function(){
const svg = document.getElementById('p10-iv1-svg');
const tInp = document.getElementById('p10-iv1-t');
const pInp = document.getElementById('p10-iv1-p');
const tLab = document.getElementById('p10-iv1-tL');
const pLab = document.getElementById('p10-iv1-pL');
const info = document.getElementById('p10-iv1-info');
const W = 380, H = 280;
const configs = new Set();
let _done = false;
function render(){
const t = +tInp.value;
let p = +pInp.value;
const pn = pSatWater(t);
// p не может превышать p_н(t) физически — обрежем
if(p > pn) p = pn;
pInp.value = p.toFixed(2);
const phi = (p / pn) * 100;
const dew = dewPointWater(p);
tLab.textContent = t;
pLab.textContent = p.toFixed(2);
// SVG: горизонтальная шкала комфорта 0..100%
const xL = 30, xR = W - 30, yBar = 80, hBar = 28;
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="transparent"/>';
g += '<text x="'+(W/2)+'" y="22" font-size="13" font-weight="700" fill="currentColor" text-anchor="middle">Шкала относительной влажности</text>';
// зоны
const xAt = (v) => xL + (xR - xL) * (v/100);
g += '<rect x="'+xL+'" y="'+yBar+'" width="'+(xAt(30)-xL)+'" height="'+hBar+'" fill="#ef4444" opacity="0.85"/>';
g += '<rect x="'+xAt(30)+'" y="'+yBar+'" width="'+(xAt(70)-xAt(30))+'" height="'+hBar+'" fill="#10b981" opacity="0.85"/>';
g += '<rect x="'+xAt(70)+'" y="'+yBar+'" width="'+(xR-xAt(70))+'" height="'+hBar+'" fill="#0ea5e9" opacity="0.85"/>';
g += '<rect x="'+xL+'" y="'+yBar+'" width="'+(xR-xL)+'" height="'+hBar+'" fill="none" stroke="currentColor" stroke-width="1.2"/>';
// подписи зон
g += '<text x="'+((xL+xAt(30))/2)+'" y="'+(yBar+hBar+14)+'" font-size="11" fill="#dc2626" text-anchor="middle">Сухо</text>';
g += '<text x="'+((xAt(30)+xAt(70))/2)+'" y="'+(yBar+hBar+14)+'" font-size="11" fill="#059669" text-anchor="middle">Комфорт</text>';
g += '<text x="'+((xAt(70)+xR)/2)+'" y="'+(yBar+hBar+14)+'" font-size="11" fill="#0369a1" text-anchor="middle">Влажно</text>';
// деления 0/30/70/100
[0, 30, 70, 100].forEach(v => {
const x = xAt(v);
g += '<line x1="'+x+'" y1="'+(yBar-4)+'" x2="'+x+'" y2="'+yBar+'" stroke="currentColor" stroke-width="1"/>';
g += '<text x="'+x+'" y="'+(yBar-8)+'" font-size="10" fill="currentColor" text-anchor="middle">'+v+'%</text>';
});
// индикатор текущей phi
const xPhi = xAt(Math.min(100, Math.max(0, phi)));
g += '<polygon points="'+xPhi+','+(yBar+hBar+18)+' '+(xPhi-7)+','+(yBar+hBar+30)+' '+(xPhi+7)+','+(yBar+hBar+30)+'" fill="#fbbf24" stroke="#92400e" stroke-width="1"/>';
g += '<text x="'+xPhi+'" y="'+(yBar+hBar+44)+'" font-size="11" font-weight="700" fill="currentColor" text-anchor="middle">φ = '+phi.toFixed(0)+'%</text>';
// нижняя справка
g += '<g transform="translate(0,'+(yBar+hBar+70)+')">';
g += '<text x="'+(W/2)+'" y="0" font-size="12" fill="currentColor" text-anchor="middle">t = '+t+' °C · p = '+p.toFixed(2)+' кПа · pₙ('+t+'°) = '+pn.toFixed(2)+' кПа</text>';
g += '<text x="'+(W/2)+'" y="22" font-size="12" fill="currentColor" text-anchor="middle">Точка росы: T_росы ≈ '+dew.toFixed(1)+' °C</text>';
// вердикт
let verdict, vcol;
if(phi < 30){ verdict = 'Сухо: пересыхают слизистые.'; vcol = '#dc2626'; }
else if(phi < 70){ verdict = 'Комфорт: оптимальная влажность.'; vcol = '#059669'; }
else if(phi < 100){ verdict = 'Влажно: пот плохо испаряется.'; vcol = '#0369a1'; }
else { verdict = 'Насыщение! Возможна конденсация.'; vcol = '#7c3aed'; }
g += '<text x="'+(W/2)+'" y="48" font-size="13" font-weight="700" fill="'+vcol+'" text-anchor="middle">'+verdict+'</text>';
g += '</g>';
svg.innerHTML = g;
info.innerHTML = '<b>$\\varphi = '+phi.toFixed(1)+'\\%$</b>, $p_н('+t+'°) \\approx '+pn.toFixed(2)+'$ кПа, точка росы $T_{\\text{росы}} \\approx '+dew.toFixed(1)+'°$C.';
renderMath(info);
configs.add(Math.round(phi/20)+'_'+Math.round(t/10));
if(!_done && configs.size >= 4){
_done = true;
addXp(10, 'p10-iv1');
bumpProgress('p10', 15);
}
}
tInp.addEventListener('input', render);
pInp.addEventListener('input', render);
render();
})();
/* IV2 — Калькулятор влажности */
(function(){
const tInp = document.getElementById('p10-iv2-t');
const pInp = document.getElementById('p10-iv2-p');
const out = document.getElementById('p10-iv2-out');
const fb = document.getElementById('p10-iv2-fb');
const go = document.getElementById('p10-iv2-go');
const used = new Set();
let _done = false;
function calc(){
const t = parseFloat((tInp.value||'').replace(',','.'));
const p = parseFloat((pInp.value||'').replace(',','.'));
if(!isFinite(t) || t < 0 || t > 100){
feedback(fb, false, '&#10007; Введи $t$ от 0 до 100 °C.');
return;
}
if(!isFinite(p) || p <= 0){
feedback(fb, false, '&#10007; $p$ должно быть положительным.');
return;
}
const pn = pSatWater(t);
if(p > pn + 0.01){
feedback(fb, false, '&#10007; $p > p_н$ — невозможно: $p_н('+t+'°) = '+pn.toFixed(2)+'$ кПа.');
return;
}
const phi = (p / pn) * 100;
const dew = dewPointWater(p);
out.innerHTML =
'<div><b>Дано:</b> $t = '+t+'°$C, $p = '+p+'$ кПа.</div>'
+ '<div style="margin-top:6px"><b>Из таблицы:</b> $p_н('+t+'°) \\approx '+pn.toFixed(2)+'$ кПа.</div>'
+ '<div style="margin-top:6px"><b>Относительная влажность:</b> $\\varphi = \\dfrac{p}{p_н} \\cdot 100\\% = \\dfrac{'+p+'}{'+pn.toFixed(2)+'} \\cdot 100\\% \\approx '+phi.toFixed(1)+'\\%$</div>'
+ '<div style="margin-top:6px"><b>Точка росы:</b> $T_{\\text{росы}} \\approx '+dew.toFixed(1)+'°$C</div>';
renderMath(out);
feedback(fb, true, '&#10003; Готово.');
used.add(Math.round(phi/25));
if(!_done && used.size >= 2){ _done = true; addXp(10, 'p10-iv2'); bumpProgress('p10', 15); }
}
go.addEventListener('click', calc);
[tInp, pInp].forEach(el => el.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }));
})();
/* IV3 — Какая влажность? */
(function(){
// 0 = сухо, 1 = комфорт, 2 = влажно
const Q = [
{ q:'$\\varphi = 15\\%$', ans:0, why:'Меньше 30% — сухо.' },
{ q:'$\\varphi = 50\\%$', ans:1, why:'40–60% — зона комфорта.' },
{ q:'$\\varphi = 85\\%$', ans:2, why:'Больше 70% — влажно.' },
{ q:'$\\varphi = 30\\%$', ans:0, why:'На границе, но всё ещё сухо.' },
{ q:'$\\varphi = 60\\%$', ans:1, why:'Верхняя граница комфорта.' },
{ q:'$\\varphi = 95\\%$', ans:2, why:'Очень близко к точке росы — влажно.' },
];
const LABELS = ['Сухо', 'Комфорт', 'Влажно'];
const COLORS = ['#ef4444', '#10b981', '#0ea5e9'];
let i = 0, score = 0;
const qEl = document.getElementById('p10-iv3-q');
const oEl = document.getElementById('p10-iv3-opts');
const fb = document.getElementById('p10-iv3-fb');
const iEl = document.getElementById('p10-iv3-i');
const sEl = document.getElementById('p10-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p10-iv3'); bumpProgress('p10', 25); }
else if(score >= 4){ addXp(8, 'p10-iv3'); bumpProgress('p10', 15); }
return;
}
iEl.textContent = (i+1); sEl.textContent = score;
qEl.innerHTML = Q[i].q;
oEl.innerHTML = LABELS.map((l, k) =>
'<button class="btn primary" data-v="'+k+'" style="background:'+COLORS[k]+';border-color:'+COLORS[k]+'">'+l+'</button>'
).join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Это <b>'+LABELS[Q[i].ans]+'</b>. '+Q[i].why+' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1800);
});
});
}
document.getElementById('p10-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр влажности */
(function(){
const Q = [
{ q:'$t = 20°$C, $p = 1{,}17$ кПа, $p_н(20°) = 2{,}34$ кПа. Найди $\\varphi$ в %.', ans:50, tol:2, hint:'$\\varphi = 1{,}17/2{,}34 \\cdot 100\\% = 50\\%$.' },
{ q:'$\\varphi = 100\\%$, $t = 20°$C. Чему равно $p$ в кПа? (с точностью до десятых)', ans:2.34, tol:0.15, hint:'$p = p_н(20°) = 2{,}34$ кПа.' },
{ q:'$t = 50°$C, $p = 6{,}15$ кПа, $p_н(50°) = 12{,}3$ кПа. Найди $\\varphi$ в %.', ans:50, tol:2, hint:'$\\varphi = 6{,}15/12{,}3 \\cdot 100\\% = 50\\%$.' },
{ q:'$t = 25°$C, $\\varphi = 50\\%$. Чему равно $p$ в кПа? Используй $p_н(25°) \\approx 3{,}17$. Ответ в десятых.', ans:1.6, tol:0.15, hint:'$p = 0{,}5 \\cdot 3{,}17 \\approx 1{,}58$ кПа.' },
{ q:'Среднее значение комфортной влажности в комнате (в %)?', ans:50, tol:2, hint:'Норма $40$$60\\%$, среднее — $50\\%$.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p10-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p10-iv4'); bumpProgress('p10', 25); }
else if(score >= 3){ addXp(8, 'p10-iv4'); bumpProgress('p10', 15); }
return;
}
document.getElementById('p10-iv4-i').textContent = (i+1);
document.getElementById('p10-iv4-s').textContent = score;
document.getElementById('p10-iv4-q').innerHTML = Q[i].q;
document.getElementById('p10-iv4-ans').value = '';
renderMath(document.getElementById('p10-iv4-q'));
document.getElementById('p10-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p10-iv4-fb');
const raw = document.getElementById('p10-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Q[i].tol || Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p10-iv4-s').textContent = score;
i++;
setTimeout(show, 1900);
}
document.getElementById('p10-iv4-go').addEventListener('click', go);
document.getElementById('p10-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p10-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p10');
}
/* ========== ФИНАЛ ГЛАВЫ 1 — «ОСНОВЫ МКТ» ========== */
function build_final1(){
const box = document.getElementById('final1-body');
let html = '';
/* Часть А — Шпаргалка главы (10 mini-карточек) */
const SHEET = [
{ t:'§ 1 · МКТ', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0ea5e9" stroke-width="2" style="width:18px;height:18px"><circle cx="6" cy="12" r="2"/><circle cx="12" cy="6" r="2"/><circle cx="18" cy="12" r="2"/><circle cx="12" cy="18" r="2"/></svg>', body:'3 положения: 1) тела состоят из частиц; 2) частицы непрерывно движутся; 3) частицы взаимодействуют (притягиваются и отталкиваются).' },
{ t:'§ 2 · Кол-во в-ва', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" style="width:18px;height:18px"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h10"/></svg>', body:'$\\nu = \\dfrac{N}{N_A} = \\dfrac{m}{M}$, $N_A = 6 \\cdot 10^{23}$ моль⁻¹.' },
{ t:'§ 3 · Осн. ур. МКТ', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#8b5cf6" stroke-width="2" style="width:18px;height:18px"><path d="M3 18l4-8 4 8 4-8 4 8"/></svg>', body:'$p = \\dfrac{1}{3} n m_0 \\overline{v^2} = \\dfrac{2}{3} n \\overline{E_k}$.' },
{ t:'§ 4 · Температура', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2" style="width:18px;height:18px"><rect x="9" y="3" width="6" height="14" rx="3"/><circle cx="12" cy="19" r="3"/></svg>', body:'$\\overline{E_k} = \\dfrac{3}{2} k_B T$, $T = t + 273$. $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К.' },
{ t:'§ 5 · Менделеев-Клапейрон', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" style="width:18px;height:18px"><path d="M4 19V5"/><path d="M4 19h16"/><path d="M8 15l3-5 3 3 4-6"/></svg>', body:'$pV = \\nu R T$, $R = 8{,}31$ Дж/(моль·К), $V_M = 22{,}4$ л/моль (н.у.).' },
{ t:'§ 6 · Изопроцессы', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0891b2" stroke-width="2" style="width:18px;height:18px"><path d="M4 12c4-6 12-6 16 0"/><path d="M4 18h16"/></svg>', body:'Бойль: $pV = $ const ($T$=const). Гей-Люссак: $V/T = $ const. Шарль: $p/T = $ const.' },
{ t:'§ 7 · Тв. тела', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#7c3aed" stroke-width="2" style="width:18px;height:18px"><polygon points="12,3 21,8 21,16 12,21 3,16 3,8"/></svg>', body:'Кристалл (моно- и поликристаллы) — анизотропия. 4 типа решёток: ионная, атомная, молекулярная, металлическая. Аморфные тела изотропны.' },
{ t:'§ 8 · Жидкости', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0ea5e9" stroke-width="2" style="width:18px;height:18px"><path d="M12 3c-4 6-6 9-6 12a6 6 0 0 0 12 0c0-3-2-6-6-12z"/></svg>', body:'Ближний порядок. $F = \\sigma L$. Капилляр: $h = \\dfrac{2\\sigma\\cos\\theta}{\\rho g r}$.' },
{ t:'§ 9 · Насыщенный пар', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#06b6d4" stroke-width="2" style="width:18px;height:18px"><path d="M4 14h16"/><path d="M6 10c2-2 4-2 6 0s4 2 6 0"/><path d="M6 18c2-2 4-2 6 0s4 2 6 0"/></svg>', body:'Динамическое равновесие. $p_н$ зависит только от $T$. Кипение: $p_н(T_{\\text{кип}}) = p_{\\text{атм}}$.' },
{ t:'§ 10 · Влажность', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0369a1" stroke-width="2" style="width:18px;height:18px"><path d="M12 3l6 9a6 6 0 1 1 -12 0z"/></svg>', body:'$\\varphi = \\dfrac{p}{p_н(T)} \\cdot 100\\%$. Точка росы $T_{\\text{росы}}$: $p_н(T_{\\text{росы}}) = p$. Комфорт: $40$$60\\%$.' },
];
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 1 — Основы МКТ</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Ключевые формулы и идеи всех 10 параграфов в одном месте — просмотри перед битвой с боссами.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-top:10px">
${SHEET.map(s => `<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
${s.icon}
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.88rem">${s.t}</div>
</div>
<div style="font-size:.92rem;line-height:1.55">${s.body}</div>
</div>`).join('')}
</div>
</div>
</div>`;
/* Часть Б — 7 боссов (intro) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 1</span>
<span class="card-num">7</span>
</div>
<div class="card-body">
<p>7 интегрированных задач — каждая комбинирует ≥ 2 темы. За каждого побеждённого босса: <b>+10 XP, +14% к прогрессу</b>. Победишь всех — ачивка <b>«Мастер МКТ»</b> и <b>+50 XP бонус</b>.</p>
<p style="margin-top:6px;font-size:.92rem;color:var(--muted)">Константы: $R = 8{,}3$ Дж/(моль·К), $k_B = 1{,}38 \\cdot 10^{-23}$ Дж/К, $N_A = 6 \\cdot 10^{23}$ моль⁻¹. Допуск ±5%.</p>
</div>
</div>`;
html += '<div id="ch1-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--sec-acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch1-final-summary">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>
<div id="ch1-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 7 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch1-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#d97706,#fbbf24);transition:width .35s"></div>
</div>
<div id="ch1-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #f59e0b">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#92400e;font-size:1.05rem;margin-bottom:6px">Мастер МКТ</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 1 пройдена! Все 7 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/physics-10-ch2" style="text-decoration:none">Дальше: Глава 2 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p10', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Капля Авогадро',
tag:'§ 2',
q:'Сколько молекул в $36$ г воды ($M = 18$ г/моль)? Введи коэффициент $a$ в записи $N = a \\cdot 10^{23}$.',
ans:12,
hint:'$\\nu = m/M = 36/18 = 2$ моль. $N = \\nu N_A = 2 \\cdot 6 \\cdot 10^{23} = 12 \\cdot 10^{23}$.'
},
{
n:2, color:'#0891b2',
title:'Кинетический Голем',
tag:'§ 3 + § 4',
q:'При $T = 300$ К средняя кинетическая энергия одной молекулы. Введи $b$ в записи $\\overline{E_k} = b \\cdot 10^{-21}$ Дж.',
ans:6.2,
hint:'$\\overline{E_k} = \\dfrac{3}{2} k_B T = 1{,}5 \\cdot 1{,}38 \\cdot 10^{-23} \\cdot 300 = 6{,}21 \\cdot 10^{-21}$ Дж.'
},
{
n:3, color:'#7c3aed',
title:'Дракон Клапейрона',
tag:'§ 5',
q:'В баллоне $\\nu = 2$ моль газа при $T = 300$ К, $V = 50$ л. Найди $p$ в кПа.',
ans:99.6,
hint:'$p = \\dfrac{\\nu R T}{V} = \\dfrac{2 \\cdot 8{,}3 \\cdot 300}{0{,}05} = 99\\,600$ Па $\\approx 99{,}6$ кПа.'
},
{
n:4, color:'#dc2626',
title:'Изохорный Циклоп',
tag:'§ 6',
q:'Газ при $T_1 = 290$ К, $p_1 = 2$ атм нагрели изохорно до $T_2 = 348$ К. Найди $p_2$ в атм.',
ans:2.4,
hint:'Закон Шарля: $p_2 = p_1 \\dfrac{T_2}{T_1} = 2 \\cdot \\dfrac{348}{290} = 2{,}4$ атм.'
},
{
n:5, color:'#0ea5e9',
title:'Капиллярный Спрут',
tag:'§ 8',
q:'$\\sigma = 0{,}073$ Н/м, $L = 0{,}5$ м. Найди силу поверхностного натяжения $F$ в мН.',
ans:36.5,
hint:'$F = \\sigma L = 0{,}073 \\cdot 0{,}5 = 0{,}0365$ Н $= 36{,}5$ мН.'
},
{
n:6, color:'#06b6d4',
title:'Туман-Хранитель',
tag:'§ 9 + § 10',
q:'При $t = 50°$C давление пара $p = 8$ кПа, $p_н(50°) = 12{,}3$ кПа. Найди $\\varphi$ в %.',
ans:65,
hint:'$\\varphi = \\dfrac{p}{p_н} \\cdot 100\\% = \\dfrac{8}{12{,}3} \\cdot 100\\% \\approx 65\\%$.'
},
{
n:7, color:'#f59e0b',
title:'Магистр МКТ',
tag:'§ 3 + § 4 + § 5',
q:'В сосуде $V = 1$ л идеальный газ при $T = 300$ К, $p = 100$ кПа. Найди число молекул $N$. Введи $c$ в записи $N = c \\cdot 10^{22}$.',
ans:2.4,
hint:'$pV = N k_B T$ → $N = \\dfrac{pV}{k_B T} = \\dfrac{10^5 \\cdot 10^{-3}}{1{,}38 \\cdot 10^{-23} \\cdot 300} \\approx 2{,}42 \\cdot 10^{22}$.'
},
];
const cont = document.getElementById('ch1-bosses-container');
const STATE_KEY = 'physics10_ch1_bosses';
const BOSS_STATE = (function(){
try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }catch(e){}
return BOSSES.map(()=>({defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
cont.innerHTML = BOSSES.map((b, idx)=>{
return '<div class="boss-card" id="boss1-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--sec-acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div class="boss-q" id="boss1-'+b.n+'-q" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;line-height:1.5;margin-bottom:10px">'+b.q+'</div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<span style="font-family:\'JetBrains Mono\',monospace;font-size:.92rem">ответ =</span>'
+'<input type="number" id="boss1-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" step="0.01" placeholder="число">'
+'<button class="btn primary" id="boss1-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss1-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss1-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch1-boss-overall');
const fill = document.getElementById('ch1-boss-overall-fill');
if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено';
if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%';
if(won >= BOSSES.length){
const reward = document.getElementById('ch1-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch1_done')){
achievement('ch1_done','Мастер МКТ');
addXp(50, 'ch1-bonus');
bumpProgress('final1', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss1-'+b.n+'-card');
const goBtn = document.getElementById('boss1-'+b.n+'-go');
const hintBtn = document.getElementById('boss1-'+b.n+'-hint');
const ansInp = document.getElementById('boss1-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated){
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
card.classList.add('glow');
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
}
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('boss1-'+b.n+'-fb');
const raw = ansInp.value.replace(',', '.');
const val = parseFloat(raw);
if(isNaN(val)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(b.ans), 0.05);
if(Math.abs(val - b.ans) < tol + 0.001){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch1-'+b.n);
bumpProgress('final1', 14);
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
card.classList.add('glow','pulse');
setTimeout(()=>card.classList.remove('pulse'), 900);
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('boss1-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+b.hint;
fb.style.display = 'block';
fb.style.background = 'var(--warn-bg)';
fb.style.color = '#92400e';
fb.style.borderLeftColor = 'var(--warn)';
renderMath(fb);
});
ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
});
refreshOverall();
}
/* ===== Search ===== */
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)+'…':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(); });
}
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);
/* === PHYS10 POLISH JS === */
(function(){
function bumpScore(el){
if(!el) return;
el.classList.remove('bump');
void el.offsetWidth;
el.classList.add('bump');
setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
}
window.__phys10BumpScore = bumpScore;
function observeScores(root){
root = root || document;
var nodes = root.querySelectorAll('.score-display b');
nodes.forEach(function(b){
if(b.__scoreObs) return;
b.__scoreObs = true;
var last = b.textContent;
try{
var mo = new MutationObserver(function(){
var nv = b.textContent;
if(nv !== last){ last = nv; bumpScore(b); }
});
mo.observe(b, {childList:true, characterData:true, subtree:true});
}catch(e){}
});
}
function rescanScores(){ try{ observeScores(document); }catch(e){} }
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
else rescanScores();
try{
var rootObs = new MutationObserver(function(muts){
var need = false;
for(var i=0;i<muts.length && !need;i++){
var m = muts[i];
for(var j=0;j<m.addedNodes.length;j++){
var n = m.addedNodes[j];
if(n.nodeType===1){
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
}
}
}
if(need) rescanScores();
});
rootObs.observe(document.body, {childList:true, subtree:true});
}catch(e){}
function refreshDoneMarks(){
try{
if(typeof STATE === 'undefined' || !STATE.progress) return;
document.querySelectorAll('.psel-card').forEach(function(c){
var id = c.dataset.id || c.dataset.progCard;
if(!id) return;
var pct = +STATE.progress[id] || 0;
if(!c.querySelector('.psel-done')){
var s = document.createElement('span');
s.className = 'psel-done';
s.setAttribute('title','Прочитано');
s.innerHTML = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
c.appendChild(s);
}
c.classList.toggle('done', pct >= 50);
});
}catch(e){}
}
try{
if(typeof window.refreshProgressUI === 'function'){
var _origRP = window.refreshProgressUI;
window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
}
}catch(e){}
setTimeout(refreshDoneMarks, 600);
setTimeout(refreshDoneMarks, 1800);
window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
document.addEventListener('click', function(e){
var card = e.target.closest && e.target.closest('.psel-card');
if(!card) return;
var id = card.dataset.id;
if(!id) return;
setTimeout(function(){
var sec = document.getElementById('sec-' + id);
if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
}, 60);
});
})();
</script>
</body>
</html>