Files
Learn_System/frontend/textbooks/physics_8_ch2.html
T
Maxim Dolgolyov 053c2ebfdd feat(phys8 ch2): Phase 2 Wave 2 — §15 элементарный заряд + §16 строение атома
§15 Электрический заряд. Элементарный заряд:
- 3 теории: e = 1.6·10⁻¹⁹ Кл, формула q = Ne, закон сохранения
- IV-1: интерактивный калькулятор q ↔ N со slider в логарифм. шкале
  10⁶..10¹⁸ электронов, выводит q в Кл и нКл
- IV-2: 6 раундов «существует ли такой заряд?» (проверка кратности e)
- IV-3: DnD 8 ситуаций «сохраняется / меняется» (заземление, рентген...)
- IV-4: 5 расчётных задач с допусками и подсказками

§16 Строение атома. Ионы:
- 3 теории: планетарная модель, ионы, таблица атомов и ионов
- IV-1: главный визуал — интерактивная модель атома: slider'ы Z и
  число электронов, электроны распределяются по 3 оболочкам (2/8/18),
  ядро с Z протонов, заряд иона рассчитывается автоматически
- IV-2: 6 викторин по таблице ионов
- IV-3: DnD 9 частиц на 3 категории (+/-/нейтр)
- IV-4: 6 MCQ

Глобальная константа E_CHARGE = 1.6e-19 на верхнем уровне.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:18:46 +03:00

