Files
Learn_System/frontend/textbooks/physics_10_ch1.html
T

2259 lines
154 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)}
.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(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"></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"><div class="xp-card-title"><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 = '';
html += makeCard('theory', "Уравнение состояния идеального газа", "§5", `
<p><b>Уравнение состояния идеального газа</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p4', 'p6');
html += readButton('p5');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p5');
}
function build_p6(){
const box = document.getElementById('p6-body');
let html = '';
html += makeCard('theory', "Изопроцессы", "§6", `
<p><b>Изопроцессы</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p5', 'p7');
html += readButton('p6');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p6');
}
function build_p7(){
const box = document.getElementById('p7-body');
let html = '';
html += makeCard('theory', "Строение и свойства твёрдых тел", "§7", `
<p><b>Строение и свойства твёрдых тел</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p6', 'p8');
html += readButton('p7');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p7');
}
function build_p8(){
const box = document.getElementById('p8-body');
let html = '';
html += makeCard('theory', "Строение и свойства жидкостей", "§8", `
<p><b>Строение и свойства жидкостей</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p7', 'p9');
html += readButton('p8');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p8');
}
function build_p9(){
const box = document.getElementById('p9-body');
let html = '';
html += makeCard('theory', "Испарение и конденсация. Насыщенный пар", "§9", `
<p><b>Испарение и конденсация. Насыщенный пар</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p8', 'p10');
html += readButton('p9');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p9');
}
function build_p10(){
const box = document.getElementById('p10-body');
let html = '';
html += makeCard('theory', "Влажность воздуха", "§10", `
<p><b>Влажность воздуха</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p9', 'final1');
html += readButton('p10');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p10');
}
function build_final1(){
const box = document.getElementById('final1-body');
let html = '';
html += makeCard('theory', "Финал главы 1", "★", `
<p><b>Финал главы 1</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p10', null);
html += readButton('final1');
box.innerHTML = html;
renderMath(box);
wireReadBtn('final1');
}
/* ===== 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>