1872 lines
152 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Физика 8 · Глава 2 · «Электромагнитные явления»</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/g3d.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/optics.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fffbeb; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#7c3aed; --pri2:#5b21b6; --pri-soft:#ede9fe;
--acc:#a78bfa; --acc2:#7c3aed; --acc-soft:#ede9fe;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --ink:#fef9e7; --muted:#a39070; --border:#2a2512}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#78350f 0%,#d97706 55%,#fcd34d 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft))}
.psel-card.final .psel-num{color:var(--warn)}
.sec[id="sec-p12"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p13"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p14"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p15"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p16"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p17"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p18"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p19"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p20"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p21"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p22"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p23"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p24"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p25"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p26"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p27"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p28"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p29"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p30"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p31"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec[id="sec-final2"]{ --sec-acc:#d97706; --sec-acc-d:#92400e; --sec-acc-soft:#fef3c7; }
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--acc));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.search-modal.show{display:flex}
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
.search-results{flex:1;overflow-y:auto;padding:6px 0}
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
.sec{transition:opacity .25s}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 8 · Глава 2</h1>
<div class="hdr-sub">Электризация · ток · закон Ома · магнитное поле</div>
</div>
<div class="hdr-side">
<a href="/textbook/physics-8" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 8</a>
<button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Электромагнитные явления — от заряда до магнита</h2>
<p>Заряды притягиваются и отталкиваются, образуют электрическое поле. По проводнику течёт ток, и закон Ома связывает $U$, $I$, $R$. У постоянных магнитов и проводников с током есть магнитное поле.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p12')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 12</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p12" class="sec"><div class="sec-header"><span class="sec-num">&sect; 12</span><h2 class="sec-h">Электризация тел. Взаимодействие зарядов</h2></div><div id="p12-body"></div></section>
<section id="sec-p13" class="sec"><div class="sec-header"><span class="sec-num">&sect; 13</span><h2 class="sec-h">Проводники и диэлектрики</h2></div><div id="p13-body"></div></section>
<section id="sec-p14" class="sec"><div class="sec-header"><span class="sec-num">&sect; 14</span><h2 class="sec-h">Электризация через влияние</h2></div><div id="p14-body"></div></section>
<section id="sec-p15" class="sec"><div class="sec-header"><span class="sec-num">&sect; 15</span><h2 class="sec-h">Электрический заряд. Элементарный заряд</h2></div><div id="p15-body"></div></section>
<section id="sec-p16" class="sec"><div class="sec-header"><span class="sec-num">&sect; 16</span><h2 class="sec-h">Строение атома. Ионы</h2></div><div id="p16-body"></div></section>
<section id="sec-p17" class="sec"><div class="sec-header"><span class="sec-num">&sect; 17</span><h2 class="sec-h">Электрическое поле. Электрическое напряжение</h2></div><div id="p17-body"></div></section>
<section id="sec-p18" class="sec"><div class="sec-header"><span class="sec-num">&sect; 18</span><h2 class="sec-h">Единица электрического напряжения. Расчёт работы в электрическом поле</h2></div><div id="p18-body"></div></section>
<section id="sec-p19" class="sec"><div class="sec-header"><span class="sec-num">&sect; 19</span><h2 class="sec-h">Электрический ток. Источники тока</h2></div><div id="p19-body"></div></section>
<section id="sec-p20" class="sec"><div class="sec-header"><span class="sec-num">&sect; 20</span><h2 class="sec-h">Сила и направление электрического тока</h2></div><div id="p20-body"></div></section>
<section id="sec-p21" class="sec"><div class="sec-header"><span class="sec-num">&sect; 21</span><h2 class="sec-h">Электрическая цепь. Измерение силы тока и напряжения</h2></div><div id="p21-body"></div></section>
<section id="sec-p22" class="sec"><div class="sec-header"><span class="sec-num">&sect; 22</span><h2 class="sec-h">Связь силы тока и напряжения. Закон Ома для участка электрической цепи</h2></div><div id="p22-body"></div></section>
<section id="sec-p23" class="sec"><div class="sec-header"><span class="sec-num">&sect; 23</span><h2 class="sec-h">Единица сопротивления. Расчёт сопротивления</h2></div><div id="p23-body"></div></section>
<section id="sec-p24" class="sec"><div class="sec-header"><span class="sec-num">&sect; 24</span><h2 class="sec-h">Последовательное соединение проводников. Реостат</h2></div><div id="p24-body"></div></section>
<section id="sec-p25" class="sec"><div class="sec-header"><span class="sec-num">&sect; 25</span><h2 class="sec-h">Параллельное соединение проводников</h2></div><div id="p25-body"></div></section>
<section id="sec-p26" class="sec"><div class="sec-header"><span class="sec-num">&sect; 26</span><h2 class="sec-h">Работа и мощность электрического тока. Закон Джоуля — Ленца</h2></div><div id="p26-body"></div></section>
<section id="sec-p27" class="sec"><div class="sec-header"><span class="sec-num">&sect; 27</span><h2 class="sec-h">Использование и экономия электроэнергии. Безопасность</h2></div><div id="p27-body"></div></section>
<section id="sec-p28" class="sec"><div class="sec-header"><span class="sec-num">&sect; 28</span><h2 class="sec-h">Постоянные магниты</h2></div><div id="p28-body"></div></section>
<section id="sec-p29" class="sec"><div class="sec-header"><span class="sec-num">&sect; 29</span><h2 class="sec-h">Магнитное поле</h2></div><div id="p29-body"></div></section>
<section id="sec-p30" class="sec"><div class="sec-header"><span class="sec-num">&sect; 30</span><h2 class="sec-h">Магнитное поле тока</h2></div><div id="p30-body"></div></section>
<section id="sec-p31" class="sec"><div class="sec-header"><span class="sec-num">&sect; 31</span><h2 class="sec-h">Магнитное поле прямого проводника и катушки с током. Электромагнит</h2></div><div id="p31-body"></div></section>
<section id="sec-final2" class="sec"><div class="sec-header"><span class="sec-num">&#9733;</span><h2 class="sec-h">Финал главы</h2></div><div id="final2-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Физика 8» · Глава 2 · «Электромагнитные явления» · 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:'p12', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 21;
const _TB_SLUG = 'physics-8-ch2';
const LS_PREFIX = 'physics8_ch2';
const LS_XP = 'physics8_xp';
const PARAS = [
{ id:'p12', num:'\u00a7 12', name:'Электризация тел. Взаимодействие зарядов', sub:'Два рода зарядов' },
{ id:'p13', num:'\u00a7 13', name:'Проводники и диэлектрики', sub:'Свободные носители' },
{ id:'p14', num:'\u00a7 14', name:'Электризация через влияние', sub:'Индукция' },
{ id:'p15', num:'\u00a7 15', name:'Электрический заряд. Элементарный заряд', sub:'$e = 1{,}6 \\cdot 10^{-19}$ Кл' },
{ id:'p16', num:'\u00a7 16', name:'Строение атома. Ионы', sub:'Ядро + электроны' },
{ id:'p17', num:'\u00a7 17', name:'Электрическое поле. Электрическое напряжение', sub:'$U$ как работа поля' },
{ id:'p18', num:'\u00a7 18', name:'Единица электрического напряжения. Расчёт работы в электрическом поле', sub:'$A = qU$' },
{ id:'p19', num:'\u00a7 19', name:'Электрический ток. Источники тока', sub:'Упорядоченное движение' },
{ id:'p20', num:'\u00a7 20', name:'Сила и направление электрического тока', sub:'$I = q/t$' },
{ id:'p21', num:'\u00a7 21', name:'Электрическая цепь. Измерение силы тока и напряжения', sub:'A — последов., V — паралл.' },
{ id:'p22', num:'\u00a7 22', name:'Связь силы тока и напряжения. Закон Ома для участка электрической цепи', sub:'$I = U/R$' },
{ id:'p23', num:'\u00a7 23', name:'Единица сопротивления. Расчёт сопротивления', sub:'$R = \\rho l/S$' },
{ id:'p24', num:'\u00a7 24', name:'Последовательное соединение проводников. Реостат', sub:'$R = R_1 + R_2$' },
{ id:'p25', num:'\u00a7 25', name:'Параллельное соединение проводников', sub:'$1/R = 1/R_1 + 1/R_2$' },
{ id:'p26', num:'\u00a7 26', name:'Работа и мощность электрического тока. Закон Джоуля — Ленца', sub:'$P = UI$, $Q = I^2 Rt$' },
{ id:'p27', num:'\u00a7 27', name:'Использование и экономия электроэнергии. Безопасность', sub:'кВт·ч, ТБ' },
{ id:'p28', num:'\u00a7 28', name:'Постоянные магниты', sub:'N, S, поле Земли' },
{ id:'p29', num:'\u00a7 29', name:'Магнитное поле', sub:'$\\vec{B}$, линии' },
{ id:'p30', num:'\u00a7 30', name:'Магнитное поле тока', sub:'Опыт Эрстеда' },
{ id:'p31', num:'\u00a7 31', name:'Магнитное поле прямого проводника и катушки с током. Электромагнит', sub:'Правило правой руки' },
{ id:'final2', num:'\u2605', name:'Финал главы', sub:'Итоги · 10 боссов', final:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
const ACH_LABELS = {
start:"Начало главы 2!",
p12_done:"Электризация тел. Взаимодействие зарядов освоен!",
p13_done:"Проводники и диэлектрики освоен!",
p14_done:"Электризация через влияние освоен!",
p15_done:"Электрический заряд. Элементарный заряд освоен!",
p16_done:"Строение атома. Ионы освоен!",
p17_done:"Электрическое поле. Электрическое напряжение освоен!",
p18_done:"Единица электрического напряжения. Расчёт работы в электрическом поле освоен!",
p19_done:"Электрический ток. Источники тока освоен!",
p20_done:"Сила и направление электрического тока освоен!",
p21_done:"Электрическая цепь. Измерение силы тока и напряжения освоен!",
p22_done:"Связь силы тока и напряжения. Закон Ома для участка электрической цепи освоен!",
p23_done:"Единица сопротивления. Расчёт сопротивления освоен!",
p24_done:"Последовательное соединение проводников. Реостат освоен!",
p25_done:"Параллельное соединение проводников освоен!",
p26_done:"Работа и мощность электрического тока. Закон Джоуля — Ленца освоен!",
p27_done:"Использование и экономия электроэнергии. Безопасность освоен!",
p28_done:"Постоянные магниты освоен!",
p29_done:"Магнитное поле освоен!",
p30_done:"Магнитное поле тока освоен!",
p31_done:"Магнитное поле прямого проводника и катушки с током. Электромагнит освоен!",
ch2_done:"Глава 2 пройдена!"
};
const SIDEBARS = {
p12:{title:"Шпаргалка § 12",rows:[
["2 рода зарядов","+ (стекло о шёлк), &minus; (эбонит о шерсть)"],
["Одноимённые","отталкиваются"],
["Разноимённые","притягиваются"],
["При трении","заряд один — &plus;, другой — &minus;"],
["Сумма","сохраняется (закон сохр. заряда)"]
]},
p13:{title:"Шпаргалка § 13",rows:[
["Проводники","есть свободные носители"],
["Примеры пров.","металлы, графит, растворы солей, кислот, щелочей"],
["Диэлектрики","эбонит, стекло, дерево, пластик, дист. вода"],
["В проводнике","заряд по поверхности"],
["В диэлектрике","остаётся где появился"]
]},
p14:{title:"Шпаргалка § 14",rows:[
["Индукция","перераспределение зарядов в проводнике"],
["Без касания","нейтральный проводник $\\to$ разделение"],
["Ближний конец","заряд противоположного знака"],
["Если разделить","на 2 заряженные части"]
]},
p15:{title:"Шпаргалка § 15",rows:[
["Элементарный заряд","$e = 1{,}6 \\cdot 10^{-19}$ Кл"],
["Формула","$q = N e$ ($N$ — число избыт. электронов)"],
["Знак $-q$","избыток электронов"],
["Знак $+q$","нехватка электронов"],
["1 Кл","заряд $6{,}25 \\cdot 10^{18}$ электронов"],
["Сохранение","$\\sum q = $ const"]
]},
p16:{title:"Шпаргалка § 16",rows:[
["Атом","ядро (+) + электроны (&minus;)"],
["Ядро","протоны + нейтроны"],
["Нейтр. атом","$N_{электр} = Z$ (атомный номер)"],
["Катион (+)","потерял электрон(ы)"],
["Анион (&minus;)","принял электрон(ы)"],
["Электронные оболочки","K, L, M, …"]
]},
p17:{title:"Шпаргалка § 17",rows:[["В разработке","Phase 2 Wave 3"]]},
p18:{title:"Шпаргалка § 18",rows:[["В разработке","Phase 2 Wave 3"]]},
p19:{title:"Шпаргалка § 19",rows:[["В разработке","Phase 3 Wave 1"]]},
p20:{title:"Шпаргалка § 20",rows:[["В разработке","Phase 3 Wave 1"]]},
p21:{title:"Шпаргалка § 21",rows:[["В разработке","Phase 3 Wave 2"]]},
p22:{title:"Шпаргалка § 22",rows:[["В разработке","Phase 3 Wave 2"]]},
p23:{title:"Шпаргалка § 23",rows:[["В разработке","Phase 3 Wave 3"]]},
p24:{title:"Шпаргалка § 24",rows:[["В разработке","Phase 3 Wave 3"]]},
p25:{title:"Шпаргалка § 25",rows:[["В разработке","Phase 3 Wave 3"]]},
p26:{title:"Шпаргалка § 26",rows:[["В разработке","Phase 3 Wave 4"]]},
p27:{title:"Шпаргалка § 27",rows:[["В разработке","Phase 3 Wave 4"]]},
p28:{title:"Шпаргалка § 28",rows:[["В разработке","Phase 4 Wave 1"]]},
p29:{title:"Шпаргалка § 29",rows:[["В разработке","Phase 4 Wave 1"]]},
p30:{title:"Шпаргалка § 30",rows:[["В разработке","Phase 4 Wave 2"]]},
p31:{title:"Шпаргалка § 31",rows:[["В разработке","Phase 4 Wave 2"]]},
final2:{title:"Шпаргалка ★",rows:[["В разработке","Phase 4 Wave 2"]]}
};
const TIPS=[
{sec:'p12',html:"Потри расчёску о волосы — она начнёт притягивать клочки бумаги. Это <b>электризация</b>. При трении один предмет получает <b>положительный</b> заряд, другой — <b>отрицательный</b>. Одноимённые отталкиваются, разноимённые — притягиваются."},
{sec:'p13',html:"Металлы — отличные проводники: их электроны легко двигаются. Эбонит, стекло, пластик — диэлектрики: электроны связаны атомами. Поэтому ручка отвёртки из пластика — а сама отвёртка металлическая."},
{sec:'p14',html:"Поднеси заряженный шар к незаряженному металлическому шарику — он притянется. В нейтральном проводнике под действием внешнего заряда электроны перераспределяются, и ближний конец получает противоположный знак."},
{sec:'p15',html:"Заряд не бывает «дробным» — все заряды кратны элементарному $e = 1{,}6 \\cdot 10^{-19}$ Кл. Если у тела «лишние» $N$ электронов, его заряд $q = -Ne$. 1 Кл — это очень много: примерно $6 \\cdot 10^{18}$ электронов."},
{sec:'p16',html:"Атом — это плотное положительное ядро (протоны+нейтроны) и облако отрицательных электронов вокруг. В обычном атоме число электронов равно числу протонов — он нейтрален. Если потерять электрон → ион +, если получить → ион &minus;."},
{sec:'p17',html:"Параграф § 17 будет реализован в Phase 2 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p18',html:"Параграф § 18 будет реализован в Phase 2 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p19',html:"Параграф § 19 будет реализован в Phase 3 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p20',html:"Параграф § 20 будет реализован в Phase 3 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p21',html:"Параграф § 21 будет реализован в Phase 3 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p22',html:"Параграф § 22 будет реализован в Phase 3 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p23',html:"Параграф § 23 будет реализован в Phase 3 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p24',html:"Параграф § 24 будет реализован в Phase 3 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p25',html:"Параграф § 25 будет реализован в Phase 3 Wave 3. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p26',html:"Параграф § 26 будет реализован в Phase 3 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p27',html:"Параграф § 27 будет реализован в Phase 3 Wave 4. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p28',html:"Параграф § 28 будет реализован в Phase 4 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p29',html:"Параграф § 29 будет реализован в Phase 4 Wave 1. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p30',html:"Параграф § 30 будет реализован в Phase 4 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'p31',html:"Параграф § 31 будет реализован в Phase 4 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."},
{sec:'final2',html:"Параграф ★ будет реализован в Phase 4 Wave 2. Используем хелперы из <code>phys.js</code> и <code>optics.js</code>."}
];
const BUILDERS = {
p12: ()=>{ build_p12(); },
p13: ()=>{ build_p13(); },
p14: ()=>{ build_p14(); },
p15: ()=>{ build_p15(); },
p16: ()=>{ build_p16(); },
p17: ()=>{ const box=document.getElementById('p17-body'); box.innerHTML = buildStub('p17', 'Электрическое поле. Электрическое напряжение', 'Phase 2 Wave 3') + secNavFor('p17') + readButton('p17'); renderMath(box); wireReadBtn('p17'); },
p18: ()=>{ const box=document.getElementById('p18-body'); box.innerHTML = buildStub('p18', 'Единица электрического напряжения. Расчёт работы в электрическом поле', 'Phase 2 Wave 3') + secNavFor('p18') + readButton('p18'); renderMath(box); wireReadBtn('p18'); },
p19: ()=>{ const box=document.getElementById('p19-body'); box.innerHTML = buildStub('p19', 'Электрический ток. Источники тока', 'Phase 3 Wave 1') + secNavFor('p19') + readButton('p19'); renderMath(box); wireReadBtn('p19'); },
p20: ()=>{ const box=document.getElementById('p20-body'); box.innerHTML = buildStub('p20', 'Сила и направление электрического тока', 'Phase 3 Wave 1') + secNavFor('p20') + readButton('p20'); renderMath(box); wireReadBtn('p20'); },
p21: ()=>{ const box=document.getElementById('p21-body'); box.innerHTML = buildStub('p21', 'Электрическая цепь. Измерение силы тока и напряжения', 'Phase 3 Wave 2') + secNavFor('p21') + readButton('p21'); renderMath(box); wireReadBtn('p21'); },
p22: ()=>{ const box=document.getElementById('p22-body'); box.innerHTML = buildStub('p22', 'Связь силы тока и напряжения. Закон Ома для участка электрической цепи', 'Phase 3 Wave 2') + secNavFor('p22') + readButton('p22'); renderMath(box); wireReadBtn('p22'); },
p23: ()=>{ const box=document.getElementById('p23-body'); box.innerHTML = buildStub('p23', 'Единица сопротивления. Расчёт сопротивления', 'Phase 3 Wave 3') + secNavFor('p23') + readButton('p23'); renderMath(box); wireReadBtn('p23'); },
p24: ()=>{ const box=document.getElementById('p24-body'); box.innerHTML = buildStub('p24', 'Последовательное соединение проводников. Реостат', 'Phase 3 Wave 3') + secNavFor('p24') + readButton('p24'); renderMath(box); wireReadBtn('p24'); },
p25: ()=>{ const box=document.getElementById('p25-body'); box.innerHTML = buildStub('p25', 'Параллельное соединение проводников', 'Phase 3 Wave 3') + secNavFor('p25') + readButton('p25'); renderMath(box); wireReadBtn('p25'); },
p26: ()=>{ const box=document.getElementById('p26-body'); box.innerHTML = buildStub('p26', 'Работа и мощность электрического тока. Закон Джоуля — Ленца', 'Phase 3 Wave 4') + secNavFor('p26') + readButton('p26'); renderMath(box); wireReadBtn('p26'); },
p27: ()=>{ const box=document.getElementById('p27-body'); box.innerHTML = buildStub('p27', 'Использование и экономия электроэнергии. Безопасность', 'Phase 3 Wave 4') + secNavFor('p27') + readButton('p27'); renderMath(box); wireReadBtn('p27'); },
p28: ()=>{ const box=document.getElementById('p28-body'); box.innerHTML = buildStub('p28', 'Постоянные магниты', 'Phase 4 Wave 1') + secNavFor('p28') + readButton('p28'); renderMath(box); wireReadBtn('p28'); },
p29: ()=>{ const box=document.getElementById('p29-body'); box.innerHTML = buildStub('p29', 'Магнитное поле', 'Phase 4 Wave 1') + secNavFor('p29') + readButton('p29'); renderMath(box); wireReadBtn('p29'); },
p30: ()=>{ const box=document.getElementById('p30-body'); box.innerHTML = buildStub('p30', 'Магнитное поле тока', 'Phase 4 Wave 2') + secNavFor('p30') + readButton('p30'); renderMath(box); wireReadBtn('p30'); },
p31: ()=>{ const box=document.getElementById('p31-body'); box.innerHTML = buildStub('p31', 'Магнитное поле прямого проводника и катушки с током. Электромагнит', 'Phase 4 Wave 2') + secNavFor('p31') + readButton('p31'); renderMath(box); wireReadBtn('p31'); },
final2: ()=>{ const box=document.getElementById('final2-body'); box.innerHTML = buildStub('final2', 'Финал главы', 'Phase 4 Wave 2') + secNavFor('final2') + readButton('final2'); renderMath(box); wireReadBtn('final2'); }
};
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
function loadProgress(){
try{
const s=localStorage.getItem(LS_PREFIX+'_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem(LS_PREFIX+'_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem(LS_XP)||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem(LS_PREFIX+'_progress', JSON.stringify(STATE.progress));
localStorage.setItem(LS_PREFIX+'_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem(LS_XP, String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
function loadServerReadState(){
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{
if(!d||!d.progress) return;
(d.progress.read||[]).forEach(k=>{_markedRead.add(k); if((STATE.progress[k]||0)<50) STATE.progress[k]=100;});
saveProgress(); refreshProgressUI();
}).catch(()=>{});
}
function addXp(n,src){
if(!n) return;
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
saveProgress(); refreshProgressUI();
if(window.LS&&window.LS.xp) window.LS.xp.add(n, LS_PREFIX+'-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS[PARAS[0].id];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem(LS_PREFIX+'_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem(LS_PREFIX+'_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
function ipow(base, exp){ let r=1; for(let i=0;i<Math.abs(exp);i++) r*=base; return exp<0 ? 1/r : r; }
function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
/* === SVG-хелперы === */
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
const ux = (W - 2*pad) / (xmax - xmin);
const uy = (H - 2*pad) / (ymax - ymin);
const toX = v => pad + (v - xmin) * ux;
const toY = v => H - pad - (v - ymin) * uy;
let g = '';
g += '<g stroke="#e5e7eb" stroke-width="1">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
g += '<line x1="'+toX(x)+'" y1="'+pad+'" x2="'+toX(x)+'" y2="'+(H-pad)+'"/>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
g += '<line x1="'+pad+'" y1="'+toY(y)+'" x2="'+(W-pad)+'" y2="'+toY(y)+'"/>';
}
g += '</g>';
const y0 = toY(0), x0 = toX(0);
g += '<line x1="'+pad+'" y1="'+y0+'" x2="'+(W-pad)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<line x1="'+x0+'" y1="'+pad+'" x2="'+x0+'" y2="'+(H-pad)+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<text x="'+(W-pad+2)+'" y="'+(y0-4)+'" font-size="11" fill="#0f172a">x</text>';
g += '<text x="'+(x0+4)+'" y="'+(pad-2)+'" font-size="11" fill="#0f172a">y</text>';
g += '<g font-size="10" fill="#64748b">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
if (x !== 0) g += '<text x="'+(toX(x)-3)+'" y="'+(y0+12)+'">'+x+'</text>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
if (y !== 0) g += '<text x="'+(x0+4)+'" y="'+(toY(y)+3)+'">'+y+'</text>';
}
g += '<text x="'+(x0+4)+'" y="'+(y0+12)+'">0</text>';
g += '</g>';
return { content: g, toX, toY, ux, uy };
}
function plotFunc(f, xmin, xmax, toX, toY, color, N){
N = N || 200;
let d = '';
let prevValid = false;
for (let i = 0; i <= N; i++){
const x = xmin + (xmax - xmin) * i / N;
let y;
try { y = f(x); } catch(e){ y = NaN; }
if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
prevValid = true;
}
return '<path d="'+d+'" stroke="'+color+'" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>';
}
function pointWithDrop(x, fx, toX, toY, color, label){
const px = toX(x), py = toY(fx);
let s = '';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+px+'" y2="'+toY(0)+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+toX(0)+'" y2="'+py+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<circle cx="'+px+'" cy="'+py+'" r="4.5" fill="'+color+'" stroke="#fff" stroke-width="2"/>';
if (label){
s += '<text x="'+(px+8)+'" y="'+(py-8)+'" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+color+'">'+label+'</text>';
}
return s;
}
function asymptote(orientation, value, toX, toY, xmin, xmax, ymin, ymax, color){
color = color || '#94a3b8';
if (orientation === 'h'){
const y = toY(value);
return '<line x1="'+toX(xmin)+'" y1="'+y+'" x2="'+toX(xmax)+'" y2="'+y+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
} else {
const x = toX(value);
return '<line x1="'+x+'" y1="'+toY(ymin)+'" x2="'+x+'" y2="'+toY(ymax)+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
}
}
function snapToValue(value, snapPoints, tolerance){
tolerance = tolerance || 0.1;
for (const sp of snapPoints){
if (Math.abs(value - sp) < tolerance) return sp;
}
return value;
}
function rightAngleMark(V, uIn, wIn, s){
s = s || 9;
const p1 = {x: V.x + s*uIn.x, y: V.y + s*uIn.y};
const c = {x: p1.x + s*wIn.x, y: p1.y + s*wIn.y};
const p2 = {x: V.x + s*wIn.x, y: V.y + s*wIn.y};
return p1.x+','+p1.y+' '+c.x+','+c.y+' '+p2.x+','+p2.y;
}
function angleArcAuto(V, uA, uB, R){
const sA = {x: V.x + R*uA.x, y: V.y + R*uA.y};
const eB = {x: V.x + R*uB.x, y: V.y + R*uB.y};
const cross = uA.x*uB.y - uA.y*uB.x;
const sweep = cross > 0 ? 1 : 0;
return 'M'+sA.x+','+sA.y+' A'+R+','+R+' 0 0,'+sweep+' '+eB.x+','+eB.y;
}
function unitVec(p1, p2){
const dx = p2.x - p1.x, dy = p2.y - p1.y;
const len = Math.sqrt(dx*dx + dy*dy) || 1;
return {x: dx/len, y: dy/len};
}
function deg2rad(d){ return d * Math.PI / 180; }
const ICONS = {
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'
};
function secNavFor(curId){
const idx = PARAS.findIndex(p => p.id === curId);
const prev = idx > 0 ? PARAS[idx-1].id : null;
const next = idx < PARAS.length - 1 ? PARAS[idx+1].id : null;
return secNav(prev, next);
}
function secNav(prev, next){
function lbl(id){ if(!id) return ''; const p=PARAS.find(x=>x.id===id); return p?p.num:id; }
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+lbl(prev)+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+lbl(next)+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
const p = PARAS.find(x => x.id === paraId);
const labelTail = p && p.final ? 'финал' : (p ? p.num : '?');
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал \u2014 '+labelTail+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
const aId = paraId+'_done';
if(ACH_LABELS[aId]) achievement(aId);
});
}
function setupSorter(cfg){
const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed = null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
function buildStub(id, name, phase){
return '<div class="card" style="background:linear-gradient(135deg,var(--sec-acc-soft),var(--card));border:1.5px dashed var(--sec-acc)">'
+ '<div class="card-header"><div class="card-icon theory">'+ICONS.theory+'</div><div class="card-title">В разработке</div></div>'
+ '<div class="card-body"><p>Контент <b>'+name+'</b> будет реализован в <b>'+phase+'</b> по плану <code>PLAN_PHYSICS_8.md</code>.</p>'
+ '<p style="margin-top:8px;color:var(--muted);font-size:.9rem">Phase 0 \u2014 это каркас (skeleton). Все 4 интерактива, 3 теоретические карточки и тренажёр задач будут добавлены в волне.</p>'
+ '</div></div>';
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
return arr;
})();
function initSearch(){
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
if(!modal||!inp||!out) return;
let cur=0,rows=[];
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'\u2026':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
function close(){ modal.classList.remove('show'); }
btn&&btn.addEventListener('click',open);
modal.addEventListener('click',e=>{if(e.target===modal)close();});
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
/* ======================================================================
PHASE 2 · WAVE 1 — §12, §13, §14
====================================================================== */
/* Sim-management */
const _SIMS = {};
function _killSim(key){ if(_SIMS[key] && _SIMS[key].raf){ cancelAnimationFrame(_SIMS[key].raf); _SIMS[key].raf=0; } }
function _isVisible(secId){ const el=document.getElementById('sec-'+secId); return el && el.classList.contains('active'); }
/* ======== §12 — Электризация тел ======== */
function build_p12(){
const box = document.getElementById('p12-body');
let h = '';
h += makeCard('theory', 'Электрический заряд', '§ 12.1',
'<p>При трении некоторых тел друг о друга они приобретают свойство притягивать лёгкие предметы — клочки бумаги, волоски. Говорят: тело <b>электризуется</b>, на нём появляется <b>электрический заряд</b>.</p>'
+'<p>Опыты показывают: существует <b>два рода</b> зарядов.</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Положительный (+):</b> возникает на стекле, потёртом о шёлк.</li>'
+'<li><b>Отрицательный (&minus;):</b> возникает на эбоните или пластике, потёртом о шерсть.</li>'
+'</ul>'
);
h += makeCard('rule', 'Закон взаимодействия зарядов', '§ 12.2',
'<p><b>Одноимённые</b> заряды (++ или &minus;&minus;) — <b>отталкиваются</b>.<br>'
+'<b>Разноимённые</b> заряды (+&minus;) — <b>притягиваются</b>.</p>'
+'<p>При электризации трением заряды на двух телах <b>равны по модулю</b> и <b>противоположны</b> по знаку. Это закон <b>сохранения электрического заряда</b>.</p>'
+'<p style="margin-top:6px">Электризация происходит из-за переноса электронов с одного тела на другое: потерявшее электроны становится «+», получившее — «&minus;».</p>'
);
h += makeCard('example', 'Примеры', '§ 12.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Расчёска после волос притягивает мелкие бумажки.</li>'
+'<li>Воздушный шарик после трения о волосы прилипает к стене.</li>'
+'<li>Снятие шерстяного свитера — иногда искрит.</li>'
+'<li>Молния — гигантский электрический разряд между облаком и землёй.</li>'
+'</ul>'
);
/* IV1 — виртуальный электроскоп */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Виртуальный электроскоп</div></div>'
+'<div class="wg-help">Потри палочку о ткань — она зарядится. Поднеси её к электроскопу — листочки разойдутся: одноимённые заряды отталкиваются.</div>'
+'<svg id="p12-sim" viewBox="0 0 460 240" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="actions" style="margin-top:10px"><button class="btn primary" id="p12-rub">Потереть палочку</button><button class="btn" id="p12-touch">Поднести к электроскопу</button><button class="btn" id="p12-reset">Сброс</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Заряд палочки: <b id="p12-q">0</b></span><span>Угол листочков: <b id="p12-a">0&#176;</b></span></div>'
+'</div>';
/* IV2 — викторина «знаки зарядов» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Каков знак заряда?</div></div>'
+'<div class="wg-help">По опыту с трением определи знак заряда тела.</div>'
+'<div id="p12-quiz"></div>'
+'<div class="actions"><button class="btn" id="p12-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p12-quiz-r">1</b> / 5</span><span>Правильно: <b id="p12-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD «притягивает / отталкивает» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Что происходит при контакте?</div></div>'
+'<div class="wg-help">Распредели пары зарядов по типу взаимодействия.</div>'
+'<div id="p12-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Притягиваются</h5><div class="drop-items" data-cat="attr"></div></div>'
+'<div class="drop-box"><h5>Отталкиваются</h5><div class="drop-items" data-cat="rep"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p12-dnd-check">Проверить</button><button class="btn" id="p12-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p12-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP.</div>'
+'<div id="p12-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p12-mcq-i">1</b> / 6</span><span>Правильно: <b id="p12-mcq-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p12') + readButton('p12');
renderMath(box);
wireReadBtn('p12');
_initP12_sim();
_initP12_quiz();
_initP12_dnd();
_initP12_mcq();
}
function _initP12_sim(){
const svg = document.getElementById('p12-sim'); if(!svg) return;
let charged = false; /* палочка заряжена */
let touched = false; /* поднесено к электроскопу */
function draw(){
let s = '';
/* электроскоп: стеклянная колба, металлический стержень, два листочка */
const escCx = 320, escCy = 130;
s += '<rect x="'+(escCx-50)+'" y="'+(escCy-20)+'" width="100" height="120" fill="rgba(186,230,253,.35)" stroke="#0f172a" stroke-width="1.6" rx="4"/>';
/* шар сверху */
s += '<circle cx="'+escCx+'" cy="'+(escCy-40)+'" r="14" fill="#94a3b8" stroke="#0f172a" stroke-width="1.6"/>';
/* стержень */
s += '<line x1="'+escCx+'" y1="'+(escCy-26)+'" x2="'+escCx+'" y2="'+(escCy+50)+'" stroke="#475569" stroke-width="3"/>';
/* листочки */
const leafAng = touched && charged ? 35 : 4;
document.getElementById('p12-a').textContent = leafAng+'&#176;';
const lx1 = escCx, ly1 = escCy+50;
const len = 36;
const lx2L = lx1 - len*Math.sin(leafAng*Math.PI/180);
const ly2L = ly1 + len*Math.cos(leafAng*Math.PI/180);
const lx2R = lx1 + len*Math.sin(leafAng*Math.PI/180);
const ly2R = ly1 + len*Math.cos(leafAng*Math.PI/180);
s += '<line x1="'+lx1+'" y1="'+ly1+'" x2="'+lx2L+'" y2="'+ly2L+'" stroke="#fbbf24" stroke-width="3"/>';
s += '<line x1="'+lx1+'" y1="'+ly1+'" x2="'+lx2R+'" y2="'+ly2R+'" stroke="#fbbf24" stroke-width="3"/>';
/* если заряжен и поднесён — рисуем + + на листочках */
if(touched && charged){
s += '<text x="'+(lx2L-8)+'" y="'+(ly2L+6)+'" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="#dc2626">&minus;</text>';
s += '<text x="'+(lx2R+4)+'" y="'+(ly2R+6)+'" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="#dc2626">&minus;</text>';
s += '<text x="'+escCx+'" y="'+(escCy-40+4)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="#dc2626">&minus;</text>';
}
/* палочка — слева */
const rodX = touched ? 260 : 100;
const rodY = 100;
s += '<rect x="'+rodX+'" y="'+(rodY-10)+'" width="100" height="20" fill="#1e1b4b" stroke="#0f172a" stroke-width="1.5" rx="3"/>';
if(charged){
for(let i=0;i<5;i++) s += '<text x="'+(rodX+10+i*20)+'" y="'+(rodY+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="#fca5a5">&minus;</text>';
}
/* ткань (если ещё не тёрли) */
if(!charged){
s += '<rect x="20" y="160" width="80" height="40" fill="#f87171" stroke="#0f172a" stroke-width="1.5" rx="3"/>';
s += '<text x="60" y="184" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">шерсть</text>';
}
/* подпись */
s += '<text x="150" y="50" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#475569">палочка (эбонит)</text>';
s += '<text x="320" y="220" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#475569">электроскоп</text>';
svg.innerHTML = s;
}
document.getElementById('p12-rub').addEventListener('click', ()=>{ charged = true; document.getElementById('p12-q').textContent = '5 нКл'; draw(); });
document.getElementById('p12-touch').addEventListener('click', ()=>{
if(!charged){
const fb = document.getElementById('p12-q'); fb.textContent = 'сначала потри!'; return;
}
touched = true; draw();
});
document.getElementById('p12-reset').addEventListener('click', ()=>{ charged=false; touched=false; document.getElementById('p12-q').textContent='0'; draw(); });
draw();
}
function _initP12_quiz(){
const QS = [
{sit:'Стеклянная палочка, потёртая о шёлк', ans:'+', why:'По таблице: стекло о шёлк — стекло положительное.'},
{sit:'Эбонитовая палочка о шерсть', ans:'-', why:'Эбонит получает электроны от шерсти — становится отрицательным.'},
{sit:'Шёлк после трения о стекло', ans:'-', why:'Шёлк забирает электроны со стекла — отрицательный.'},
{sit:'Шерсть после трения об эбонит', ans:'+', why:'Шерсть отдала электроны эбониту — положительная.'},
{sit:'Воздушный шарик о волосы', ans:'-', why:'Шарик принимает электроны с волос.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p12-quiz'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">'+q.sit+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<button class="btn" data-pick="+" style="padding:14px;font-size:1.1rem"><b>+</b> положительный</button>'
+'<button class="btn" data-pick="-" style="padding:14px;font-size:1.1rem"><b>&minus;</b> отрицательный</button>'
+'</div>'
+'<div class="feedback" id="p12-quiz-fb"></div>';
document.getElementById('p12-quiz-r').textContent = (i+1);
document.getElementById('p12-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p12-quiz-fb');
if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p12-quiz'); bumpProgress('p12', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p12-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p12-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP12_dnd(){
const items = [
{id:'pp', cat:'rep', html:'$+q$ и $+q$'},
{id:'mm', cat:'rep', html:'$-q$ и $-q$'},
{id:'pm', cat:'attr', html:'$+q$ и $-q$'},
{id:'mp', cat:'attr', html:'$-q$ и $+q$'},
{id:'p0', cat:'attr', html:'$+q$ и нейтральный проводник'},
{id:'m0', cat:'attr', html:'$-q$ и нейтральный проводник'},
{id:'pn', cat:'attr', html:'$+q$ и капля воды (диэл.)'},
{id:'pf', cat:'attr', html:'$+q$ и металл. шарик'}
];
const dnd = setupSorter({ poolId:'p12-dnd-pool', scopeSelector:'#sec-p12', cats:['attr','rep'], items, columnLayout:false });
document.getElementById('p12-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p12-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Заряженный объект <b>всегда</b> притягивает нейтральные (индукция).'; addXp(15,'p12-dnd'); bumpProgress('p12', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: одноимённые отталкиваются; нейтральные всегда притягиваются.'; }
});
document.getElementById('p12-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p12-dnd-fb'); fb.style.display='none'; });
}
function _initP12_mcq(){
const QS = [
{q:'Сколько родов зарядов существует?', opts:['один','два','три','бесконечно много'], ans:1, why:'+ и &minus;, всего два.'},
{q:'При электризации стекла о шёлк стекло становится…', opts:['отрицательным','положительным','не заряжается','любым'], ans:1, why:'Стекло теряет электроны → +.'},
{q:'Что переносится при трении?', opts:['атомы','молекулы','электроны','ядра'], ans:2, why:'Электроны переходят с одного тела на другое.'},
{q:'Два одинаково заряженных шарика…', opts:['притягиваются','отталкиваются','неподвижны','зависит от размера'], ans:1, why:'Одноимённые всегда отталкиваются.'},
{q:'Сумма зарядов при трении двух нейтральных тел…', opts:['растёт','становится 0','остаётся 0','становится +'], ans:2, why:'Заряд сохраняется: было 0, стало $+q$ и $-q$, сумма всё ещё 0.'},
{q:'Почему стенка притягивает шарик после трения о голову?', opts:['стенка тоже зарядилась','индукция в нейтральной стенке','гравитация','клей'], ans:1, why:'В диэлектрике стенки происходит небольшая поляризация — она притягивается к шарику.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p12-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p12-mcq-fb"></div><div class="actions"><button class="btn" id="p12-mcq-next">Следующий</button></div>';
wrap.innerHTML = h;
document.getElementById('p12-mcq-i').textContent = (i+1);
document.getElementById('p12-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p12-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p12-mcq'); bumpProgress('p12', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p12-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p12-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p12-mcq-bonus'); bumpProgress('p12', 15); }, 600); }
});
});
const nb = document.getElementById('p12-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §13 — Проводники и диэлектрики ======== */
function build_p13(){
const box = document.getElementById('p13-body');
let h = '';
h += makeCard('theory', 'Свободные носители заряда', '§ 13.1',
'<p>Все вещества разделяют на два больших класса по способности проводить электрический заряд:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Проводники</b> — есть свободные носители заряда (например, свободные электроны у металлов). Заряд легко перемещается внутри.</li>'
+'<li><b>Диэлектрики</b> (изоляторы) — носители связаны с атомами/молекулами и не могут свободно перемещаться. Заряд «застревает» там, куда попал.</li>'
+'</ul>'
);
h += makeCard('rule', 'Примеры', '§ 13.2',
'<table style="width:100%;border-collapse:collapse;font-size:.92rem"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px;text-align:left">Проводники</th><th style="padding:6px;text-align:left">Диэлектрики</th></tr></thead>'
+'<tbody><tr><td style="padding:6px;border-bottom:1px dashed var(--border)">все металлы</td><td style="padding:6px;border-bottom:1px dashed var(--border)">эбонит, стекло, янтарь</td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">графит</td><td style="padding:6px;border-bottom:1px dashed var(--border)">дерево (сухое), пластик, резина</td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">растворы солей, кислот, щелочей</td><td style="padding:6px;border-bottom:1px dashed var(--border)">фарфор, бумага (сухая)</td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">тело человека (через жидкости)</td><td style="padding:6px;border-bottom:1px dashed var(--border)">дистиллированная вода, воздух (сухой)</td></tr>'
+'<tr><td style="padding:6px">влажная земля</td><td style="padding:6px">шёлк, мех</td></tr></tbody></table>'
);
h += makeCard('example', 'Где это видно', '§ 13.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Провода — медные (проводник), но в изоляции из пластика (диэлектрик).</li>'
+'<li>Розетки и выключатели — пластиковые корпуса.</li>'
+'<li>Заряд на металлическом шарике распределяется по всей поверхности — потому что он проводник.</li>'
+'<li>Заряд на пластиковой расчёске остаётся в месте трения — там, где «потёрли».</li>'
+'</ul>'
);
/* IV1 — Анимация: заряд на проводнике vs диэлектрике */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Куда уходит заряд?</div></div>'
+'<div class="wg-help">Сравни: на металлическом шарике заряд <b>распределяется</b> по всей поверхности. На пластиковом — остаётся в одном месте.</div>'
+'<svg id="p13-sim" viewBox="0 0 460 220" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="actions" style="margin-top:10px"><button class="btn primary" id="p13-charge">Дать заряд в одной точке</button><button class="btn" id="p13-reset">Сброс</button></div>'
+'</div>';
/* IV2 — викторина пров/диэл */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Проводник или диэлектрик?</div></div>'
+'<div class="wg-help">Назови материал — выбери тип.</div>'
+'<div id="p13-quiz"></div>'
+'<div class="actions"><button class="btn" id="p13-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p13-quiz-r">1</b> / 8</span><span>Правильно: <b id="p13-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сортировка материалов</div></div>'
+'<div class="wg-help">Перетащи материалы в нужную колонку.</div>'
+'<div id="p13-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Проводники</h5><div class="drop-items" data-cat="cond"></div></div>'
+'<div class="drop-box"><h5>Диэлектрики</h5><div class="drop-items" data-cat="diel"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p13-dnd-check">Проверить</button><button class="btn" id="p13-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p13-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP.</div>'
+'<div id="p13-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p13-mcq-i">1</b> / 6</span><span>Правильно: <b id="p13-mcq-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p13') + readButton('p13');
renderMath(box);
wireReadBtn('p13');
_initP13_sim();
_initP13_quiz();
_initP13_dnd();
_initP13_mcq();
}
function _initP13_sim(){
_killSim('p13sim');
const svg = document.getElementById('p13-sim'); if(!svg) return;
/* два шара: металлический (слева) и пластиковый (справа). При нажатии «дать заряд» — на метал. заряды разлетаются по поверхности, на пласт. остаются. */
const metCx = 130, metCy = 110, R = 60;
const plCx = 330, plCy = 110;
let charges = []; /* {x, y, target: 'met'|'pla', angTarget, sett: bool, settAng } */
let phase = 0; /* 0 = пусто, 1 = заряд дан */
function reset(){ charges = []; phase = 0; draw(); }
function giveCharge(){
/* добавляем 14 минус-зарядов в одной точке (верх каждого шара) */
if(phase > 0) return;
phase = 1;
for(let i=0;i<14;i++){
const a = Math.PI * 2 * i / 14;
charges.push({ targetA: a, x: metCx, y: metCy - R + 6, kind:'met', settled: false, t: 0 });
charges.push({ targetA: 0, x: plCx, y: plCy - R + 6, kind:'pla', settled: true, t: 0 });
}
}
function tick(){
if(!_isVisible('p13')){ _SIMS.p13sim.raf = requestAnimationFrame(tick); return; }
for(const c of charges){
if(c.kind === 'met' && !c.settled){
/* движение к точке на окружности (под углом targetA) */
c.t += 0.025;
const tx = metCx + (R-8) * Math.cos(c.targetA);
const ty = metCy + (R-8) * Math.sin(c.targetA);
c.x += (tx - c.x) * 0.06;
c.y += (ty - c.y) * 0.06;
if(Math.abs(c.x - tx) < 1 && Math.abs(c.y - ty) < 1) c.settled = true;
}
/* пластиковые сразу осели */
}
let s = '';
/* шары */
s += '<circle cx="'+metCx+'" cy="'+metCy+'" r="'+R+'" fill="#cbd5e1" stroke="#0f172a" stroke-width="2"/>';
s += '<text x="'+metCx+'" y="'+(metCy+R+22)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">МЕТАЛЛ (проводник)</text>';
s += '<circle cx="'+plCx+'" cy="'+plCy+'" r="'+R+'" fill="#fde68a" stroke="#0f172a" stroke-width="2"/>';
s += '<text x="'+plCx+'" y="'+(plCy+R+22)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">ПЛАСТИК (диэлектрик)</text>';
/* заряды */
for(const c of charges){
s += '<text x="'+c.x.toFixed(1)+'" y="'+(c.y+5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="#dc2626">&minus;</text>';
}
/* стрелки касания если phase=0 */
if(phase === 0){
s += window.PHYS.drawArrow(metCx-30, metCy-R-30, metCx, metCy-R+2, '#10b981', 2, 9);
s += window.PHYS.drawArrow(plCx-30, plCy-R-30, plCx, plCy-R+2, '#10b981', 2, 9);
s += '<text x="'+(metCx-60)+'" y="'+(metCy-R-34)+'" font-family="Inter,sans-serif" font-size="11" fill="#0f172a">касание</text>';
s += '<text x="'+(plCx-60)+'" y="'+(plCy-R-34)+'" font-family="Inter,sans-serif" font-size="11" fill="#0f172a">касание</text>';
}
svg.innerHTML = s;
_SIMS.p13sim.raf = requestAnimationFrame(tick);
}
_SIMS.p13sim = { raf: 0 };
_SIMS.p13sim.raf = requestAnimationFrame(tick);
document.getElementById('p13-charge').addEventListener('click', giveCharge);
document.getElementById('p13-reset').addEventListener('click', reset);
}
function _initP13_quiz(){
const QS = [
{mat:'медь', ans:'C', why:'Металл — проводник.'},
{mat:'эбонит', ans:'D', why:'Эбонит — классический диэлектрик.'},
{mat:'графит (карандаш)', ans:'C', why:'Графит — проводник.'},
{mat:'стекло', ans:'D', why:'Стекло не проводит ток.'},
{mat:'раствор поваренной соли', ans:'C', why:'В растворе есть свободные ионы — проводит.'},
{mat:'дистиллированная вода', ans:'D', why:'Чистая вода — диэлектрик (нет ионов).'},
{mat:'железо', ans:'C', why:'Металл.'},
{mat:'фарфор', ans:'D', why:'Фарфор — изолятор, поэтому из него делают изоляторы на линиях электропередачи.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p13-quiz'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5;font-weight:700;font-size:1rem">'+q.mat+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<button class="btn" data-pick="C" style="padding:14px"><b>Проводник</b></button>'
+'<button class="btn" data-pick="D" style="padding:14px"><b>Диэлектрик</b></button>'
+'</div>'
+'<div class="feedback" id="p13-quiz-fb"></div>';
document.getElementById('p13-quiz-r').textContent = (i+1);
document.getElementById('p13-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p13-quiz-fb');
if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p13-quiz'); bumpProgress('p13', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p13-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p13-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP13_dnd(){
const items = [
{id:'cu', cat:'cond', html:'медь'},
{id:'al', cat:'cond', html:'алюминий'},
{id:'gr', cat:'cond', html:'графит'},
{id:'sl', cat:'cond', html:'солёная вода'},
{id:'eb', cat:'diel', html:'эбонит'},
{id:'gl', cat:'diel', html:'стекло'},
{id:'pl', cat:'diel', html:'пластик'},
{id:'dw', cat:'diel', html:'дистил. вода'}
];
const dnd = setupSorter({ poolId:'p13-dnd-pool', scopeSelector:'#sec-p13', cats:['cond','diel'], items, columnLayout:false });
document.getElementById('p13-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p13-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Металлы и растворы — проводники, остальные — диэлектрики.'; addXp(15,'p13-dnd'); bumpProgress('p13', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Дистиллированная вода — диэлектрик (нет ионов).'; }
});
document.getElementById('p13-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p13-dnd-fb'); fb.style.display='none'; });
}
function _initP13_mcq(){
const QS = [
{q:'Чем отличаются проводники от диэлектриков?', opts:['массой','цветом','свободными носителями зарядов','температурой'], ans:2, why:'У проводников есть свободные заряды, у диэлектриков — нет.'},
{q:'Какие частицы свободны в металлах?', opts:['атомы','протоны','электроны','нейтроны'], ans:2, why:'Свободные электроны переносят заряд в металлах.'},
{q:'Заряд на металлическом шарике распределяется…', opts:['в центре','в одной точке','по всей поверхности','внутри'], ans:2, why:'Свободные заряды отталкиваются и оседают по поверхности.'},
{q:'Заряд на пластиковой расчёске…', opts:['распределяется','остаётся в месте трения','исчезает','уходит в землю'], ans:1, why:'Диэлектрик не позволяет заряду перемещаться.'},
{q:'Что используют для изоляции проводов?', opts:['медь','алюминий','пластик','графит'], ans:2, why:'Пластик — хороший диэлектрик.'},
{q:'Можно ли «зарядить» проводник, держа в руке голыми пальцами?', opts:['да','нет, заряд уйдёт через тело','зависит от металла','только летом'], ans:1, why:'Тело — проводник (через жидкости), заряд уходит в землю.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p13-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p13-mcq-fb"></div><div class="actions"><button class="btn" id="p13-mcq-next">Следующий</button></div>';
wrap.innerHTML = h;
document.getElementById('p13-mcq-i').textContent = (i+1);
document.getElementById('p13-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p13-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p13-mcq'); bumpProgress('p13', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p13-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p13-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p13-mcq-bonus'); bumpProgress('p13', 15); }, 600); }
});
});
const nb = document.getElementById('p13-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §14 — Электризация через влияние (индукция) ======== */
function build_p14(){
const box = document.getElementById('p14-body');
let h = '';
h += makeCard('theory', 'Что такое индукция', '§ 14.1',
'<p>Если поднести заряженное тело к нейтральному <b>проводнику</b>, не касаясь его, в проводнике произойдёт <b>перераспределение</b> свободных электронов.</p>'
+'<p><b>Электризация через влияние</b> (или <b>индукция</b>) — это разделение зарядов в проводнике под действием внешнего заряда без непосредственного контакта.</p>'
+'<p>Ближний к источнику конец проводника получает заряд <b>противоположного</b> знака, дальний — того же знака.</p>'
);
h += makeCard('rule', 'Как это работает', '§ 14.2',
'<p>Подносим $+q$ к незаряженному металлическому шарику:</p>'
+'<ol style="padding-left:20px;margin:6px 0">'
+'<li>Свободные электроны (отрицательные) притягиваются к $+q$ — собираются на <b>ближнем</b> конце.</li>'
+'<li>На <b>дальнем</b> конце остаются положительные ионы — некомпенсированный $+q$.</li>'
+'<li>В целом шарик остался нейтральным, но <b>заряды разделились</b>.</li>'
+'<li>Если теперь шарик разделить на 2 половинки — каждая будет заряжена!</li>'
+'</ol>'
);
h += makeCard('example', 'Примеры явления', '§ 14.3',
'<ul style="padding-left:20px;margin:6px 0">'
+'<li>Заряженный шарик притягивает любой кусочек металла — даже нейтральный.</li>'
+'<li>Расчёска (заряженная) притягивает бумажки (диэлектрик слегка поляризуется).</li>'
+'<li>В стенке (диэлектрик) поляризация молекул — шарик прилипает.</li>'
+'<li>Молниеотвод собирает индукцией заряд из грозовой тучи и отводит в землю.</li>'
+'</ul>'
);
/* IV1 — Симуляция: палочка + металлический шарик */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Поднеси заряд — увидь индукцию</div></div>'
+'<div class="wg-help">Двигай заряженную палочку (slider) — наблюдай, как электроны в металлическом шарике перераспределяются.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Положение палочки: <b id="p14-xv">слева</b><input type="range" id="p14-x" min="40" max="160" step="5" value="60"></label>'
+'<label>Знак: <select id="p14-sig" class="tinp" style="width:auto;padding:6px 10px;font-size:.92rem"><option value="-1">отрицательная (&minus;)</option><option value="1">положительная (+)</option></select></label>'
+'</div>'
+'<svg id="p14-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Ближний конец шара: <b id="p14-near">+</b> (противоположный палочке)</span>'
+'<span>Дальний конец шара: <b id="p14-far">&minus;</b> (того же знака)</span>'
+'</div>'
+'</div>';
/* IV2 — викторина: «что будет с шаром если…» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Что произойдёт?</div></div>'
+'<div class="wg-help">Дана ситуация — определи итог.</div>'
+'<div id="p14-quiz"></div>'
+'<div class="actions"><button class="btn" id="p14-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p14-quiz-r">1</b> / 5</span><span>Правильно: <b id="p14-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD: «в проводнике / в диэлектрике» */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Где идёт индукция, а где — поляризация?</div></div>'
+'<div class="wg-help">В проводниках — <b>индукция</b> (электроны бегут); в диэлектриках — <b>поляризация</b> (молекулы поворачиваются).</div>'
+'<div id="p14-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Индукция (в проводнике)</h5><div class="drop-items" data-cat="ind"></div></div>'
+'<div class="drop-box"><h5>Поляризация (в диэлектрике)</h5><div class="drop-items" data-cat="pol"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p14-dnd-check">Проверить</button><button class="btn" id="p14-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p14-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP.</div>'
+'<div id="p14-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p14-mcq-i">1</b> / 6</span><span>Правильно: <b id="p14-mcq-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p14') + readButton('p14');
renderMath(box);
wireReadBtn('p14');
_initP14_sim();
_initP14_quiz();
_initP14_dnd();
_initP14_mcq();
}
function _initP14_sim(){
const svg = document.getElementById('p14-sim'); if(!svg) return;
function draw(){
const sx = +document.getElementById('p14-x').value;
const sig = +document.getElementById('p14-sig').value;
document.getElementById('p14-xv').textContent = sx < 80 ? 'далеко' : (sx < 130 ? 'близко' : 'почти касается');
/* у шарика: nearLabel = противоп. знаку палочки */
const nearSym = sig > 0 ? '&minus;' : '+';
const farSym = sig > 0 ? '+' : '&minus;';
document.getElementById('p14-near').innerHTML = nearSym;
document.getElementById('p14-far').innerHTML = farSym;
/* draw */
let s = '';
/* палочка слева */
s += '<rect x="'+(sx-50)+'" y="80" width="50" height="20" fill="#1e1b4b" stroke="#0f172a" stroke-width="1.5" rx="3"/>';
const rodCol = sig > 0 ? '#dc2626' : '#2563eb';
const rodTxt = sig > 0 ? '+' : '&minus;';
for(let i=0;i<4;i++) s += '<text x="'+(sx-42+i*12)+'" y="95" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="'+rodCol+'">'+rodTxt+'</text>';
/* металлический шарик в центре */
const shCx = 260, shCy = 90, shR = 56;
s += '<circle cx="'+shCx+'" cy="'+shCy+'" r="'+shR+'" fill="#cbd5e1" stroke="#0f172a" stroke-width="2"/>';
/* концентрация электронов на ближнем конце (если sig=+, электроны к палочке) или ионов */
const intensity = Math.min(1, (180 - sx) / 100); /* чем ближе палочка, тем сильнее эффект */
/* near = слева от шара (ближе к палочке), far = справа */
/* ставим знаки. Если палочка -, на ближнем + (т.е. на левой стороне +) */
const nearCol = sig > 0 ? '#2563eb' : '#dc2626';
const farCol = sig > 0 ? '#dc2626' : '#2563eb';
const nearTxt = sig > 0 ? '&minus;' : '+';
const farTxt = sig > 0 ? '+' : '&minus;';
for(let i=0;i<6;i++){
const ang = -Math.PI/2 + (i-2.5)*0.18;
const r = shR - 14;
/* near (слева) */
const nx = shCx + r*Math.cos(Math.PI + ang*0.4);
const ny = shCy + r*Math.sin(Math.PI + ang*0.4) - i*0;
s += '<text x="'+nx.toFixed(1)+'" y="'+(ny+5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="'+nearCol+'" opacity="'+intensity.toFixed(2)+'">'+nearTxt+'</text>';
/* far (справа) */
const fx = shCx + r*Math.cos(ang*0.4);
const fy = shCy + r*Math.sin(ang*0.4);
s += '<text x="'+fx.toFixed(1)+'" y="'+(fy+5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="'+farCol+'" opacity="'+intensity.toFixed(2)+'">'+farTxt+'</text>';
}
s += '<text x="'+shCx+'" y="'+(shCy+shR+18)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">МЕТАЛЛ. ШАР (нейтральный)</text>';
/* подпись индукции */
if(intensity > 0.3){
s += '<text x="230" y="40" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#7c3aed">индукция!</text>';
}
svg.innerHTML = s;
}
document.getElementById('p14-x').addEventListener('input', draw);
document.getElementById('p14-sig').addEventListener('change', draw);
draw();
}
function _initP14_quiz(){
const QS = [
{sit:'$+q$ поднесли к нейтральному металлическому шару, не касаясь.', opts:['шар стал +','шар стал ','шар стал нейтральным с разделёнными зарядами','шар не изменился'], ans:2, why:'Произошла индукция: внутри шара разделились заряды, но в целом он остался нейтральным.'},
{sit:'$-q$ поднесли к шару и коснулись его.', opts:['заряды шара не изменились','шар стал отрицательным','шар стал положительным','шар стал нейтральным'], ans:1, why:'Электроны с палочки перешли на шар.'},
{sit:'$+q$ долго держали возле шара, а потом убрали (не касаясь).', opts:['шар остался заряженным','шар нейтральный','шар стал отрицательным','шар стал положительным'], ans:1, why:'Без касания заряд просто перераспределялся; при удалении источника всё возвращается, шар нейтрален.'},
{sit:'$+q$ поднесли к двум прижатым друг к другу шарам. Их разъединили (не убирая $+q$), затем убрали $+q$.', opts:['оба нейтральны','оба +','оба ','один +, другой '], ans:3, why:'При разделении в присутствии $+q$ заряды зафиксировались: дальний шар стал +, ближний −.'},
{sit:'$-q$ поднесли к листку бумаги (диэлектрик).', opts:['ничего','бумага зарядилась +','в бумаге поляризация, она притянулась','бумага оттолкнулась'], ans:2, why:'В диэлектрике молекулы поляризуются, листок притягивается.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p14-quiz'); if(!wrap) return;
let html = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5">'+q.sit+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ html += '<button class="btn" data-k="'+k+'" style="padding:10px 14px;text-align:left">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
html += '</div><div class="feedback" id="p14-quiz-fb"></div>';
wrap.innerHTML = html;
document.getElementById('p14-quiz-r').textContent = (i+1);
document.getElementById('p14-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p14-quiz-fb');
if(k===q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p14-quiz'); bumpProgress('p14', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p14-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p14-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP14_dnd(){
const items = [
{id:'ms', cat:'ind', html:'$+q$ возле металлического шара'},
{id:'mf', cat:'ind', html:'заряженный шар возле фольги'},
{id:'mp', cat:'ind', html:'заряженный шар возле железной пластины'},
{id:'mw', cat:'ind', html:'$+q$ возле провода'},
{id:'pl', cat:'pol', html:'$-q$ возле клочка бумаги'},
{id:'pg', cat:'pol', html:'$+q$ возле стеклянной палочки'},
{id:'pw', cat:'pol', html:'$-q$ возле деревянной планки'},
{id:'pp', cat:'pol', html:'$+q$ возле пластиковой пластины'}
];
const dnd = setupSorter({ poolId:'p14-dnd-pool', scopeSelector:'#sec-p14', cats:['ind','pol'], items, columnLayout:false });
document.getElementById('p14-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p14-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. В проводниках индукция, в диэлектриках поляризация — оба эффекта приводят к притяжению.'; addXp(15,'p14-dnd'); bumpProgress('p14', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Металлы и фольга — проводники, остальное — диэлектрики.'; }
});
document.getElementById('p14-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p14-dnd-fb'); fb.style.display='none'; });
}
function _initP14_mcq(){
const QS = [
{q:'Что такое электризация через влияние?', opts:['заряжение трением','перенос заряда касанием','разделение зарядов в проводнике без касания','испускание электронов'], ans:2, why:'Это разделение зарядов внутри проводника под действием внешнего заряда.'},
{q:'Ближний к источнику конец проводника получает заряд…', opts:['того же знака','противоположного','становится нейтральным','положительный'], ans:1, why:'Притягиваются заряды противоположного знака.'},
{q:'После убирания внешнего заряда (без касания) проводник…', opts:['остаётся заряженным','становится нейтральным','меняет знак','раскалывается'], ans:1, why:'Перераспределение исчезает, и проводник снова нейтрален.'},
{q:'Если в присутствии $+q$ разъединить шар на 2 части, что станет с частями после убирания $+q$?', opts:['обе +','обе ','одна +, другая ','обе нейтральны'], ans:2, why:'Разделение зафиксировалось: ближняя часть −, дальняя +.'},
{q:'Почему листочки бумаги притягиваются к расчёске?', opts:['индукция','поляризация молекул в диэлектрике','магнетизм','гравитация'], ans:1, why:'В диэлектрике молекулы поляризуются, и листок притягивается.'},
{q:'Молниеотвод использует…', opts:['индукцию','теплопередачу','излучение','реакцию горения'], ans:0, why:'Индукция собирает заряд из тучи на острие и отводит его в землю.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p14-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p14-mcq-fb"></div><div class="actions"><button class="btn" id="p14-mcq-next">Следующий</button></div>';
wrap.innerHTML = h;
document.getElementById('p14-mcq-i').textContent = (i+1);
document.getElementById('p14-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p14-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p14-mcq'); bumpProgress('p14', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p14-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p14-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p14-mcq-bonus'); bumpProgress('p14', 15); }, 600); }
});
});
const nb = document.getElementById('p14-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======================================================================
PHASE 2 · WAVE 2 — §15, §16
====================================================================== */
const E_CHARGE = 1.6e-19;
/* ======== §15 — Электрический заряд. Элементарный заряд ======== */
function build_p15(){
const box = document.getElementById('p15-body');
let h = '';
h += makeCard('theory', 'Элементарный заряд', '§ 15.1',
'<p>Эксперимент Милликена с масляными каплями показал: <b>заряд тела всегда кратен</b> наименьшей порции — <b>элементарному заряду</b>:</p>'
+'<p style="text-align:center;margin:8px 0">$$e = 1{,}6 \\cdot 10^{-19} \\text{ Кл}$$</p>'
+'<p>Это заряд одного электрона (со знаком «&minus;») и одного протона (со знаком «+»). Меньше — не бывает (это <b>квантуется</b>).</p>'
);
h += makeCard('rule', 'Формула $q = N e$', '§ 15.2',
'<p>Если у тела «лишних» $N$ электронов, его заряд:</p>'
+'<p style="text-align:center;margin:8px 0">$$q = N \\cdot e$$</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li>$q < 0$ — избыток электронов;</li>'
+'<li>$q > 0$ — нехватка электронов;</li>'
+'<li>$q = 0$ — нейтральное тело (число +зарядов = число &minus;зарядов).</li>'
+'</ul>'
+'<p>Сколько электронов в 1 Кл? $N = 1/e = 6{,}25 \\cdot 10^{18}$ — это огромное число.</p>'
);
h += makeCard('example', 'Закон сохранения заряда', '§ 15.3',
'<p>В замкнутой системе сумма всех зарядов <b>не меняется</b>:</p>'
+'<p style="text-align:center;margin:8px 0">$$\\sum q_i = \\text{const}$$</p>'
+'<p>Электризация трением — не «появление» заряда, а его <b>перенос</b>: было два нейтральных тела (всего 0), стало $+q$ и $-q$ (сумма всё ещё 0).</p>'
);
/* IV1 — калькулятор q ↔ N */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Сколько электронов?</div></div>'
+'<div class="wg-help">Преобразуй между числом электронов $N$ и зарядом $q$ в нанокулонах.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Число электронов $N$: <b id="p15-nv">10<sup>9</sup></b><input type="range" id="p15-n" min="6" max="18" step="0.5" value="9"></label>'
+'</div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$N$ = <b id="p15-nval">1 000 000 000</b></span>'
+'<span>$|q| = N e$ = <b id="p15-qval">1.6&times;10<sup>-10</sup></b> Кл = <b id="p15-qnc">0.16</b> нКл</span>'
+'<span style="font-size:.84rem;color:var(--muted)">Это очень малый заряд — почти неощутим.</span>'
+'</div>'
+'</div>';
/* IV2 — викторина квантования */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Может ли существовать такой заряд?</div></div>'
+'<div class="wg-help">Проверь, кратен ли указанный заряд $e = 1{,}6 \\cdot 10^{-19}$ Кл.</div>'
+'<div id="p15-quiz"></div>'
+'<div class="actions"><button class="btn" id="p15-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p15-quiz-r">1</b> / 6</span><span>Правильно: <b id="p15-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD сохранение заряда */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сохраняется ли заряд?</div></div>'
+'<div class="wg-help">Распредели ситуации: где общий заряд изменился, а где остался прежним.</div>'
+'<div id="p15-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Сохраняется</h5><div class="drop-items" data-cat="keep"></div></div>'
+'<div class="drop-box"><h5>Меняется (внеш. вмешат.)</h5><div class="drop-items" data-cat="ext"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p15-dnd-check">Проверить</button><button class="btn" id="p15-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p15-dnd-fb"></div>'
+'</div>';
/* IV4 — числовые задачи */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 5 расчётных задач</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP. $e = 1{,}6 \\cdot 10^{-19}$ Кл.</div>'
+'<div id="p15-task"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p15-task-i">1</b> / 5</span><span>Правильно: <b id="p15-task-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p15') + readButton('p15');
renderMath(box);
wireReadBtn('p15');
_initP15_calc();
_initP15_quiz();
_initP15_dnd();
_initP15_tasks();
}
function _initP15_calc(){
function update(){
const exp = +document.getElementById('p15-n').value;
const N = Math.pow(10, exp);
document.getElementById('p15-nv').innerHTML = '10<sup>'+exp.toFixed(1)+'</sup>';
document.getElementById('p15-nval').textContent = N.toExponential(2).replace('+','');
const q = N * E_CHARGE;
const qExp = Math.floor(Math.log10(q));
const qMant = (q/Math.pow(10,qExp)).toFixed(2);
document.getElementById('p15-qval').innerHTML = qMant+'&times;10<sup>'+qExp+'</sup>';
document.getElementById('p15-qnc').textContent = (q*1e9).toExponential(2).replace('+','');
}
document.getElementById('p15-n').addEventListener('input', update);
update();
}
function _initP15_quiz(){
const QS = [
{q:'$q = 3{,}2 \\cdot 10^{-19}$ Кл', ans:'Y', why:'$N = q/e = 2$ — кратно, существует.'},
{q:'$q = 8 \\cdot 10^{-19}$ Кл', ans:'Y', why:'$N = 5$ — целое, существует.'},
{q:'$q = 2{,}4 \\cdot 10^{-19}$ Кл', ans:'N', why:'$N = 1{,}5$ — не целое, не существует.'},
{q:'$q = 1 \\cdot 10^{-19}$ Кл', ans:'N', why:'$N = 0{,}625$ — не целое.'},
{q:'$q = 1{,}6 \\cdot 10^{-18}$ Кл', ans:'Y', why:'$N = 10$ — целое, существует.'},
{q:'$q = 5 \\cdot 10^{-19}$ Кл', ans:'N', why:'$N = 3{,}125$ — не целое.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p15-quiz'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin:8px 0;line-height:1.5;font-size:1.05rem">'+q.q+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<button class="btn" data-pick="Y" style="padding:14px"><b>Существует</b></button>'
+'<button class="btn" data-pick="N" style="padding:14px"><b>Не существует</b></button>'
+'</div>'
+'<div class="feedback" id="p15-quiz-fb"></div>';
document.getElementById('p15-quiz-r').textContent = (i+1);
document.getElementById('p15-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-pick]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true);
const fb = document.getElementById('p15-quiz-fb');
if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p15-quiz'); bumpProgress('p15', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p15-quiz-ok').textContent = ok;
renderMath(wrap);
});
});
renderMath(wrap);
}
document.getElementById('p15-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP15_dnd(){
const items = [
{id:'a', cat:'keep', html:'трение двух тел в изолированной системе'},
{id:'b', cat:'keep', html:'переход электронов внутри проводника'},
{id:'c', cat:'keep', html:'индукция в шарике'},
{id:'d', cat:'keep', html:'два шара соприкоснулись и разъединились'},
{id:'e', cat:'ext', html:'тело заземлили (заряд ушёл в землю)'},
{id:'f', cat:'ext', html:'тело потёрли о внешний предмет, а потом убрали'},
{id:'g', cat:'ext', html:'к телу поднесли «насос электронов»'},
{id:'h', cat:'ext', html:'тело облучили рентгеном (выбил электроны)'}
];
const dnd = setupSorter({ poolId:'p15-dnd-pool', scopeSelector:'#sec-p15', cats:['keep','ext'], items, columnLayout:false });
document.getElementById('p15-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p15-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Заряд сохраняется в изолированной системе.'; addXp(15,'p15-dnd'); bumpProgress('p15', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Заземление и внешнее излучение — это внешнее вмешательство.'; }
});
document.getElementById('p15-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p15-dnd-fb'); fb.style.display='none'; });
}
function _initP15_tasks(){
const TASKS = [
{q:'Сколько электронов нужно собрать, чтобы получить заряд $q = -1$ нКл? ($e = 1{,}6 \\cdot 10^{-19}$ Кл) — Ответ в формате $N \\cdot 10^9$, введи $N$ с одним знаком после запятой.', ans:6.25, tol:0.1, why:'$N = q/e = 10^{-9}/(1{,}6\\cdot10^{-19}) = 6{,}25 \\cdot 10^9$. Ответ: $6{,}25$.'},
{q:'У тела «лишних» $5 \\cdot 10^{10}$ электронов. Каков его заряд (в нКл, по модулю)?', ans:8, tol:0.2, why:'$q = Ne = 5\\cdot10^{10} \\cdot 1{,}6\\cdot10^{-19} = 8\\cdot10^{-9}$ Кл = $8$ нКл.'},
{q:'У одного тела $q_1 = +3$ нКл, у другого $q_2 = -5$ нКл. После их соприкосновения и разделения сумма $q_1 + q_2$ равна (в нКл)?', ans:-2, tol:0.05, why:'Заряд сохраняется: $3 + (-5) = -2$ нКл (так и осталось после контакта).'},
{q:'Какой заряд (в Кл, по модулю) получится при $N = 2 \\cdot 10^{19}$? Ответ в формате $a \\cdot 10^0$, введи $a$ (одно число).', ans:3.2, tol:0.05, why:'$q = 2\\cdot10^{19} \\cdot 1{,}6\\cdot10^{-19} = 3{,}2$ Кл — это очень много!'},
{q:'Заряд $q = 4{,}8 \\cdot 10^{-19}$ Кл существует? Если да — сколько в нём электронов? Введи $N$.', ans:3, tol:0.1, why:'$N = q/e = 4{,}8/1{,}6 = 3$. Заряд существует, в нём 3 элементарных.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const t = TASKS[i]; const wrap = document.getElementById('p15-task'); if(!wrap) return;
wrap.innerHTML =
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+'<div class="boss-row"><input type="number" step="0.01" class="tinp" id="p15-task-inp" placeholder="число" style="width:140px">'
+'<button class="btn primary" id="p15-task-go">Ответ</button>'
+'<button class="btn" id="p15-task-hint">Подсказка</button>'
+'<button class="btn" id="p15-task-next">Следующая</button></div>'
+'<div class="boss-hint-txt" id="p15-task-hint-txt">'+t.why+'</div>'
+'<div class="feedback" id="p15-task-fb"></div>';
document.getElementById('p15-task-i').textContent = (i+1);
document.getElementById('p15-task-ok').textContent = ok;
document.getElementById('p15-task-go').addEventListener('click', ()=>{
const v = parseFloat((document.getElementById('p15-task-inp').value || '').replace(',','.'));
const fb = document.getElementById('p15-task-fb');
if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Введи число.'; return; }
done++;
if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно! '+t.why; addXp(4,'p15-task'); bumpProgress('p15', 6); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. Правильный ответ: '+t.ans+'. '+t.why; }
document.getElementById('p15-task-ok').textContent = ok;
renderMath(wrap);
if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p15-task-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p15-task-bonus'); bumpProgress('p15', 15); }, 600); }
});
document.getElementById('p15-task-hint').addEventListener('click', ()=>{ document.getElementById('p15-task-hint-txt').classList.toggle('show'); });
document.getElementById('p15-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); });
renderMath(wrap);
}
render();
}
/* ======== §16 — Строение атома. Ионы ======== */
function build_p16(){
const box = document.getElementById('p16-body');
let h = '';
h += makeCard('theory', 'Планетарная модель атома', '§ 16.1',
'<p>Атом устроен как маленькая «солнечная система»:</p>'
+'<ul style="padding-left:20px;margin:6px 0">'
+'<li><b>Ядро</b> в центре — очень маленькое (10<sup>-15</sup> м), но в нём почти вся масса. Состоит из <b>протонов</b> ($+e$) и <b>нейтронов</b> (нейтральные).</li>'
+'<li><b>Электроны</b> ($-e$) движутся вокруг ядра по оболочкам.</li>'
+'<li>Атом в целом размером $\\sim 10^{-10}$ м — ядро в 100 000 раз меньше всего атома.</li>'
+'</ul>'
+'<p>В <b>нейтральном</b> атоме число электронов = число протонов = <b>атомный номер</b> $Z$.</p>'
);
h += makeCard('rule', 'Ионы', '§ 16.2',
'<p>Если атом <b>потерял</b> 1 или больше электронов — он становится <b>положительным ионом (катионом)</b>: $\\text{Na} \\to \\text{Na}^+$.</p>'
+'<p>Если атом <b>принял</b> лишний электрон — он становится <b>отрицательным ионом (анионом)</b>: $\\text{Cl} \\to \\text{Cl}^-$.</p>'
+'<p>Соли в растворах диссоциируют на ионы — поэтому растворы солей проводят ток.</p>'
);
h += makeCard('example', 'Атомы и ионы', '§ 16.3',
'<table style="width:100%;border-collapse:collapse;font-size:.92rem"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px">Элемент</th><th style="padding:6px;text-align:center">$Z$</th><th style="padding:6px;text-align:center">Электр.</th><th style="padding:6px;text-align:center">Заряд иона</th></tr></thead>'
+'<tbody><tr><td style="padding:6px;border-bottom:1px dashed var(--border)">водород H</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">1</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">1</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">H<sup>+</sup></td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">натрий Na</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">11</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">11</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">Na<sup>+</sup></td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">хлор Cl</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">17</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">17</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">Cl<sup>&minus;</sup></td></tr>'
+'<tr><td style="padding:6px;border-bottom:1px dashed var(--border)">кальций Ca</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">20</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">20</td><td style="padding:6px;border-bottom:1px dashed var(--border);text-align:center">Ca<sup>2+</sup></td></tr>'
+'<tr><td style="padding:6px">кислород O</td><td style="padding:6px;text-align:center">8</td><td style="padding:6px;text-align:center">8</td><td style="padding:6px;text-align:center">O<sup>2&minus;</sup></td></tr></tbody></table>'
);
/* IV1 — конструктор атома */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-1</span><div class="wg-title">Конструктор атома / иона</div></div>'
+'<div class="wg-help">Меняй число протонов и электронов — увидь, нейтрален ли атом и какой ион получится.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Протонов ($Z$): <b id="p16-zv">11</b><input type="range" id="p16-z" min="1" max="20" step="1" value="11"></label>'
+'<label>Электронов: <b id="p16-ev">11</b><input type="range" id="p16-e" min="0" max="20" step="1" value="11"></label>'
+'</div>'
+'<svg id="p16-sim" viewBox="0 0 460 240" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Заряд: <b id="p16-qbal">0</b> (нейтрален)</span>'
+'<span>Состояние: <b id="p16-state">нейтральный атом</b></span>'
+'</div>'
+'</div>';
/* IV2 — викторина по таблице */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-2</span><div class="wg-title">Какой заряд у иона?</div></div>'
+'<div class="wg-help">Дан элемент и преобразование — назови заряд получившегося иона.</div>'
+'<div id="p16-quiz"></div>'
+'<div class="actions"><button class="btn" id="p16-quiz-next">Следующий</button></div>'
+'<div class="score-display" style="margin-top:10px"><span>Раунд: <b id="p16-quiz-r">1</b> / 6</span><span>Правильно: <b id="p16-quiz-ok">0</b></span></div>'
+'</div>';
/* IV3 — DnD сортировка частиц */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-3</span><div class="wg-title">Сортировка частиц</div></div>'
+'<div class="wg-help">Распредели частицы по знаку заряда.</div>'
+'<div id="p16-dnd-pool"></div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:10px">'
+'<div class="drop-box"><h5>Положительные</h5><div class="drop-items" data-cat="pos"></div></div>'
+'<div class="drop-box"><h5>Отрицательные</h5><div class="drop-items" data-cat="neg"></div></div>'
+'<div class="drop-box"><h5>Нейтральные</h5><div class="drop-items" data-cat="neu"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p16-dnd-check">Проверить</button><button class="btn" id="p16-dnd-reset">Сброс</button></div>'
+'<div class="feedback" id="p16-dnd-fb"></div>'
+'</div>';
/* IV4 — MCQ */
h += '<div class="wg">'
+'<div class="wg-header"><span class="wg-badge">IV-4</span><div class="wg-title">Тренажёр: 6 вопросов</div></div>'
+'<div class="wg-help">4+ правильных — +15 XP.</div>'
+'<div id="p16-mcq"></div>'
+'<div class="score-display" style="margin-top:10px"><span>Вопрос: <b id="p16-mcq-i">1</b> / 6</span><span>Правильно: <b id="p16-mcq-ok">0</b></span></div>'
+'</div>';
box.innerHTML = h + secNavFor('p16') + readButton('p16');
renderMath(box);
wireReadBtn('p16');
_initP16_atom();
_initP16_quiz();
_initP16_dnd();
_initP16_mcq();
}
function _initP16_atom(){
const svg = document.getElementById('p16-sim'); if(!svg) return;
function draw(){
const Z = +document.getElementById('p16-z').value;
const N = +document.getElementById('p16-e').value;
document.getElementById('p16-zv').textContent = Z;
document.getElementById('p16-ev').textContent = N;
const delta = Z - N;
document.getElementById('p16-qbal').textContent = (delta > 0 ? '+'+delta : (delta < 0 ? delta : '0'))+' e';
let state = '';
if(delta === 0) state = 'нейтральный атом';
else if(delta > 0) state = 'катион (+'+delta+')';
else state = 'анион ('+delta+')';
document.getElementById('p16-state').textContent = state;
/* draw */
const cx = 230, cy = 120;
let s = '';
/* электронные оболочки */
const shellRs = [50, 80, 110];
for(const r of shellRs) s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="none" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3 3"/>';
/* ядро */
s += '<circle cx="'+cx+'" cy="'+cy+'" r="22" fill="#fef3c7" stroke="#dc2626" stroke-width="2"/>';
s += '<text x="'+cx+'" y="'+(cy+5)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="800" fill="#dc2626">'+Z+'p</text>';
/* электроны на оболочках: 2 на K (50), 8 на L (80), остальное на M (110) */
let placed = 0;
const shellMax = [2, 8, 18];
for(let sh = 0; sh < 3 && placed < N; sh++){
const r = shellRs[sh];
const count = Math.min(shellMax[sh], N - placed);
for(let i = 0; i < count; i++){
const ang = 2 * Math.PI * i / count + sh * 0.3;
const ex = cx + r*Math.cos(ang);
const ey = cy + r*Math.sin(ang);
s += '<circle cx="'+ex.toFixed(1)+'" cy="'+ey.toFixed(1)+'" r="5" fill="#2563eb" stroke="#0f172a" stroke-width="0.8"/>';
s += '<text x="'+ex.toFixed(1)+'" y="'+(ey+3).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" font-weight="800" fill="#fff">&minus;</text>';
}
placed += count;
}
/* пометка заряда */
if(delta !== 0){
const sign = delta > 0 ? '+' : '';
const col = delta > 0 ? '#dc2626' : '#2563eb';
s += '<text x="'+(cx+130)+'" y="'+(cy-90)+'" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="20" font-weight="900" fill="'+col+'">'+sign+delta+'</text>';
}
svg.innerHTML = s;
}
document.getElementById('p16-z').addEventListener('input', draw);
document.getElementById('p16-e').addEventListener('input', draw);
draw();
}
function _initP16_quiz(){
const QS = [
{q:'Na отдал 1 электрон. Какой ион?', opts:['Na<sup>+</sup>','Na<sup>&minus;</sup>','Na<sup>2+</sup>','Na'], ans:0, why:'Потерял 1 электрон → катион +1.'},
{q:'Cl принял 1 электрон. Какой ион?', opts:['Cl<sup>+</sup>','Cl<sup>&minus;</sup>','Cl<sup>2&minus;</sup>','Cl'], ans:1, why:'Принял 1 → анион &minus;1.'},
{q:'Ca отдал 2 электрона. Заряд иона?', opts:['+1','+2','&minus;1','0'], ans:1, why:'2 потерянных электрона → +2.'},
{q:'O принял 2 электрона. Заряд иона?', opts:['+2','&minus;2','&minus;1','0'], ans:1, why:'2 принятых электрона → &minus;2.'},
{q:'Сколько электронов у Na<sup>+</sup>? ($Z_{Na}$=11)', opts:['10','11','12','9'], ans:0, why:'Был 11, отдал 1, остался 10.'},
{q:'Сколько электронов у Cl<sup>&minus;</sup>? ($Z_{Cl}$=17)', opts:['18','17','16','19'], ans:0, why:'Принял 1, стало 18.'}
];
let i = 0, ok = 0;
function render(){
const q = QS[i]; const wrap = document.getElementById('p16-quiz'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p16-quiz-fb"></div>';
wrap.innerHTML = h;
document.getElementById('p16-quiz-r').textContent = (i+1);
document.getElementById('p16-quiz-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p16-quiz-fb');
if(k===q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(3,'p16-quiz'); bumpProgress('p16', 4); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p16-quiz-ok').textContent = ok;
});
});
}
document.getElementById('p16-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
render();
}
function _initP16_dnd(){
const items = [
{id:'p', cat:'pos', html:'протон'},
{id:'na', cat:'pos', html:'ион Na<sup>+</sup>'},
{id:'ca', cat:'pos', html:'ион Ca<sup>2+</sup>'},
{id:'e', cat:'neg', html:'электрон'},
{id:'cl', cat:'neg', html:'ион Cl<sup>&minus;</sup>'},
{id:'o', cat:'neg', html:'ион O<sup>2&minus;</sup>'},
{id:'n', cat:'neu', html:'нейтрон'},
{id:'aH', cat:'neu', html:'атом водорода H'},
{id:'aHe',cat:'neu', html:'атом гелия He'}
];
const dnd = setupSorter({ poolId:'p16-dnd-pool', scopeSelector:'#sec-p16', cats:['pos','neg','neu'], items, columnLayout:false });
document.getElementById('p16-dnd-check').addEventListener('click', ()=>{
const fb = document.getElementById('p16-dnd-fb');
let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; });
if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Идеально! +15 XP. Протон + катион, электрон + анион, нейтрон/атомы — нейтральные.'; addXp(15,'p16-dnd'); bumpProgress('p16', 20); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'. Подсказка: нейтрон и сами атомы — нейтральные, ионы — заряжены.'; }
});
document.getElementById('p16-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p16-dnd-fb'); fb.style.display='none'; });
}
function _initP16_mcq(){
const QS = [
{q:'Что находится в ядре атома?', opts:['электроны','протоны и нейтроны','только протоны','только нейтроны'], ans:1, why:'Ядро = протоны + нейтроны.'},
{q:'Какой заряд у протона?', opts:['$-e$','$+e$','0','$+2e$'], ans:1, why:'Протон несёт элементарный + заряд.'},
{q:'Какой заряд у нейтрона?', opts:['$-e$','$+e$','0','$+2e$'], ans:2, why:'Нейтрон нейтрален.'},
{q:'В нейтральном атоме число электронов равно…', opts:['числу нейтронов','числу протонов','массовому числу','$Z+N$'], ans:1, why:'$Z$ электронов компенсируют $Z$ протонов.'},
{q:'Какой ион получится, если атом отдаст 2 электрона?', opts:['&minus;2','&minus;1','+1','+2'], ans:3, why:'2 потерянных электрона → заряд +2.'},
{q:'Что больше: атом или ядро?', opts:['ядро','атом','одинаково','зависит от элемента'], ans:1, why:'Атом примерно в 100 000 раз больше ядра.'}
];
let i = 0, ok = 0, done = 0, awarded = false;
function render(){
const q = QS[i]; const wrap = document.getElementById('p16-mcq'); if(!wrap) return;
let h = '<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.5"><b>Вопрос '+(i+1)+'.</b> '+q.q+'</div><div style="display:grid;grid-template-columns:1fr;gap:6px">';
q.opts.forEach((opt,k)=>{ h += '<button class="btn" data-k="'+k+'" style="text-align:left;padding:10px 14px">'+String.fromCharCode(65+k)+'. '+opt+'</button>'; });
h += '</div><div class="feedback" id="p16-mcq-fb"></div><div class="actions"><button class="btn" id="p16-mcq-next">Следующий</button></div>';
wrap.innerHTML = h;
document.getElementById('p16-mcq-i').textContent = (i+1);
document.getElementById('p16-mcq-ok').textContent = ok;
wrap.querySelectorAll('[data-k]').forEach(btn=>{
btn.addEventListener('click', ()=>{
if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true);
const k = +btn.dataset.k; const fb = document.getElementById('p16-mcq-fb');
if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+q.why; addXp(2,'p16-mcq'); bumpProgress('p16', 3); }
else { done++; fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+q.why; }
document.getElementById('p16-mcq-ok').textContent = ok;
renderMath(wrap);
if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p16-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='&#10003; +15 XP — тренажёр пройден ('+ok+'/'+QS.length+').'; addXp(15,'p16-mcq-bonus'); bumpProgress('p16', 15); }, 600); }
});
});
const nb = document.getElementById('p16-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); });
renderMath(wrap);
}
render();
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>