Files
Learn_System/frontend/textbooks/physics_10_ch3.html
T

2319 lines
163 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Физика 10 · Глава 3 · «Электростатика»</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/g3d.js" defer></script>
<script src="/js/phys.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#7c3aed; --pri2:#6d28d9; --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,#3b0764 0%,#7c3aed 55%,#a78bfa 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 3';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero::before{content:'+q';position:absolute;right:0;top:-30px;font-size:clamp(2rem,12vw,8rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft))}
.psel-card.final .psel-num{color:var(--warn)}
.sec[id="sec-p16"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p17"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p18"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p19"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p20"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p21"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p22"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p23"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p24"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-final3"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-pool.col .dnd-chip{width:auto}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
.dnd-chip:active{cursor:grabbing}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(0,0,0,.10);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--acc));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.search-modal.show{display:flex}
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
.search-results{flex:1;overflow-y:auto;padding:6px 0}
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
/* === PHYS10 POLISH (visual + micro-interactions) === */
@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
.sec.active .wg:nth-of-type(1){animation-delay:.02s}
.sec.active .wg:nth-of-type(2){animation-delay:.08s}
.sec.active .wg:nth-of-type(3){animation-delay:.14s}
.sec.active .wg:nth-of-type(4){animation-delay:.20s}
.sec.active .wg:nth-of-type(5){animation-delay:.26s}
.sec.active .wg:nth-of-type(6){animation-delay:.32s}
.wg svg{transition:filter .25s ease}
.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
.wg input[type=range]{cursor:ew-resize}
.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
.score-display b.bump{transform:scale(1.28);color:var(--pri)}
.katex{transition:color .2s}
.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
.psel-card{position:relative}
.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.psel-card.done .psel-done{display:flex}
.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
.boss-card.pulse{animation:bossPulse .8s ease-out}
.sec{transition:opacity .25s}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 10 · Глава 3</h1>
<div class="hdr-sub">Заряд · Кулон · поле · потенциал · напряжение · конденсаторы</div>
</div>
<div class="hdr-side">
<a href="/textbook/physics-10" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 10</a>
<button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Электростатика — поле неподвижных зарядов</h2>
<p>Электрический заряд создаёт поле. Изучаем закон Кулона, напряжённость и потенциал поля, конденсаторы и их энергию.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p16')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 16</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge"></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p16" class="sec" data-watermark="q"><div class="sec-header"><span class="sec-num">§ 16</span><h2 class="sec-h">Электрический заряд</h2></div><div id="p16-body"></div></section>
<section id="sec-p17" class="sec" data-watermark="Кулон"><div class="sec-header"><span class="sec-num">§ 17</span><h2 class="sec-h">Закон Кулона</h2></div><div id="p17-body"></div></section>
<section id="sec-p18" class="sec" data-watermark="&vec;E"><div class="sec-header"><span class="sec-num">§ 18</span><h2 class="sec-h">Электростатическое поле</h2></div><div id="p18-body"></div></section>
<section id="sec-p19" class="sec" data-watermark="E"><div class="sec-header"><span class="sec-num">§ 19</span><h2 class="sec-h">Напряжённость поля. Принцип суперпозиции</h2></div><div id="p19-body"></div></section>
<section id="sec-p20" class="sec" data-watermark="&rarr;"><div class="sec-header"><span class="sec-num">§ 20</span><h2 class="sec-h">Линии напряжённости</h2></div><div id="p20-body"></div></section>
<section id="sec-p21" class="sec" data-watermark="A=qU"><div class="sec-header"><span class="sec-num">§ 21</span><h2 class="sec-h">Работа поля. Потенциал</h2></div><div id="p21-body"></div></section>
<section id="sec-p22" class="sec" data-watermark="U"><div class="sec-header"><span class="sec-num">§ 22</span><h2 class="sec-h">Разность потенциалов. Напряжение</h2></div><div id="p22-body"></div></section>
<section id="sec-p23" class="sec" data-watermark="C"><div class="sec-header"><span class="sec-num">§ 23</span><h2 class="sec-h">Конденсаторы</h2></div><div id="p23-body"></div></section>
<section id="sec-p24" class="sec" data-watermark="CU&sup2;"><div class="sec-header"><span class="sec-num">§ 24</span><h2 class="sec-h">Энергия поля конденсатора</h2></div><div id="p24-body"></div></section>
<section id="sec-final3" class="sec" data-watermark="&#9733;"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#7c3aed,#a78bfa)"></span><h2 class="sec-h">Финал главы</h2></div><div id="final3-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Физика 10» · Глава 3 · «Электростатика» · 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:'p16', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 10;
const _TB_SLUG = 'physics-10-ch3';
const PARAS = [
{ id:'p16', num:'\u00a7 16', name:"Электрический заряд", sub:'$q = ne$' },
{ id:'p17', num:'\u00a7 17', name:"Закон Кулона", sub:'$F = k\\dfrac{q_1 q_2}{r^2}$' },
{ id:'p18', num:'\u00a7 18', name:"Электростатическое поле", sub:'$\\vec{E}$' },
{ id:'p19', num:'\u00a7 19', name:"Напряжённость поля. Принцип суперпозиции", sub:'$\\vec{E} = \\sum \\vec{E_i}$' },
{ id:'p20', num:'\u00a7 20', name:"Линии напряжённости", sub:'Силовые линии' },
{ id:'p21', num:'\u00a7 21', name:"Работа поля. Потенциал", sub:'$A = qU$' },
{ id:'p22', num:'\u00a7 22', name:"Разность потенциалов. Напряжение", sub:'$U = E \\cdot d$' },
{ id:'p23', num:'\u00a7 23', name:"Конденсаторы", sub:'$C = q/U$' },
{ id:'p24', num:'\u00a7 24', name:"Энергия поля конденсатора", sub:'$W = CU^2/2$' },
{ id:'final3', num:'\u2605', name:'Финал главы', sub:'Итоги \u00b7 боссы главы 3', final:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:"Начало главы 3!",
p16_done:"Электрический заряд освоен!",
p17_done:"Закон Кулона освоен!",
p18_done:"Электростатическое поле освоен!",
p19_done:"Напряжённость поля. Принцип суперпозиции освоен!",
p20_done:"Линии напряжённости освоен!",
p21_done:"Работа поля. Потенциал освоен!",
p22_done:"Разность потенциалов. Напряжение освоен!",
p23_done:"Конденсаторы освоен!",
p24_done:"Энергия поля конденсатора освоен!",
ch3_done:"Глава 3 пройдена!"
};
function loadProgress(){
try{
const s=localStorage.getItem('physics10_ch3_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics10_ch3_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('physics10_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('physics10_ch3_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics10_ch3_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('physics10_xp', String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
function loadServerReadState(){
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{
if(!d||!d.progress) return;
(d.progress.read||[]).forEach(k=>{_markedRead.add(k); if((STATE.progress[k]||0)<50) STATE.progress[k]=100;});
saveProgress(); refreshProgressUI();
}).catch(()=>{});
}
function addXp(n,src){
if(!n) return;
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
saveProgress(); refreshProgressUI();
if(window.LS&&window.LS.xp) window.LS.xp.add(n,'physics10-ch3-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p16:()=>build_p16(), p17:()=>build_p17(), p18:()=>build_p18(), p19:()=>build_p19(), p20:()=>build_p20(), p21:()=>build_p21(), p22:()=>build_p22(), p23:()=>build_p23(), p24:()=>build_p24(), final3:()=>build_final3() };
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
const SIDEBARS = {
p16:{title:"Шпаргалка §16",rows:[["Заряд","$q$ — Кл"],["Электрон","$e = 1{,}6 \\cdot 10^{-19}$ Кл"],["Закон сохр.","$\\sum q = \\text{const}$ в замкн. системе"],["$q = ne$","дискретность"]]},
p17:{title:"Шпаргалка §17",rows:[["Закон","$F = k\\dfrac{|q_1 q_2|}{r^2}$"],["$k$","$k = 9 \\cdot 10^9$ Н·м²/Кл²"],["$\\varepsilon_0$","$\\varepsilon_0 = 8{,}85 \\cdot 10^{-12}$ Ф/м"]]},
p18:{title:"Шпаргалка §18",rows:[["Поле","посредник взаимодействия"],["Свойства","действует на заряд силой $\\vec{F}$"],["Источник","$+q$ или $-q$"]]},
p19:{title:"Шпаргалка §19",rows:[["$\\vec{E}$","$\\vec{E} = \\vec{F}/q_0$"],["Точ. заряд","$E = k|q|/r^2$"],["Суперпоз.","$\\vec{E} = \\sum \\vec{E_i}$"]]},
p20:{title:"Шпаргалка §20",rows:[["Линии","касат. — $\\vec{E}$"],["Густота","$E$ велико — линии чаще"],["$+ \\to -$","начало на $+$, конец на $-$ или $\\infty$"]]},
p21:{title:"Шпаргалка §21",rows:[["Работа поля","$A_{поля} = qU$ — не зависит от пути"],["Потенц.","$\\varphi = W_p/q$"],["Знак","от $+$ к $-$ ток. зар. — $A > 0$"]]},
p22:{title:"Шпаргалка §22",rows:[["Разность","$U = \\varphi_1 - \\varphi_2$"],["Однор. поле","$U = Ed$"],["Эквипот.","$\\bot$ линиям $\\vec{E}$"]]},
p23:{title:"Шпаргалка §23",rows:[["$C = q/U$","Ф (фарад)"],["Плоский","$C = \\varepsilon\\varepsilon_0 S/d$"],["Парал.","$C = \\sum C_i$"],["Послед.","$1/C = \\sum 1/C_i$"]]},
p24:{title:"Шпаргалка §24",rows:[["Энергия","$W = \\tfrac{CU^2}{2} = \\tfrac{q^2}{2C} = \\tfrac{qU}{2}$"],["Плотн.","$w = \\tfrac{\\varepsilon\\varepsilon_0 E^2}{2}$"]]},
final3:{title:"Финал главы 3",rows:[["§§1624","теория главы 3"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p16',html:"Заряд квантуется: $q = ne$, где $e = 1{,}6 \\cdot 10^{-19}$ Кл. Закон сохранения заряда — фундаментальный."},
{sec:'p17',html:"$F = k\\dfrac{|q_1 q_2|}{r^2}$, $k = 9 \\cdot 10^9$ Н·м²/Кл². Аналог закона всемирного тяготения."},
{sec:'p18',html:"Поле — посредник взаимодействия. Действует на заряд силой $\\vec{F} = q\\vec{E}$."},
{sec:'p19',html:"$\\vec{E} = \\vec{F}/q_0$ — векторная характеристика поля. Принцип суперпозиции: $\\vec{E} = \\sum \\vec{E_i}$."},
{sec:'p20',html:"Линии напряжённости — касательные к $\\vec{E}$. Начинаются на «+» зарядах, заканчиваются на «−» или в $\\infty$."},
{sec:'p21',html:"Работа поля не зависит от пути: $A_{поля} = qU = q(\\varphi_1 - \\varphi_2)$. Поле потенциально."},
{sec:'p22',html:"В однородном поле: $U = E \\cdot d$. Эквипотенциальные поверхности перпендикулярны линиям $\\vec{E}$."},
{sec:'p23',html:"$C = q/U$ — Ф. Плоский: $C = \\dfrac{\\varepsilon\\varepsilon_0 S}{d}$. Параллельно — $C_\\Sigma = \\sum C_i$."},
{sec:'p24',html:"$W = \\dfrac{CU^2}{2} = \\dfrac{q^2}{2C} = \\dfrac{qU}{2}$ — три эквивалентные формулы."},
{sec:'final3',html:"Финал главы 3 — интегрированные задачи по §§16–24. В разработке (Phase 3+)."}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS[PARAS[0].id];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('physics10_ch3_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('physics10_ch3_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
function ipow(base, exp){ let r=1; for(let i=0;i<Math.abs(exp);i++) r*=base; return exp<0 ? 1/r : r; }
function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
function setupSorter(cfg){
const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed = null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
/* === SVG-хелперы (axes2D, plotFunc, pointWithDrop, asymptote, snapToValue, геом.) === */
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
const ux = (W - 2*pad) / (xmax - xmin);
const uy = (H - 2*pad) / (ymax - ymin);
const toX = v => pad + (v - xmin) * ux;
const toY = v => H - pad - (v - ymin) * uy;
let g = '';
g += '<g stroke="#e5e7eb" stroke-width="1">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
g += '<line x1="'+toX(x)+'" y1="'+pad+'" x2="'+toX(x)+'" y2="'+(H-pad)+'"/>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
g += '<line x1="'+pad+'" y1="'+toY(y)+'" x2="'+(W-pad)+'" y2="'+toY(y)+'"/>';
}
g += '</g>';
const y0 = toY(0), x0 = toX(0);
g += '<line x1="'+pad+'" y1="'+y0+'" x2="'+(W-pad)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<line x1="'+x0+'" y1="'+pad+'" x2="'+x0+'" y2="'+(H-pad)+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<text x="'+(W-pad+2)+'" y="'+(y0-4)+'" font-size="11" fill="#0f172a">x</text>';
g += '<text x="'+(x0+4)+'" y="'+(pad-2)+'" font-size="11" fill="#0f172a">y</text>';
g += '<g font-size="10" fill="#64748b">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
if (x !== 0) g += '<text x="'+(toX(x)-3)+'" y="'+(y0+12)+'">'+x+'</text>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
if (y !== 0) g += '<text x="'+(x0+4)+'" y="'+(toY(y)+3)+'">'+y+'</text>';
}
g += '<text x="'+(x0+4)+'" y="'+(y0+12)+'">0</text>';
g += '</g>';
return { content: g, toX, toY, ux, uy };
}
function plotFunc(f, xmin, xmax, toX, toY, color, N){
N = N || 200;
let d = '';
let prevValid = false;
for (let i = 0; i <= N; i++){
const x = xmin + (xmax - xmin) * i / N;
let y;
try { y = f(x); } catch(e){ y = NaN; }
if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
prevValid = true;
}
return '<path d="'+d+'" stroke="'+color+'" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>';
}
function pointWithDrop(x, fx, toX, toY, color, label){
const px = toX(x), py = toY(fx);
let s = '';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+px+'" y2="'+toY(0)+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+toX(0)+'" y2="'+py+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<circle cx="'+px+'" cy="'+py+'" r="4.5" fill="'+color+'" stroke="#fff" stroke-width="2"/>';
if (label){
s += '<text x="'+(px+8)+'" y="'+(py-8)+'" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+color+'">'+label+'</text>';
}
return s;
}
function asymptote(orientation, value, toX, toY, xmin, xmax, ymin, ymax, color){
color = color || '#94a3b8';
if (orientation === 'h'){
const y = toY(value);
return '<line x1="'+toX(xmin)+'" y1="'+y+'" x2="'+toX(xmax)+'" y2="'+y+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
} else {
const x = toX(value);
return '<line x1="'+x+'" y1="'+toY(ymin)+'" x2="'+x+'" y2="'+toY(ymax)+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
}
}
function snapToValue(value, snapPoints, tolerance){
tolerance = tolerance || 0.1;
for (const sp of snapPoints){
if (Math.abs(value - sp) < tolerance) return sp;
}
return value;
}
function rightAngleMark(V, uIn, wIn, s){
s = s || 9;
const p1 = {x: V.x + s*uIn.x, y: V.y + s*uIn.y};
const c = {x: p1.x + s*wIn.x, y: p1.y + s*wIn.y};
const p2 = {x: V.x + s*wIn.x, y: V.y + s*wIn.y};
return p1.x+','+p1.y+' '+c.x+','+c.y+' '+p2.x+','+p2.y;
}
function angleArcAuto(V, uA, uB, R){
const sA = {x: V.x + R*uA.x, y: V.y + R*uA.y};
const eB = {x: V.x + R*uB.x, y: V.y + R*uB.y};
const cross = uA.x*uB.y - uA.y*uB.x;
const sweep = cross > 0 ? 1 : 0;
return 'M'+sA.x+','+sA.y+' A'+R+','+R+' 0 0,'+sweep+' '+eB.x+','+eB.y;
}
function unitVec(p1, p2){
const dx = p2.x - p1.x, dy = p2.y - p1.y;
const len = Math.sqrt(dx*dx + dy*dy) || 1;
return {x: dx/len, y: dy/len};
}
function deg2rad(d){ return d * Math.PI / 180; }
const ICONS = {
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
};
function secNavFor(curId){
const idx = PARAS.findIndex(p => p.id === curId);
const prev = idx > 0 ? PARAS[idx-1].id : null;
const next = idx < PARAS.length - 1 ? PARAS[idx+1].id : null;
return secNav(prev, next);
}
function secNav(prev, next){
const NAMES = {p16:'\xA716',p17:'\xA717',p18:'\xA718',p19:'\xA719',p20:'\xA720',p21:'\xA721',p22:'\xA722',p23:'\xA723',p24:'\xA724',final3:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
const p = PARAS.find(x => x.id === paraId);
const labelTail = p && p.final ? 'финал' : (p ? p.num : '\xA7?');
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал — '+labelTail+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
const aId = paraId+'_done';
if(ACH_LABELS[aId]) achievement(aId);
});
}
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function build_p16(){
const box = document.getElementById('p16-body');
let html = '';
/* THEORY 1 — Электрический заряд и виды */
html += makeCard('theory', "Электрический заряд и виды", "§16", `
<p><b>Электрический заряд</b> $q$ — физическая величина, характеризующая способность тел и частиц участвовать в электромагнитных взаимодействиях.</p>
<p style="margin-top:8px">Бывает <b>двух видов</b>: положительный (+) и отрицательный (&minus;).</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b style="color:#dc2626">Одноимённые</b> заряды <b>отталкиваются</b>.</li>
<li><b style="color:#2563eb">Разноимённые</b> заряды <b>притягиваются</b>.</li>
</ul>
<p><b>Элементарный заряд</b> — наименьший существующий в природе модуль заряда:</p>
<p style="text-align:center;margin:10px 0">$$e = 1{,}6 \\cdot 10^{-19} \\text{ Кл}$$</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Электрон</b>: заряд $-e$, масса $\\sim 9{,}1 \\cdot 10^{-31}$ кг.</li>
<li><b>Протон</b>: заряд $+e$, масса $\\sim 1{,}67 \\cdot 10^{-27}$ кг (в 1836 раз больше электрона).</li>
<li><b>Нейтрон</b>: заряд $0$.</li>
</ul>
<p>Любой макроскопический заряд <b>кратен элементарному</b>: $q = ne$, где $n$ — целое число.</p>
`);
/* THEORY 2 — Закон сохранения заряда */
html += makeCard('rule', "Закон сохранения заряда", "§16", `
<p><b>Закон сохранения электрического заряда</b>: в любой замкнутой (изолированной) системе алгебраическая сумма электрических зарядов сохраняется:</p>
<p style="text-align:center;margin:10px 0">$$\\sum q_i = \\text{const}$$</p>
<p>Заряды могут <b>перераспределяться</b> между телами, но общий заряд не меняется.</p>
<p style="margin-top:8px"><b>Способы электризации</b> (передачи заряда):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Трением</b>: например, расчёска из пластика трением о волосы.</li>
<li><b>Прикосновением</b>: заряженное тело передаёт часть заряда нейтральному.</li>
<li><b>Через индукцию</b>: заряженное тело наводит противоположный заряд на близлежащее проводящее тело (без касания).</li>
</ul>
<p style="padding:10px 14px;background:var(--warn-bg,#fef3c7);border-left:4px solid var(--warn,#f59e0b);border-radius:9px;margin:10px 0"><b>Пример:</b> два одинаковых металлических шара. Один заряжен зарядом $q$, другой нейтрален. После соприкосновения заряд распределится поровну: на каждом по $q/2$.</p>
`);
/* THEORY 3 — Единица заряда */
html += makeCard('example', "Единица заряда и примеры", "§16", `
<p><b>Единица заряда</b> в СИ — <b>Кулон</b> (Кл). Назван в честь Шарля Кулона.</p>
<p style="margin-top:8px">$1$ Кл — заряд, эквивалентный $1/e \\approx 6{,}25 \\cdot 10^{18}$ элементарных зарядов.</p>
<p>Это <b>очень большой заряд</b>! В реальных задачах часто используются:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>мкКл</b> (микроКулон) $= 10^{-6}$ Кл</li>
<li><b>нКл</b> (наноКулон) $= 10^{-9}$ Кл</li>
<li><b>пКл</b> (пикоКулон) $= 10^{-12}$ Кл</li>
</ul>
<p><b>Примеры зарядов</b>:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Заряд электрона: $-1{,}6 \\cdot 10^{-19}$ Кл.</li>
<li>Заряд натёртой стеклянной палочки: $\\sim 10^{-7}-10^{-9}$ Кл.</li>
<li>Заряд молнии: $\\sim 10-100$ Кл.</li>
<li>Заряд при касании дверной ручки в сухом помещении: пара нКл, но напряжение огромно!</li>
</ul>
<p style="padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0"><b>Точечный заряд</b> — модель: заряженное тело, размерами которого можно пренебречь по сравнению с расстояниями до других объектов.</p>
`);
/* INTERACTIVE 1 — Симуляция передачи заряда */
html += `<div class="wg" id="p16-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Закон сохранения заряда: симуляция</div></div>
<div class="wg-help">Два одинаковых металлических шара. Задай заряды и нажми «Прикоснуть» — заряды распределятся поровну. Сумма $q_1 + q_2$ сохранится!</div>
<div class="sliders">
<label>$q_1$: <b id="p16-iv1-q1L">+5</b> нКл <input type="range" id="p16-iv1-q1" min="-10" max="10" value="5" step="1"></label>
<label>$q_2$: <b id="p16-iv1-q2L">-3</b> нКл <input type="range" id="p16-iv1-q2" min="-10" max="10" value="-3" step="1"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p16-iv1-svg" viewBox="0 0 380 220" width="100%" style="height:auto"></svg>
</div>
<div class="actions" style="justify-content:center;margin-top:10px">
<button class="btn primary" id="p16-iv1-touch">Прикоснуть шары</button>
<button class="btn" id="p16-iv1-reset">Сначала</button>
</div>
<div id="p16-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
<div class="feedback" id="p16-iv1-fb"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор электронов */
html += `<div class="wg" id="p16-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор числа электронов</div></div>
<div class="wg-help">Введи заряд $q$ — получи число электронов $|n| = |q|/e$ (избыток при $q<0$, недостаток при $q>0$).</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<label>$q =$ <input type="number" id="p16-iv2-q" class="tinp" style="width:120px;text-align:center" value="-1" step="any"></label>
<select id="p16-iv2-unit" class="tinp" style="padding:8px 10px">
<option value="1e-9">нКл</option>
<option value="1e-6">мкКл</option>
<option value="1e-12">пКл</option>
<option value="1">Кл</option>
</select>
<button class="btn primary" id="p16-iv2-go">Вычислить</button>
</div>
<div id="p16-iv2-out" style="padding:12px 14px;background:var(--card);border:1px solid var(--border);border-radius:9px;font-size:.94rem;min-height:60px;line-height:1.85"></div>
<div class="feedback" id="p16-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр «Притяжение или отталкивание?» */
html += `<div class="wg" id="p16-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Притяжение или отталкивание?</div></div>
<div class="wg-help">6 ситуаций — выбери, что произойдёт между телами.</div>
<div class="score-display"><span>Задача <b id="p16-iv3-i">1</b> / 6</span><span>Очки: <b id="p16-iv3-s">0</b> / 6</span></div>
<div id="p16-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p16-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p16-iv3-fb"></div>
<div class="actions"><button class="btn" id="p16-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр заряда */
html += `<div class="wg" id="p16-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр заряда</div></div>
<div class="wg-help">5 задач. $e = 1{,}6 \\cdot 10^{-19}$ Кл. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p16-iv4-i">1</b> / 5</span><span>Очки: <b id="p16-iv4-s">0</b> / 5</span></div>
<div id="p16-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p16-iv4-ans" class="tinp" style="width:140px;text-align:center" step="any">
<button class="btn primary" id="p16-iv4-go">Проверить</button>
<button class="btn" id="p16-iv4-start">Заново</button>
</div>
<div class="feedback" id="p16-iv4-fb"></div>
</div>`;
html += secNav(null, 'p17');
html += readButton('p16');
box.innerHTML = html;
renderMath(box);
/* IV1 — Симуляция передачи заряда */
(function(){
const svg = document.getElementById('p16-iv1-svg');
const q1S = document.getElementById('p16-iv1-q1');
const q2S = document.getElementById('p16-iv1-q2');
const q1L = document.getElementById('p16-iv1-q1L');
const q2L = document.getElementById('p16-iv1-q2L');
const out = document.getElementById('p16-iv1-out');
const fb = document.getElementById('p16-iv1-fb');
let q1 = +q1S.value, q2 = +q2S.value;
const seen = new Set();
let _done = false;
function fmtQ(q){ return (q>=0?'+':'') + q; }
function render(){
const W=380, H=220;
// Координаты шаров
const cx1 = 110, cx2 = 270, cy = 120;
const r = 30;
let g = '';
// Подставка
g += '<line x1="60" y1="180" x2="320" y2="180" stroke="#94a3b8" stroke-width="2"/>';
g += '<line x1="' + cx1 + '" y1="' + (cy + r) + '" x2="' + cx1 + '" y2="180" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="3 3"/>';
g += '<line x1="' + cx2 + '" y1="' + (cy + r) + '" x2="' + cx2 + '" y2="180" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="3 3"/>';
// Шары
g += PHYS.chargeMark(cx1, cy, q1 === 0 ? 1 : (q1 > 0 ? 1 : -1), r, '');
if(q1 === 0){
// overlay серый круг для нейтрального
g += '<circle cx="' + cx1 + '" cy="' + cy + '" r="' + r + '" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="' + cx1 + '" y="' + (cy + 4) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
}
g += PHYS.chargeMark(cx2, cy, q2 === 0 ? 1 : (q2 > 0 ? 1 : -1), r, '');
if(q2 === 0){
g += '<circle cx="' + cx2 + '" cy="' + cy + '" r="' + r + '" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="' + cx2 + '" y="' + (cy + 4) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
}
// Подписи
g += '<text x="' + cx1 + '" y="' + (cy + r + 22) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#0f172a">q₁ = ' + fmtQ(q1) + ' нКл</text>';
g += '<text x="' + cx2 + '" y="' + (cy + r + 22) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#0f172a">q₂ = ' + fmtQ(q2) + ' нКл</text>';
// Заголовок
g += '<text x="190" y="30" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Два металлических шара</text>';
svg.innerHTML = g;
const sum = q1 + q2;
out.innerHTML = '<b>Сумма зарядов:</b> $q_1 + q_2 = ' + fmtQ(q1) + ' + (' + fmtQ(q2) + ') = ' + fmtQ(sum) + '$ нКл (сохраняется!)';
renderMath(out);
}
function sync(){
q1 = +q1S.value; q2 = +q2S.value;
q1L.textContent = fmtQ(q1); q2L.textContent = fmtQ(q2);
render();
}
q1S.addEventListener('input', sync);
q2S.addEventListener('input', sync);
document.getElementById('p16-iv1-touch').addEventListener('click', () => {
const sum = q1 + q2;
const avg = sum / 2;
q1 = avg; q2 = avg;
q1S.value = q1; q2S.value = q2;
q1L.textContent = fmtQ(q1); q2L.textContent = fmtQ(q2);
render();
feedback(fb, true, '&#10003; После касания: $q_1 = q_2 = (q_1 + q_2)/2 = ' + fmtQ(avg) + '$ нКл. Сумма сохранилась!');
const key = (+q1S.value) + ':' + (+q2S.value);
seen.add(key);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p16-iv1'); bumpProgress('p16', 15); }
});
document.getElementById('p16-iv1-reset').addEventListener('click', () => {
q1 = 5; q2 = -3; q1S.value = 5; q2S.value = -3;
q1L.textContent = '+5'; q2L.textContent = '-3';
fb.style.display = 'none';
render();
});
sync();
})();
/* IV2 — Калькулятор электронов */
(function(){
const qI = document.getElementById('p16-iv2-q');
const uI = document.getElementById('p16-iv2-unit');
const out = document.getElementById('p16-iv2-out');
const fb = document.getElementById('p16-iv2-fb');
const seen = new Set();
let _done = false;
const e = PHYS.CONST.e;
function calc(){
const qv = parseFloat(qI.value);
if(!isFinite(qv)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const unit = parseFloat(uI.value);
const qC = qv * unit;
const n = Math.abs(qC) / e;
const exp = Math.floor(Math.log10(n));
const coef = n / Math.pow(10, exp);
const sign = qC < 0 ? '<b style="color:#2563eb">избыток</b>' : (qC > 0 ? '<b style="color:#dc2626">недостаток</b>' : '<b>нет</b>');
const qExp = qC === 0 ? 0 : Math.floor(Math.log10(Math.abs(qC)));
const qCoef = qC === 0 ? 0 : qC / Math.pow(10, qExp);
out.innerHTML = '<div style="margin-bottom:6px"><b>Заряд в Кл:</b> $q = ' + (qC === 0 ? '0' : (+qCoef.toFixed(3) + ' \\cdot 10^{' + qExp + '}')) + '$ Кл</div>'
+ '<div style="margin-bottom:6px"><b>Число электронов:</b> $|n| = \\dfrac{|q|}{e} = \\dfrac{' + Math.abs(qC).toExponential(2).replace('e','\\cdot 10^{') + '}}{1{,}6 \\cdot 10^{-19}} \\approx ' + (+coef.toFixed(2)) + ' \\cdot 10^{' + exp + '}$</div>'
+ '<div>' + sign + ' электронов (' + (qC < 0 ? 'заряд отрицательный' : (qC > 0 ? 'заряд положительный' : 'нейтрально')) + ').</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
seen.add((+qv.toFixed(3)) + ':' + uI.value);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p16-iv2'); bumpProgress('p16', 15); }
}
document.getElementById('p16-iv2-go').addEventListener('click', calc);
qI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
})();
/* IV3 — квикфайр Притяжение/Отталкивание */
(function(){
const Q = [
{ q:'Два положительно заряженных шарика рядом. Что происходит?', a:'Притяжение', b:'Отталкивание', ans:1, why:'Одноимённые ($+$ и $+$) отталкиваются.' },
{ q:'Положительный заряд и отрицательный заряд рядом.', a:'Притяжение', b:'Отталкивание', ans:0, why:'Разноимённые ($+$ и $-$) притягиваются.' },
{ q:'Два отрицательно заряженных тела рядом.', a:'Притяжение', b:'Отталкивание', ans:1, why:'Одноимённые ($-$ и $-$) отталкиваются.' },
{ q:'Заряженная стеклянная палочка подносится к нейтральному бумажному шарику (без касания).', a:'Притяжение (через индукцию)', b:'Отталкивание', ans:0, why:'На нейтральном шарике наводится противоположный заряд — индукция вызывает притяжение.' },
{ q:'Электрон и протон.', a:'Притяжение', b:'Отталкивание', ans:0, why:'Электрон $-e$, протон $+e$ — разноимённые, притягиваются.' },
{ q:'Два электрона.', a:'Притяжение', b:'Отталкивание', ans:1, why:'Оба отрицательны ($-e$, $-e$) — отталкиваются.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p16-iv3-q');
const oEl = document.getElementById('p16-iv3-opts');
const fb = document.getElementById('p16-iv3-fb');
const iEl = document.getElementById('p16-iv3-i');
const sEl = document.getElementById('p16-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p16-iv3'); bumpProgress('p16', 25); }
else if(score >= 4){ addXp(8, 'p16-iv3'); bumpProgress('p16', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="0">' + item.a + '</button><button class="btn primary" data-v="1">' + item.b + '</button>';
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1600);
});
});
}
document.getElementById('p16-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр заряда */
(function(){
const Q = [
{ q:'Сколько электронов нужно для заряда $-1$ нКл? Введи мантиссу при $10^9$.', ans:6.25, tol:0.3, hint:'$n = 10^{-9}/(1{,}6\\cdot 10^{-19}) = 6{,}25 \\cdot 10^9$.' },
{ q:'Два одинаковых шара с зарядами $+4$ нКл и $-6$ нКл соприкоснулись. Заряд каждого после (в нКл)?', ans:-1, tol:0.1, hint:'$(q_1+q_2)/2 = (4-6)/2 = -1$ нКл.' },
{ q:'Заряд $q = +3{,}2 \\cdot 10^{-19}$ Кл соответствует скольким элементарным?', ans:2, tol:0.1, hint:'$n = 3{,}2/1{,}6 = 2$.' },
{ q:'Заряженный шар отдал половину своего заряда. Если был $+10$ нКл, сколько осталось (в нКл)?', ans:5, tol:0.2, hint:'$10/2 = 5$ нКл.' },
{ q:'Заряд молнии $\\approx 30$ Кл. Сколько это электронов? Введи мантиссу при $10^{20}$.', ans:1.9, tol:0.3, hint:'$n = 30/(1{,}6 \\cdot 10^{-19}) \\approx 1{,}9 \\cdot 10^{20}$.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p16-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p16-iv4'); bumpProgress('p16', 25); }
else if(score >= 3){ addXp(8, 'p16-iv4'); bumpProgress('p16', 15); }
return;
}
document.getElementById('p16-iv4-i').textContent = (i+1);
document.getElementById('p16-iv4-s').textContent = score;
document.getElementById('p16-iv4-q').innerHTML = Q[i].q;
document.getElementById('p16-iv4-ans').value = '';
renderMath(document.getElementById('p16-iv4-q'));
document.getElementById('p16-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p16-iv4-fb');
const raw = document.getElementById('p16-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p16-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p16-iv4-go').addEventListener('click', go);
document.getElementById('p16-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p16-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p16');
}
function build_p17(){
const box = document.getElementById('p17-body');
let html = '';
/* THEORY 1 — Закон Кулона */
html += makeCard('theory', "Закон Кулона", "§17", `
<p><b>Закон Кулона</b>: сила взаимодействия двух точечных зарядов прямо пропорциональна произведению модулей зарядов и обратно пропорциональна квадрату расстояния между ними:</p>
<p style="text-align:center;margin:10px 0">$$F = k\\,\\dfrac{|q_1 q_2|}{r^2}$$</p>
<p>где:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$F$ — сила (Н);</li>
<li>$q_1, q_2$ — заряды (Кл);</li>
<li>$r$ — расстояние между зарядами (м);</li>
<li>$k = 9 \\cdot 10^9$ Н$\\cdot$м$^2$/Кл$^2$ — коэффициент пропорциональности.</li>
</ul>
<p>Альтернативная форма:</p>
<p style="text-align:center;margin:10px 0">$$F = \\dfrac{1}{4\\pi\\varepsilon_0} \\cdot \\dfrac{|q_1 q_2|}{r^2}$$</p>
<p>где $\\varepsilon_0 = 8{,}85 \\cdot 10^{-12}$ Ф/м — <b>электрическая постоянная</b>.</p>
<p style="margin-top:8px"><b>Направление</b>:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Одноимённые ($q_1 q_2 > 0$): силы направлены <b>от</b> другого заряда (отталкивание).</li>
<li>Разноимённые ($q_1 q_2 < 0$): силы направлены <b>к</b> другому заряду (притяжение).</li>
</ul>
<p>По 3-му закону Ньютона $\\vec{F}_{12} = -\\vec{F}_{21}$.</p>
`);
/* THEORY 2 — Закон Кулона в среде */
html += makeCard('rule', "Закон Кулона в среде", "§17", `
<p>В диэлектрической среде сила Кулона уменьшается в $\\varepsilon$ раз:</p>
<p style="text-align:center;margin:10px 0">$$F = k\\,\\dfrac{|q_1 q_2|}{\\varepsilon\\, r^2}$$</p>
<p>где $\\varepsilon$ — <b>относительная диэлектрическая проницаемость</b> среды (безразмерная).</p>
<p style="margin-top:8px"><b>Примеры</b>:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Вакуум, воздух</b>: $\\varepsilon \\approx 1$.</li>
<li><b>Бензин</b>: $\\varepsilon \\approx 2$.</li>
<li><b>Стекло</b>: $\\varepsilon \\approx 5-10$.</li>
<li><b>Вода</b>: $\\varepsilon \\approx 81$ (сильное ослабление сил!).</li>
<li><b>Сегнетоэлектрики</b>: $\\varepsilon$ может достигать сотен и тысяч.</li>
</ul>
<p style="padding:10px 14px;background:var(--warn-bg,#fef3c7);border-left:4px solid var(--warn,#f59e0b);border-radius:9px;margin:10px 0">Это объясняет, почему соли и кислоты легко диссоциируют в воде: силы между ионами в воде в $81$ раз слабее, чем в вакууме.</p>
`);
/* THEORY 3 — Принцип суперпозиции */
html += makeCard('example', "Принцип суперпозиции и пример", "§17", `
<p><b>Принцип суперпозиции</b>: если на заряд $q$ действуют несколько других зарядов, результирующая сила равна <b>векторной сумме</b> сил от каждого заряда отдельно:</p>
<p style="text-align:center;margin:10px 0">$$\\vec{F} = \\vec{F_1} + \\vec{F_2} + \\vec{F_3} + \\ldots$$</p>
<p>Силы складываются как векторы (учитывая направления!).</p>
<p style="margin-top:10px"><b>Пример.</b> Два заряда по $+1$ мкКл расположены на расстоянии $0{,}1$ м. Найти силу.</p>
<p style="text-align:center;margin:10px 0">$$F = 9 \\cdot 10^9 \\cdot \\dfrac{(10^{-6})^2}{(0{,}1)^2} = 9 \\cdot 10^9 \\cdot \\dfrac{10^{-12}}{0{,}01} = 0{,}9 \\text{ Н}$$</p>
<p style="padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0"><b>Сравнение с гравитацией</b>: кулоновская сила между двумя протонами в $\\sim 10^{36}$ раз сильнее гравитации. В макромире доминирует электромагнетизм; гравитация важна только потому, что положительные и отрицательные заряды в обычных телах в среднем уравновешиваются.</p>
`);
/* INTERACTIVE 1 — Визуализатор закона Кулона (главный) */
html += `<div class="wg" id="p17-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Визуализатор закона Кулона</div></div>
<div class="wg-help">Меняй заряды и расстояние — стрелки покажут силы, формула $F = k|q_1 q_2|/r^2$ пересчитается.</div>
<div class="sliders">
<label>$q_1$: <b id="p17-iv1-q1L">+5</b> нКл <input type="range" id="p17-iv1-q1" min="-10" max="10" value="5" step="0.5"></label>
<label>$q_2$: <b id="p17-iv1-q2L">+5</b> нКл <input type="range" id="p17-iv1-q2" min="-10" max="10" value="5" step="0.5"></label>
<label>$r$: <b id="p17-iv1-rL">0.20</b> м <input type="range" id="p17-iv1-r" min="0.05" max="0.5" value="0.2" step="0.01"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p17-iv1-svg" viewBox="0 0 480 260" width="100%" style="height:auto"></svg>
</div>
<div id="p17-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор силы Кулона */
html += `<div class="wg" id="p17-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор силы Кулона</div></div>
<div class="wg-help">Введи заряды, расстояние, выбери среду. $F = k|q_1 q_2|/(\\varepsilon r^2)$, $k = 9 \\cdot 10^9$.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-bottom:10px">
<label style="display:flex;flex-direction:column;gap:4px;font-size:.86rem">$q_1$
<div style="display:flex;gap:4px">
<input type="number" id="p17-iv2-q1" class="tinp" style="width:80px;text-align:center" value="1" step="any">
<select id="p17-iv2-u1" class="tinp" style="padding:4px"><option value="1e-9">нКл</option><option value="1e-6" selected>мкКл</option><option value="1">Кл</option></select>
</div>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.86rem">$q_2$
<div style="display:flex;gap:4px">
<input type="number" id="p17-iv2-q2" class="tinp" style="width:80px;text-align:center" value="1" step="any">
<select id="p17-iv2-u2" class="tinp" style="padding:4px"><option value="1e-9">нКл</option><option value="1e-6" selected>мкКл</option><option value="1">Кл</option></select>
</div>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.86rem">$r$
<div style="display:flex;gap:4px">
<input type="number" id="p17-iv2-r" class="tinp" style="width:80px;text-align:center" value="10" step="any">
<select id="p17-iv2-ur" class="tinp" style="padding:4px"><option value="0.01">см</option><option value="1" selected>м</option></select>
</div>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.86rem">$\\varepsilon$
<select id="p17-iv2-eps" class="tinp" style="padding:6px">
<option value="1" selected>1 (воздух)</option>
<option value="2">2 (бензин)</option>
<option value="7">7 (стекло)</option>
<option value="81">81 (вода)</option>
</select>
</label>
</div>
<div class="actions" style="justify-content:center"><button class="btn primary" id="p17-iv2-go">Вычислить $F$</button></div>
<div id="p17-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border:1px solid var(--border);border-radius:9px;font-size:.94rem;min-height:60px;line-height:1.85"></div>
<div class="feedback" id="p17-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр «Как изменится сила?» */
html += `<div class="wg" id="p17-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Как изменится сила?</div></div>
<div class="wg-help">6 ситуаций — как изменится сила Кулона? 4 варианта ответа.</div>
<div class="score-display"><span>Задача <b id="p17-iv3-i">1</b> / 6</span><span>Очки: <b id="p17-iv3-s">0</b> / 6</span></div>
<div id="p17-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p17-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p17-iv3-fb"></div>
<div class="actions"><button class="btn" id="p17-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр Кулона */
html += `<div class="wg" id="p17-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр закона Кулона</div></div>
<div class="wg-help">5 задач. $k = 9 \\cdot 10^9$ Н$\\cdot$м$^2$/Кл$^2$. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p17-iv4-i">1</b> / 5</span><span>Очки: <b id="p17-iv4-s">0</b> / 5</span></div>
<div id="p17-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p17-iv4-ans" class="tinp" style="width:140px;text-align:center" step="any">
<button class="btn primary" id="p17-iv4-go">Проверить</button>
<button class="btn" id="p17-iv4-start">Заново</button>
</div>
<div class="feedback" id="p17-iv4-fb"></div>
</div>`;
html += secNav('p16', 'p18');
html += readButton('p17');
box.innerHTML = html;
renderMath(box);
/* IV1 — Главный визуализатор закона Кулона */
(function(){
const svg = document.getElementById('p17-iv1-svg');
const q1S = document.getElementById('p17-iv1-q1');
const q2S = document.getElementById('p17-iv1-q2');
const rS = document.getElementById('p17-iv1-r');
const q1L = document.getElementById('p17-iv1-q1L');
const q2L = document.getElementById('p17-iv1-q2L');
const rL = document.getElementById('p17-iv1-rL');
const out = document.getElementById('p17-iv1-out');
const seen = new Set();
let _done = false;
const k = PHYS.CONST.k;
function fmtQ(q){ return (q>=0?'+':'') + q.toFixed(1); }
function render(){
const q1 = +q1S.value, q2 = +q2S.value, r = +rS.value;
q1L.textContent = fmtQ(q1); q2L.textContent = fmtQ(q2); rL.textContent = r.toFixed(2);
const W = 480, H = 260;
const cy = 130;
// Картина: q1 слева, q2 справа. Минимальное расстояние пикселями для наглядности
const minPx = 90, maxPx = 360;
// Линейно: r=0.05 -> minPx, r=0.5 -> maxPx
const pxR = minPx + (r - 0.05) / (0.5 - 0.05) * (maxPx - minPx);
const cx1 = (W - pxR) / 2;
const cx2 = cx1 + pxR;
const radius = 22;
let g = '';
// Линия расстояния
g += '<line x1="' + cx1 + '" y1="' + (cy - 50) + '" x2="' + cx2 + '" y2="' + (cy - 50) + '" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="4 3"/>';
g += '<line x1="' + cx1 + '" y1="' + (cy - 55) + '" x2="' + cx1 + '" y2="' + (cy - 45) + '" stroke="#94a3b8" stroke-width="1.5"/>';
g += '<line x1="' + cx2 + '" y1="' + (cy - 55) + '" x2="' + cx2 + '" y2="' + (cy - 45) + '" stroke="#94a3b8" stroke-width="1.5"/>';
g += '<text x="' + ((cx1+cx2)/2) + '" y="' + (cy - 58) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0f172a">r = ' + r.toFixed(2) + ' м</text>';
// Заряды
g += PHYS.chargeMark(cx1, cy, q1 === 0 ? 1 : (q1 > 0 ? 1 : -1), radius, '');
if(q1 === 0){
g += '<circle cx="' + cx1 + '" cy="' + cy + '" r="' + radius + '" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="' + cx1 + '" y="' + (cy + 4) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
}
g += PHYS.chargeMark(cx2, cy, q2 === 0 ? 1 : (q2 > 0 ? 1 : -1), radius, '');
if(q2 === 0){
g += '<circle cx="' + cx2 + '" cy="' + cy + '" r="' + radius + '" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="' + cx2 + '" y="' + (cy + 4) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
}
// Подписи зарядов
g += '<text x="' + cx1 + '" y="' + (cy + radius + 22) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#0f172a">q₁ = ' + fmtQ(q1) + ' нКл</text>';
g += '<text x="' + cx2 + '" y="' + (cy + radius + 22) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#0f172a">q₂ = ' + fmtQ(q2) + ' нКл</text>';
// Расчёт силы (в Н). Заряды в нКл -> Кл
const qC1 = q1 * 1e-9, qC2 = q2 * 1e-9;
const F = k * Math.abs(qC1 * qC2) / (r * r);
// Преобразование в мН/мкН
let Fdisp, Funit;
if(F >= 1e-3){ Fdisp = (F * 1000).toFixed(2); Funit = 'мН'; }
else if(F >= 1e-6){ Fdisp = (F * 1e6).toFixed(2); Funit = 'мкН'; }
else { Fdisp = F.toExponential(2); Funit = 'Н'; }
// Стрелки силы
const prod = q1 * q2;
let kind;
if(prod === 0){
kind = 'Один из зарядов = 0: сила отсутствует';
} else {
const repulse = prod > 0;
kind = repulse ? '<b style="color:#dc2626">Одноимённые: отталкиваются</b>' : '<b style="color:#10b981">Разноимённые: притягиваются</b>';
// Длина стрелки пропорциональна F (нормируем)
const maxArrowLen = 60;
// Нормировка относительно F при q1=q2=10 нКл, r=0.05м
const Fref = k * (10e-9 * 10e-9) / (0.05 * 0.05);
let arrowLen = Math.min(maxArrowLen, Math.sqrt(F / Fref) * maxArrowLen);
arrowLen = Math.max(15, arrowLen);
if(repulse){
// q1 толкается влево, q2 — вправо
g += PHYS.drawArrow(cx1 - 4, cy, cx1 - 4 - arrowLen, cy, '#dc2626', 3, 11);
g += PHYS.drawArrow(cx2 + 4, cy, cx2 + 4 + arrowLen, cy, '#dc2626', 3, 11);
g += '<text x="' + (cx1 - 4 - arrowLen - 6) + '" y="' + (cy - 8) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#dc2626">F</text>';
g += '<text x="' + (cx2 + 4 + arrowLen + 6) + '" y="' + (cy - 8) + '" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#dc2626">F</text>';
} else {
// q1 тянется вправо, q2 — влево
g += PHYS.drawArrow(cx1 + 4, cy, cx1 + 4 + arrowLen, cy, '#10b981', 3, 11);
g += PHYS.drawArrow(cx2 - 4, cy, cx2 - 4 - arrowLen, cy, '#10b981', 3, 11);
g += '<text x="' + (cx1 + 4 + arrowLen + 6) + '" y="' + (cy - 8) + '" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#10b981">F</text>';
g += '<text x="' + (cx2 - 4 - arrowLen - 6) + '" y="' + (cy - 8) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#10b981">F</text>';
}
}
// Заголовок
g += '<text x="240" y="28" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Закон Кулона: F = k|q₁q₂|/r²</text>';
svg.innerHTML = g;
out.innerHTML = '<div>' + kind + '</div>'
+ '<div style="margin-top:6px"><b>Сила:</b> $F = k\\dfrac{|q_1 q_2|}{r^2} = ' + Fdisp + '$ ' + Funit + '</div>';
renderMath(out);
seen.add(q1.toFixed(1) + ':' + q2.toFixed(1) + ':' + r.toFixed(2));
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p17-iv1'); bumpProgress('p17', 15); }
}
q1S.addEventListener('input', render);
q2S.addEventListener('input', render);
rS.addEventListener('input', render);
render();
})();
/* IV2 — Калькулятор силы Кулона */
(function(){
const out = document.getElementById('p17-iv2-out');
const fb = document.getElementById('p17-iv2-fb');
const seen = new Set();
let _done = false;
const k = PHYS.CONST.k;
function calc(){
const q1 = parseFloat(document.getElementById('p17-iv2-q1').value);
const q2 = parseFloat(document.getElementById('p17-iv2-q2').value);
const r = parseFloat(document.getElementById('p17-iv2-r').value);
const u1 = parseFloat(document.getElementById('p17-iv2-u1').value);
const u2 = parseFloat(document.getElementById('p17-iv2-u2').value);
const ur = parseFloat(document.getElementById('p17-iv2-ur').value);
const eps = parseFloat(document.getElementById('p17-iv2-eps').value);
if(!isFinite(q1) || !isFinite(q2)){ feedback(fb, false, '&#10007; Введи заряды.'); return; }
if(!isFinite(r) || r <= 0){ feedback(fb, false, '&#10007; Расстояние должно быть положительным.'); return; }
const qC1 = q1 * u1, qC2 = q2 * u2, rM = r * ur;
const F = k * Math.abs(qC1 * qC2) / (eps * rM * rM);
// подача в подходящих единицах
let Fdisp, Funit;
if(F >= 1){ Fdisp = (+F.toFixed(3)); Funit = 'Н'; }
else if(F >= 1e-3){ Fdisp = (+(F*1000).toFixed(3)); Funit = 'мН'; }
else if(F >= 1e-6){ Fdisp = (+(F*1e6).toFixed(3)); Funit = 'мкН'; }
else { Fdisp = F.toExponential(3); Funit = 'Н'; }
const epsStr = eps === 1 ? '' : '\\varepsilon';
const epsForm = eps === 1 ? '' : (' \\cdot \\dfrac{1}{' + eps + '}');
out.innerHTML = '<div style="margin-bottom:6px"><b>В СИ:</b> $q_1 = ' + qC1.toExponential(2) + '$ Кл, $q_2 = ' + qC2.toExponential(2) + '$ Кл, $r = ' + rM + '$ м'+(eps===1?'':', $\\varepsilon = '+eps+'$')+'</div>'
+ '<div style="margin-bottom:6px"><b>Подстановка:</b> $F = \\dfrac{9 \\cdot 10^9 \\cdot |' + qC1.toExponential(1) + ' \\cdot ' + qC2.toExponential(1) + '|}{' + (eps===1?'':eps+' \\cdot ') + (rM*rM).toExponential(2) + '}$</div>'
+ '<div><b>Сила:</b> $F \\approx ' + Fdisp + '$ ' + Funit + '</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
seen.add(qC1.toExponential(1) + ':' + qC2.toExponential(1) + ':' + rM + ':' + eps);
if(!_done && seen.size >= 3){ _done = true; addXp(10, 'p17-iv2'); bumpProgress('p17', 15); }
}
document.getElementById('p17-iv2-go').addEventListener('click', calc);
})();
/* IV3 — квикфайр «Как изменится сила?» */
(function(){
// 4 опции на каждый вопрос
const OPTS = ['Увеличится в 2 раза', 'Уменьшится в 2 раза', 'Увеличится в 4 раза', 'Уменьшится в 4 раза'];
const Q = [
{ q:'Если расстояние $r$ удвоить (заряды без изменений) — как изменится $F$?', ans:3, why:'$F \\propto 1/r^2$; при $r\\to 2r$ сила уменьшается в $4$ раза.' },
{ q:'Если заряд $q_1$ удвоить (остальное без изменений) — как изменится $F$?', ans:0, why:'$F \\propto q_1$; сила увеличится в $2$ раза.' },
{ q:'Если оба заряда $q_1, q_2$ удвоить (расстояние без изменений) — как изменится $F$?', ans:2, why:'$F \\propto q_1 q_2$; при $q_1\\to 2q_1$ и $q_2\\to 2q_2$ сила увеличится в $4$ раза.' },
{ q:'Если расстояние $r$ уменьшить в $2$ раза — как изменится $F$?', ans:2, why:'$F \\propto 1/r^2$; при $r\\to r/2$ сила увеличится в $4$ раза.' },
{ q:'Если заряды поместить в среду с $\\varepsilon = 2$ (вместо вакуума) — как изменится $F$?', ans:1, why:'$F = k|q_1q_2|/(\\varepsilon r^2)$; при $\\varepsilon = 2$ сила уменьшится в $2$ раза.' },
{ q:'Если один из зарядов удвоить И расстояние удвоить — как изменится $F$?', ans:1, why:'$F \\propto q/r^2$; множитель $2/4 = 1/2$ — сила уменьшится в $2$ раза.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p17-iv3-q');
const oEl = document.getElementById('p17-iv3-opts');
const fb = document.getElementById('p17-iv3-fb');
const iEl = document.getElementById('p17-iv3-i');
const sEl = document.getElementById('p17-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p17-iv3'); bumpProgress('p17', 25); }
else if(score >= 4){ addXp(8, 'p17-iv3'); bumpProgress('p17', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = OPTS.map((t, k) => '<button class="btn primary" data-v="' + k + '">' + t + '</button>').join('');
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p17-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр Кулона */
(function(){
const Q = [
{ q:'Два заряда по $+1$ мкКл на расстоянии $0{,}1$ м (в вакууме). Сила в Н?', ans:0.9, tol:0.05, hint:'$F = 9\\cdot10^9 \\cdot (10^{-6})^2/(0{,}1)^2 = 0{,}9$ Н.' },
{ q:'Заряды $+2$ нКл и $-3$ нКл на $r = 0{,}06$ м. Сила в мН?', ans:15, tol:1, hint:'$F = 9\\cdot10^9 \\cdot 6\\cdot10^{-18}/0{,}0036 = 0{,}015$ Н $= 15$ мН.' },
{ q:'При $r = 1$ м сила Кулона $F_0$. При каком $r$ (м) сила станет в $4$ раза меньше?', ans:2, tol:0.1, hint:'$F \\propto 1/r^2$; чтобы $F\\to F_0/4$, нужно $r \\to 2r_0 = 2$ м.' },
{ q:'В воде ($\\varepsilon = 81$) сила Кулона во сколько раз меньше, чем в вакууме (при тех же зарядах и $r$)?', ans:81, tol:2, hint:'$F = k|q_1q_2|/(\\varepsilon r^2)$ — делится на $\\varepsilon = 81$.' },
{ q:'Два электрона на $r = 10^{-10}$ м. Сила в Н? Введи мантиссу при $10^{-8}$.', ans:2.3, tol:0.3, hint:'$F = 9\\cdot10^9 \\cdot (1{,}6\\cdot10^{-19})^2/(10^{-10})^2 \\approx 2{,}3 \\cdot 10^{-8}$ Н.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p17-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p17-iv4'); bumpProgress('p17', 25); }
else if(score >= 3){ addXp(8, 'p17-iv4'); bumpProgress('p17', 15); }
return;
}
document.getElementById('p17-iv4-i').textContent = (i+1);
document.getElementById('p17-iv4-s').textContent = score;
document.getElementById('p17-iv4-q').innerHTML = Q[i].q;
document.getElementById('p17-iv4-ans').value = '';
renderMath(document.getElementById('p17-iv4-q'));
document.getElementById('p17-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p17-iv4-fb');
const raw = document.getElementById('p17-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p17-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p17-iv4-go').addEventListener('click', go);
document.getElementById('p17-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p17-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p17');
}
function build_p18(){
const box = document.getElementById('p18-body');
let html = '';
/* THEORY 1 — Понятие поля */
html += makeCard('theory', "Электростатическое поле", "§18", `
<p><b>Электростатическое поле</b> — особый вид материи, который существует вокруг электрических зарядов и проявляется в действии на другие заряды.</p>
<p>До открытия поля физики верили в <b>«дальнодействие»</b> — мгновенное действие на расстоянии. Однако опыты с движущимися зарядами показали, что взаимодействие распространяется с <b>конечной скоростью</b> (скоростью света). Носителем этого взаимодействия является <b>электромагнитное поле</b>.</p>
<p><b>Электростатическое</b> поле создаётся <b>неподвижными</b> зарядами и не меняется со временем.</p>
<p>Подобно тому, как магнит создаёт магнитное поле, а Земля создаёт гравитационное поле, электрический заряд создаёт <b>электростатическое поле</b>.</p>
`);
/* THEORY 2 — Свойства поля */
html += makeCard('rule', "Свойства электростатического поля", "§18", `
<p><b>Свойства электростатического поля</b>:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li><b>Создаётся</b> электрическим зарядом (источником).</li>
<li><b>Действует</b> только на электрические заряды (помещённые в поле).</li>
<li><b>Невидимо</b>, обнаруживается по силам, действующим на заряды.</li>
<li><b>Передаётся</b> с конечной скоростью (в пустоте — со скоростью света $c = 3 \\cdot 10^8$ м/с).</li>
<li><b>Подчиняется принципу суперпозиции</b>: поля складываются векторно.</li>
</ul>
<p>Поля могут создаваться:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Одним точечным зарядом.</li>
<li>Системой точечных зарядов.</li>
<li>Заряженными телами (заряд распределён по поверхности или объёму).</li>
</ul>
<p style="padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">В этом параграфе мы изучим <b>электростатическое</b> поле — поле <b>неподвижных</b> зарядов. Его характеристики не зависят от времени.</p>
`);
/* THEORY 3 — Пробный заряд и источник */
html += makeCard('example', "Пробный заряд и источник", "§18", `
<p><b>Пробный заряд</b> $q_{пр}$ — это очень маленький положительный заряд, который вносится в исследуемую точку поля для измерения характеристик поля.</p>
<p><b>Условия для пробного заряда</b>:</p>
<ol style="margin:6px 0 8px 22px;line-height:1.75">
<li>Должен быть <b>малым</b>, чтобы своим полем не искажать исходное поле.</li>
<li>По общему соглашению — <b>положительным</b>, чтобы направление силы совпадало с направлением поля.</li>
</ol>
<p><b>Источник поля</b> — заряд (или система зарядов), создающий поле.</p>
<p style="padding:10px 14px;background:var(--warn-bg,#fef3c7);border-left:4px solid var(--warn,#f59e0b);border-radius:9px;margin:10px 0"><b>Аналогия</b>: чтобы измерить силу ветра, мы выставляем флюгер (пробный объект). Так же, чтобы измерить поле, мы помещаем в точку пробный заряд и смотрим, какая сила на него действует.</p>
<p>Поле существует <b>независимо</b> от того, есть в точке пробный заряд или нет. Поле — это <b>объективная физическая реальность</b>.</p>
`);
/* INTERACTIVE 1 — Визуализатор электростатического поля */
html += `<div class="wg" id="p18-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Визуализатор электростатического поля</div></div>
<div class="wg-help">Меняй знак и величину заряда — увидишь линии поля. Добавь пробный заряд — увидишь силу, действующую на него.</div>
<div class="sliders">
<label>$q$: <b id="p18-iv1-qL">+5</b> нКл <input type="range" id="p18-iv1-q" min="-10" max="10" value="5" step="1"></label>
<label>пробных зарядов: <b id="p18-iv1-nL">2</b> <input type="range" id="p18-iv1-n" min="0" max="8" value="2" step="1"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p18-iv1-svg" viewBox="0 0 380 260" width="100%" style="height:auto"></svg>
</div>
<div id="p18-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Понятие поля */
html += `<div class="wg" id="p18-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Понятие поля</div></div>
<div class="wg-help">5 вопросов о природе и свойствах электростатического поля.</div>
<div class="score-display"><span>Вопрос <b id="p18-iv2-i">1</b> / 5</span><span>Очки: <b id="p18-iv2-s">0</b> / 5</span></div>
<div id="p18-iv2-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p18-iv2-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p18-iv2-fb"></div>
<div class="actions"><button class="btn" id="p18-iv2-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 3 — Существует ли поле? */
html += `<div class="wg" id="p18-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Существует ли поле?</div></div>
<div class="wg-help">6 ситуаций — есть ли в указанной точке электростатическое поле?</div>
<div class="score-display"><span>Задача <b id="p18-iv3-i">1</b> / 6</span><span>Очки: <b id="p18-iv3-s">0</b> / 6</span></div>
<div id="p18-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p18-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p18-iv3-fb"></div>
<div class="actions"><button class="btn" id="p18-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Кратко: что такое поле */
html += `<div class="wg" id="p18-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Кратко: что такое поле</div></div>
<div class="wg-help">4 быстрых вопроса. Выбери номер правильного варианта.</div>
<div class="score-display"><span>Вопрос <b id="p18-iv4-i">1</b> / 4</span><span>Очки: <b id="p18-iv4-s">0</b> / 4</span></div>
<div id="p18-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p18-iv4-opts" style="display:grid;grid-template-columns:1fr;gap:6px"></div>
<div class="feedback" id="p18-iv4-fb"></div>
<div class="actions"><button class="btn" id="p18-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p17', 'p19');
html += readButton('p18');
box.innerHTML = html;
renderMath(box);
/* IV1 — Визуализатор электростатического поля */
(function(){
const svg = document.getElementById('p18-iv1-svg');
const qS = document.getElementById('p18-iv1-q');
const nS = document.getElementById('p18-iv1-n');
const qL = document.getElementById('p18-iv1-qL');
const nL = document.getElementById('p18-iv1-nL');
const out = document.getElementById('p18-iv1-out');
const seen = new Set();
let _done = false;
const k = PHYS.CONST.k;
function fmtQ(q){ return (q>=0?'+':'') + q; }
function render(){
const q = +qS.value, n = +nS.value;
qL.textContent = fmtQ(q); nL.textContent = n;
const W = 380, H = 260, cx = W/2, cy = H/2;
let g = '';
// Заголовок
g += '<text x="190" y="20" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Электростатическое поле точечного заряда</text>';
// Линии поля (если q != 0)
if(q !== 0){
g += PHYS.fieldLinesPointCharge(cx, cy, q > 0 ? 1 : -1, 95, 16);
}
// Сам заряд
if(q === 0){
g += '<circle cx="'+cx+'" cy="'+cy+'" r="20" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="'+cx+'" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
} else {
g += PHYS.chargeMark(cx, cy, q > 0 ? 1 : -1, 20, '');
}
// Подпись
g += '<text x="'+cx+'" y="'+(cy+44)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#0f172a">q = '+fmtQ(q)+' нКл</text>';
// Пробные заряды (по окружности r=130)
if(n > 0 && q !== 0){
const Rp = 115;
const qpr = 1e-9; // 1 нКл пробный
for(let i = 0; i < n; i++){
const a = 2*Math.PI*i/n + 0.15;
const px = cx + Rp*Math.cos(a);
const py = cy + Rp*Math.sin(a);
// Пробный заряд: маленький красный крестик
g += '<circle cx="'+px.toFixed(1)+'" cy="'+py.toFixed(1)+'" r="5" fill="#fecaca" stroke="#dc2626" stroke-width="1.5"/>';
g += '<line x1="'+(px-3).toFixed(1)+'" y1="'+py.toFixed(1)+'" x2="'+(px+3).toFixed(1)+'" y2="'+py.toFixed(1)+'" stroke="#dc2626" stroke-width="1.5"/>';
g += '<line x1="'+px.toFixed(1)+'" y1="'+(py-3).toFixed(1)+'" x2="'+px.toFixed(1)+'" y2="'+(py+3).toFixed(1)+'" stroke="#dc2626" stroke-width="1.5"/>';
// Стрелка силы: от заряда (если q>0) или к заряду (если q<0)
const dirx = Math.cos(a), diry = Math.sin(a);
const L = 22;
const sign = q > 0 ? 1 : -1;
// F = qE. Если q>0 (источник), пробный отталкивается — стрелка от центра
const sx = px + sign * 9 * dirx;
const sy = py + sign * 9 * diry;
const ex = px + sign * (9 + L) * dirx;
const ey = py + sign * (9 + L) * diry;
g += PHYS.drawArrow(sx, sy, ex, ey, '#ea580c', 2, 8);
}
}
svg.innerHTML = g;
// Описание
let txt = '';
if(q === 0){
txt = '<b>Заряд = 0</b>: поля нет.';
} else if(q > 0){
txt = '<b>Положительный заряд</b>: линии поля направлены <b>от заряда</b>. На пробный $+$ заряд действует сила <b>отталкивания</b>.';
} else {
txt = '<b>Отрицательный заряд</b>: линии поля направлены <b>к заряду</b>. На пробный $+$ заряд действует сила <b>притяжения</b>.';
}
out.innerHTML = txt;
renderMath(out);
seen.add(q+':'+n);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p18-iv1'); bumpProgress('p18', 15); }
}
qS.addEventListener('input', render);
nS.addEventListener('input', render);
render();
})();
/* IV2 — Понятие поля */
(function(){
const Q = [
{ q:'Электростатическое поле существует, только когда в точке есть пробный заряд?', opts:['Да','Нет'], ans:1, why:'Поле существует независимо от пробного заряда — это объективная физическая реальность.' },
{ q:'Источник электростатического поля — это:', opts:['Электрический заряд','Магнит'], ans:0, why:'Электростатическое поле создаётся электрическими зарядами. Магнит создаёт магнитное поле.' },
{ q:'Пробный заряд по соглашению — какого знака?', opts:['Положительный','Отрицательный'], ans:0, why:'Пробный заряд берут положительным, чтобы направление силы совпадало с направлением поля.' },
{ q:'Электростатическое поле создаётся:', opts:['Неподвижными зарядами','Движущимися зарядами'], ans:0, why:'Именно неподвижные заряды создают <b>электростатическое</b> поле. Движущиеся создают магнитное.' },
{ q:'Через что передаётся действие заряда на другой заряд?', opts:['Через пустоту','Через поле'], ans:1, why:'Носителем взаимодействия является поле. «Дальнодействие через пустоту» — устаревшее представление.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p18-iv2-q');
const oEl = document.getElementById('p18-iv2-opts');
const fb = document.getElementById('p18-iv2-fb');
const iEl = document.getElementById('p18-iv2-i');
const sEl = document.getElementById('p18-iv2-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p18-iv2'); bumpProgress('p18', 25); }
else if(score >= 3){ addXp(8, 'p18-iv2'); bumpProgress('p18', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = item.opts.map((t, k) => '<button class="btn primary" data-v="' + k + '">' + t + '</button>').join('');
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p18-iv2-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV3 — Существует ли поле? */
(function(){
const OPTS = ['Существует', 'Не существует (E = 0)'];
const Q = [
{ q:'В точке возле положительно заряженного шара.', ans:0, why:'Заряженное тело создаёт поле вокруг себя.' },
{ q:'Вокруг нейтрального тела (суммарный заряд = 0).', ans:1, why:'Нет заряда — нет поля. (В реальности есть микрополе, но в школьной модели — нет.)' },
{ q:'Внутри однородно заряженной сферической оболочки.', ans:1, why:'По теореме Гаусса: внутри полой однородно заряженной сферы поле равно нулю.' },
{ q:'В точке возле отдельного электрона.', ans:0, why:'Электрон — заряженная частица, вокруг него всегда есть поле.' },
{ q:'В точке посередине между двумя равными одноимёнными зарядами.', ans:1, why:'Поля от двух зарядов равны по модулю и противоположны по направлению — взаимно компенсируются.' },
{ q:'В вакууме вдали от любых зарядов.', ans:1, why:'Без источников электростатического поля нет.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p18-iv3-q');
const oEl = document.getElementById('p18-iv3-opts');
const fb = document.getElementById('p18-iv3-fb');
const iEl = document.getElementById('p18-iv3-i');
const sEl = document.getElementById('p18-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p18-iv3'); bumpProgress('p18', 25); }
else if(score >= 4){ addXp(8, 'p18-iv3'); bumpProgress('p18', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = OPTS.map((t, k) => '<button class="btn primary" data-v="' + k + '">' + t + '</button>').join('');
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p18-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Кратко: что такое поле */
(function(){
const Q = [
{ q:'Электростатическое поле создаётся:', opts:['1. Электрическим зарядом', '2. Массой тела', '3. Течением времени'], ans:0, why:'Источник поля — электрический заряд.' },
{ q:'Электростатическое поле создают:', opts:['1. Неподвижные заряды', '2. Движущиеся заряды', '3. Любые заряды'], ans:0, why:'Именно <b>неподвижные</b> заряды создают электростатическое поле.' },
{ q:'Пробный заряд — это:', opts:['1. Положительный и большой', '2. Положительный и малый', '3. Любого знака и любой величины'], ans:1, why:'Малый — чтобы не искажать поле; положительный — для соответствия направления силы и поля.' },
{ q:'Действие электростатического поля передаётся:', opts:['1. Мгновенно', '2. Со скоростью света', '3. Со скоростью звука'], ans:1, why:'В вакууме электромагнитное взаимодействие распространяется со скоростью света $c \\approx 3 \\cdot 10^8$ м/с.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p18-iv4-q');
const oEl = document.getElementById('p18-iv4-opts');
const fb = document.getElementById('p18-iv4-fb');
const iEl = document.getElementById('p18-iv4-i');
const sEl = document.getElementById('p18-iv4-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p18-iv4'); bumpProgress('p18', 25); }
else if(score >= 2){ addXp(8, 'p18-iv4'); bumpProgress('p18', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = item.opts.map((t, k) => '<button class="btn primary" data-v="' + k + '">' + t + '</button>').join('');
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p18-iv4-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p18');
}
function build_p19(){
const box = document.getElementById('p19-body');
let html = '';
/* THEORY 1 — Определение напряжённости */
html += makeCard('theory', "Напряжённость электростатического поля", "§19", `
<p><b>Напряжённость электростатического поля</b> $\\vec{E}$ — векторная физическая величина, равная отношению силы, действующей на пробный заряд, к величине этого заряда:</p>
<p style="text-align:center;margin:10px 0">$$\\vec{E} = \\dfrac{\\vec{F}}{q_{пр}}$$</p>
<p>где:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$\\vec{E}$ — напряжённость поля в данной точке (Н/Кл);</li>
<li>$\\vec{F}$ — сила, действующая на пробный заряд (Н);</li>
<li>$q_{пр}$ — пробный заряд (Кл).</li>
</ul>
<p><b>Единица измерения</b>: $1$ Н/Кл $= 1$ В/м (вольт на метр).</p>
<p style="margin-top:10px"><b>Сила на заряд</b> в поле напряжённостью $\\vec{E}$:</p>
<p style="text-align:center;margin:10px 0">$$\\vec{F} = q \\vec{E}$$</p>
<p><b>Направление</b>:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Для $q > 0$: $\\vec{F}$ совпадает с $\\vec{E}$ по направлению.</li>
<li>Для $q < 0$: $\\vec{F}$ противоположна $\\vec{E}$.</li>
</ul>
`);
/* THEORY 2 — Поле точечного заряда */
html += makeCard('rule', "Напряжённость поля точечного заряда", "§19", `
<p>Модуль напряжённости поля <b>точечного заряда</b> $q$ на расстоянии $r$ от него:</p>
<p style="text-align:center;margin:10px 0">$$E = k\\,\\dfrac{|q|}{r^2}$$</p>
<p>где $k = 9 \\cdot 10^9$ Н$\\cdot$м$^2$/Кл$^2$ — постоянная Кулона.</p>
<p><b>Направление</b>:</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Поле положительного заряда направлено <b>от</b> заряда (наружу).</li>
<li>Поле отрицательного заряда направлено <b>к</b> заряду (внутрь).</li>
</ul>
<p>В среде с диэлектрической проницаемостью $\\varepsilon$:</p>
<p style="text-align:center;margin:10px 0">$$E = k\\,\\dfrac{|q|}{\\varepsilon\\, r^2}$$</p>
<p style="padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0"><b>Пример</b>. Точечный заряд $q = 1$ нКл. Напряжённость на расстоянии $r = 0{,}1$ м:
$E = 9 \\cdot 10^9 \\cdot \\dfrac{10^{-9}}{(0{,}1)^2} = \\dfrac{9}{0{,}01} = 900$ В/м.</p>
`);
/* THEORY 3 — Принцип суперпозиции */
html += makeCard('example', "Принцип суперпозиции полей", "§19", `
<p><b>Принцип суперпозиции</b>: если поле создано несколькими зарядами, то напряжённость суммарного поля равна <b>векторной сумме</b> напряжённостей полей от каждого заряда отдельно:</p>
<p style="text-align:center;margin:10px 0">$$\\vec{E} = \\vec{E_1} + \\vec{E_2} + \\vec{E_3} + \\ldots$$</p>
<p>Это <b>векторная</b> сумма — учитываем направления каждого $\\vec{E_i}$.</p>
<p style="margin-top:12px"><b>Пример 1.</b> Два заряда по $+q$ на расстоянии $2d$. Найти $E$ в точке посередине.</p>
<p>$E_1$ от первого и $E_2$ от второго равны по модулю, но направлены <b>в разные стороны</b> $\\Rightarrow$ $E = 0$. Это <b>точка равновесия</b>.</p>
<p style="margin-top:10px"><b>Пример 2.</b> Два заряда $+q$ и $-q$ на расстоянии $2d$ (диполь). В точке посередине $E_1$ и $E_2$ направлены <b>в одну сторону</b> (от $+q$ к $-q$):</p>
<p style="text-align:center;margin:10px 0">$$E = 2 \\cdot k\\dfrac{|q|}{d^2}$$</p>
<p style="padding:10px 14px;background:var(--warn-bg,#fef3c7);border-left:4px solid var(--warn,#f59e0b);border-radius:9px;margin:10px 0"><b>Однородное поле</b> — поле, в котором $\\vec{E}$ одинакова во всех точках (по модулю и направлению). Создаётся, например, между параллельными пластинами заряженного конденсатора. Сила $\\vec{F} = q\\vec{E}$ на заряд везде одинакова.</p>
`);
/* INTERACTIVE 1 — Напряжённость поля точечного заряда */
html += `<div class="wg" id="p19-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Напряжённость поля точечного заряда</div></div>
<div class="wg-help">Меняй $q$ и $r$ — стрелки $\\vec{E}$ в 8 точках вокруг заряда покажут направление и величину поля.</div>
<div class="sliders">
<label>$q$: <b id="p19-iv1-qL">+5</b> нКл <input type="range" id="p19-iv1-q" min="-10" max="10" value="5" step="1"></label>
<label>$r$: <b id="p19-iv1-rL">0.20</b> м <input type="range" id="p19-iv1-r" min="0.05" max="0.5" value="0.2" step="0.01"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p19-iv1-svg" viewBox="0 0 480 320" width="100%" style="height:auto"></svg>
</div>
<div id="p19-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Принцип суперпозиции: 2 заряда */
html += `<div class="wg" id="p19-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Принцип суперпозиции: 2 заряда</div></div>
<div class="wg-help">Заряды $q_1$ слева, $q_2$ справа на расстоянии $0{,}3$ м. Пробная точка между ними. $\\vec{E}$ от каждого — коллинеарны.</div>
<div class="sliders">
<label>$q_1$: <b id="p19-iv2-q1L">+5</b> нКл <input type="range" id="p19-iv2-q1" min="-10" max="10" value="5" step="1"></label>
<label>$q_2$: <b id="p19-iv2-q2L">+5</b> нКл <input type="range" id="p19-iv2-q2" min="-10" max="10" value="5" step="1"></label>
<label>$x$ от $q_1$: <b id="p19-iv2-xL">0.15</b> м <input type="range" id="p19-iv2-x" min="0.05" max="0.25" value="0.15" step="0.01"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p19-iv2-svg" viewBox="0 0 480 320" width="100%" style="height:auto"></svg>
</div>
<div id="p19-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 3 — Какое направление E? */
html += `<div class="wg" id="p19-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какое направление $\\vec{E}$?</div></div>
<div class="wg-help">6 ситуаций — куда направлен вектор напряжённости?</div>
<div class="score-display"><span>Задача <b id="p19-iv3-i">1</b> / 6</span><span>Очки: <b id="p19-iv3-s">0</b> / 6</span></div>
<div id="p19-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p19-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px"></div>
<div class="feedback" id="p19-iv3-fb"></div>
<div class="actions"><button class="btn" id="p19-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр напряжённости */
html += `<div class="wg" id="p19-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр напряжённости</div></div>
<div class="wg-help">5 задач. $k = 9 \\cdot 10^9$ Н$\\cdot$м$^2$/Кл$^2$. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p19-iv4-i">1</b> / 5</span><span>Очки: <b id="p19-iv4-s">0</b> / 5</span></div>
<div id="p19-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p19-iv4-ans" class="tinp" style="width:140px;text-align:center" step="any">
<button class="btn primary" id="p19-iv4-go">Проверить</button>
<button class="btn" id="p19-iv4-start">Заново</button>
</div>
<div class="feedback" id="p19-iv4-fb"></div>
</div>`;
html += secNav('p18', 'p20');
html += readButton('p19');
box.innerHTML = html;
renderMath(box);
/* IV1 — Напряжённость точечного заряда (8 стрелок) */
(function(){
const svg = document.getElementById('p19-iv1-svg');
const qS = document.getElementById('p19-iv1-q');
const rS = document.getElementById('p19-iv1-r');
const qL = document.getElementById('p19-iv1-qL');
const rL = document.getElementById('p19-iv1-rL');
const out = document.getElementById('p19-iv1-out');
const seen = new Set();
let _done = false;
const k = PHYS.CONST.k;
function fmtQ(q){ return (q>=0?'+':'') + q; }
function render(){
const q = +qS.value, r = +rS.value;
qL.textContent = fmtQ(q); rL.textContent = r.toFixed(2);
const W = 480, H = 320, cx = W/2, cy = H/2;
let g = '';
g += '<text x="240" y="22" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">E = k|q|/r² — поле точечного заряда</text>';
// Расчёт E
const qC = q * 1e-9;
const E = (q === 0) ? 0 : k * Math.abs(qC) / (r * r);
// 8 точек вокруг заряда на пиксельном радиусе Rpx
const Rpx = 110;
// Длина стрелки пропорциональна E (нормируем). Erefer = k*10e-9/0.05²
const Eref = k * 10e-9 / (0.05*0.05);
let arrowLen = (q === 0) ? 0 : Math.min(55, Math.max(12, Math.sqrt(E / Eref) * 55));
if(q !== 0){
const sign = q > 0 ? 1 : -1;
for(let i = 0; i < 8; i++){
const a = 2*Math.PI*i/8;
const dx = Math.cos(a), dy = Math.sin(a);
const px = cx + Rpx * dx, py = cy + Rpx * dy;
// Если q > 0: от заряда (наружу). Если q < 0: к заряду (внутрь).
let sx, sy, ex, ey;
if(sign > 0){
sx = px; sy = py;
ex = px + arrowLen * dx; ey = py + arrowLen * dy;
} else {
sx = px + arrowLen * dx; sy = py + arrowLen * dy;
ex = px; ey = py;
}
g += PHYS.drawArrow(sx, sy, ex, ey, '#ea580c', 2.4, 9);
}
}
// Сам заряд
if(q === 0){
g += '<circle cx="'+cx+'" cy="'+cy+'" r="22" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="'+cx+'" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
} else {
g += PHYS.chargeMark(cx, cy, q > 0 ? 1 : -1, 22, '');
}
g += '<text x="'+cx+'" y="'+(cy+44)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#0f172a">q = '+fmtQ(q)+' нКл</text>';
// Окружность радиуса r (визуально Rpx)
g += '<circle cx="'+cx+'" cy="'+cy+'" r="'+Rpx+'" fill="none" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4 4"/>';
g += '<text x="'+(cx + Rpx + 8)+'" y="'+(cy - 6)+'" font-family="JetBrains Mono,monospace" font-size="12" fill="#64748b">r = '+r.toFixed(2)+' м</text>';
svg.innerHTML = g;
// Описание
let txt;
if(q === 0){
txt = '$E = 0$ — заряда нет.';
} else {
const Estr = E.toFixed(0);
const dir = q > 0 ? '<b>от заряда</b>' : '<b>к заряду</b>';
txt = '<b>$E = k\\dfrac{|q|}{r^2} = 9\\cdot10^9 \\cdot \\dfrac{'+Math.abs(q)+'\\cdot 10^{-9}}{('+r.toFixed(2)+')^2} \\approx '+Estr+'$ В/м</b><br>'
+ 'Направление: '+dir+'.';
}
out.innerHTML = txt;
renderMath(out);
seen.add(q+':'+r.toFixed(2));
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p19-iv1'); bumpProgress('p19', 15); }
}
qS.addEventListener('input', render);
rS.addEventListener('input', render);
render();
})();
/* IV2 — Принцип суперпозиции: 2 заряда */
(function(){
const svg = document.getElementById('p19-iv2-svg');
const q1S = document.getElementById('p19-iv2-q1');
const q2S = document.getElementById('p19-iv2-q2');
const xS = document.getElementById('p19-iv2-x');
const q1L = document.getElementById('p19-iv2-q1L');
const q2L = document.getElementById('p19-iv2-q2L');
const xL = document.getElementById('p19-iv2-xL');
const out = document.getElementById('p19-iv2-out');
const seen = new Set();
let _done = false;
const k = PHYS.CONST.k;
const D = 0.3; // м, фикс расстояние
function fmtQ(q){ return (q>=0?'+':'') + q; }
function render(){
const q1 = +q1S.value, q2 = +q2S.value, x = +xS.value;
q1L.textContent = fmtQ(q1); q2L.textContent = fmtQ(q2); xL.textContent = x.toFixed(2);
const W = 480, H = 320, cy = H/2;
// Заряды по горизонтали: q1 в xpx1, q2 в xpx2
const xpx1 = 70, xpx2 = 410;
const dpx = xpx2 - xpx1;
// Пробная точка
const ppx = xpx1 + (x / D) * dpx;
let g = '';
g += '<text x="240" y="22" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Суперпозиция: E = E₁ + E₂ (векторно)</text>';
// Линия
g += '<line x1="'+xpx1+'" y1="'+cy+'" x2="'+xpx2+'" y2="'+cy+'" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3 3"/>';
// Заряды
if(q1 === 0){
g += '<circle cx="'+xpx1+'" cy="'+cy+'" r="20" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="'+xpx1+'" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
} else g += PHYS.chargeMark(xpx1, cy, q1 > 0 ? 1 : -1, 20, '');
if(q2 === 0){
g += '<circle cx="'+xpx2+'" cy="'+cy+'" r="20" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2"/>';
g += '<text x="'+xpx2+'" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#64748b">0</text>';
} else g += PHYS.chargeMark(xpx2, cy, q2 > 0 ? 1 : -1, 20, '');
// Подписи зарядов
g += '<text x="'+xpx1+'" y="'+(cy+44)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0f172a">q₁ = '+fmtQ(q1)+' нКл</text>';
g += '<text x="'+xpx2+'" y="'+(cy+44)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0f172a">q₂ = '+fmtQ(q2)+' нКл</text>';
// Пробная точка
g += '<circle cx="'+ppx.toFixed(1)+'" cy="'+cy+'" r="4.5" fill="#fde047" stroke="#a16207" stroke-width="1.5"/>';
g += '<text x="'+ppx.toFixed(1)+'" y="'+(cy-20)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#a16207">P</text>';
// Расчёт E1, E2 в точке P. Направление по оси x.
// r1 = x, r2 = D - x
const r1 = x, r2 = D - x;
const qC1 = q1 * 1e-9, qC2 = q2 * 1e-9;
// E1: знак — куда направлено поле от q1 в точке P.
// Точка P справа от q1. Если q1>0, E1 направлено вправо (+). Если q1<0, влево (-).
const E1 = (r1 > 1e-6) ? k * qC1 / (r1*r1) : 0; // знак сохраняем
// E2: точка P слева от q2. Если q2>0, поле от q2 направлено влево (-). Если q2<0, вправо (+).
const E2 = (r2 > 1e-6) ? -k * qC2 / (r2*r2) : 0;
const Esum = E1 + E2;
// Стрелки E1, E2, Esum в точке P. Длина — по модулю (с насыщением).
function arr(E, color, yOffset, label){
if(Math.abs(E) < 1e-3) return '';
const Emax = 5000;
const L = Math.min(70, Math.max(12, Math.abs(E)/Emax * 70));
const dir = E > 0 ? 1 : -1;
const sx = ppx, sy = cy + yOffset;
const ex = ppx + dir * L, ey = cy + yOffset;
let s = PHYS.drawArrow(sx, sy, ex, ey, color, 2.4, 9);
s += '<text x="'+(ex + dir*8)+'" y="'+(ey + 4)+'" text-anchor="'+(dir>0?'start':'end')+'" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+color+'">'+label+'</text>';
return s;
}
g += arr(E1, '#2563eb', -60, 'E₁');
g += arr(E2, '#10b981', -90, 'E₂');
g += arr(Esum, '#dc2626', -30, 'E');
// Расстояния
g += '<text x="'+((xpx1+ppx)/2).toFixed(1)+'" y="'+(cy+62)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">r₁ = '+r1.toFixed(2)+' м</text>';
g += '<text x="'+((ppx+xpx2)/2).toFixed(1)+'" y="'+(cy+62)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">r₂ = '+r2.toFixed(2)+' м</text>';
svg.innerHTML = g;
function s(E){ return (E>=0?'+':'') + E.toFixed(0); }
out.innerHTML = '<div><span style="color:#2563eb"><b>$E_1 = '+s(E1)+'$ В/м</b></span> &nbsp;&nbsp; '
+ '<span style="color:#10b981"><b>$E_2 = '+s(E2)+'$ В/м</b></span> &nbsp;&nbsp; '
+ '<span style="color:#dc2626"><b>$E = '+s(Esum)+'$ В/м</b></span></div>'
+ '<div style="margin-top:6px;font-size:.88rem;color:#64748b">Знак $+$ — поле направлено вправо, $-$ — влево.</div>';
renderMath(out);
seen.add(q1+':'+q2+':'+x.toFixed(2));
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p19-iv2'); bumpProgress('p19', 15); }
}
q1S.addEventListener('input', render);
q2S.addEventListener('input', render);
xS.addEventListener('input', render);
render();
})();
/* IV3 — Какое направление E? */
(function(){
const OPTS = ['От заряда (от +)', 'К заряду (к )', '$E = 0$'];
const Q = [
{ q:'Поле положительного точечного заряда. Куда направлено $\\vec{E}$?', ans:0, why:'$\\vec{E}$ положительного заряда направлено наружу (от заряда).' },
{ q:'Поле отрицательного точечного заряда. Куда направлено $\\vec{E}$?', ans:1, why:'$\\vec{E}$ отрицательного заряда направлено к заряду (внутрь).' },
{ q:'Середина между двумя одинаковыми $+q$ зарядами.', ans:2, why:'Поля равны по модулю, противоположны по направлению — сумма ноль.' },
{ q:'Середина между двумя одинаковыми $-q$ зарядами.', ans:2, why:'Аналогично: оба поля направлены к своим зарядам, в середине компенсируются.' },
{ q:'Точка возле положительного протона ($+$).', ans:0, why:'От любого положительного заряда поле направлено наружу.' },
{ q:'Точка возле отрицательного электрона ($-$).', ans:1, why:'К любому отрицательному заряду поле направлено внутрь.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p19-iv3-q');
const oEl = document.getElementById('p19-iv3-opts');
const fb = document.getElementById('p19-iv3-fb');
const iEl = document.getElementById('p19-iv3-i');
const sEl = document.getElementById('p19-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p19-iv3'); bumpProgress('p19', 25); }
else if(score >= 4){ addXp(8, 'p19-iv3'); bumpProgress('p19', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = OPTS.map((t, k) => '<button class="btn primary" data-v="' + k + '">' + t + '</button>').join('');
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1700);
});
});
}
document.getElementById('p19-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр напряжённости */
(function(){
const Q = [
{ q:'Точечный заряд $+1$ нКл. Напряжённость $E$ (В/м) на $r = 0{,}1$ м?', ans:900, tol:30, hint:'$E = 9\\cdot10^9 \\cdot 10^{-9}/(0{,}1)^2 = 900$ В/м.' },
{ q:'Поле $E = 1000$ В/м действует на заряд $q = 2$ мкКл. Найди силу в мН.', ans:2, tol:0.1, hint:'$F = qE = 2\\cdot10^{-6} \\cdot 1000 = 2\\cdot10^{-3}$ Н $= 2$ мН.' },
{ q:'Точечный заряд $-2$ нКл. $E$ (В/м) на $r = 0{,}05$ м?', ans:7200, tol:100, hint:'$E = 9\\cdot10^9 \\cdot 2\\cdot10^{-9}/(0{,}05)^2 = 18/0{,}0025 = 7200$ В/м.' },
{ q:'Два заряда $+2$ нКл и $+2$ нКл на расстоянии $0{,}2$ м. $E$ (В/м) в середине отрезка?', ans:0, tol:0.1, hint:'Поля от одинаковых зарядов в середине направлены навстречу — суммарное $E = 0$.' },
{ q:'Два заряда $+2$ нКл и $-2$ нКл на расстоянии $0{,}2$ м. $E$ (В/м) в середине? (Поля складываются.)', ans:3600, tol:50, hint:'$E_1 = E_2 = 9\\cdot10^9 \\cdot 2\\cdot10^{-9}/(0{,}1)^2 = 1800$ В/м. Направлены в одну сторону (от $+$ к $-$) $\\Rightarrow E = 3600$ В/м.' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p19-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p19-iv4'); bumpProgress('p19', 25); }
else if(score >= 3){ addXp(8, 'p19-iv4'); bumpProgress('p19', 15); }
return;
}
document.getElementById('p19-iv4-i').textContent = (i+1);
document.getElementById('p19-iv4-s').textContent = score;
document.getElementById('p19-iv4-q').innerHTML = Q[i].q;
document.getElementById('p19-iv4-ans').value = '';
renderMath(document.getElementById('p19-iv4-q'));
document.getElementById('p19-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p19-iv4-fb');
const raw = document.getElementById('p19-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p19-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p19-iv4-go').addEventListener('click', go);
document.getElementById('p19-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p19-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p19');
}
function build_p20(){
const box = document.getElementById('p20-body');
let html = '';
html += makeCard('theory', "Линии напряжённости", "§20", `
<p><b>Линии напряжённости</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 3+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p19', 'p21');
html += readButton('p20');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p20');
}
function build_p21(){
const box = document.getElementById('p21-body');
let html = '';
html += makeCard('theory', "Работа поля. Потенциал", "§21", `
<p><b>Работа поля. Потенциал</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 3+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p20', 'p22');
html += readButton('p21');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p21');
}
function build_p22(){
const box = document.getElementById('p22-body');
let html = '';
html += makeCard('theory', "Разность потенциалов. Напряжение", "§22", `
<p><b>Разность потенциалов. Напряжение</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 3+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p21', 'p23');
html += readButton('p22');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p22');
}
function build_p23(){
const box = document.getElementById('p23-body');
let html = '';
html += makeCard('theory', "Конденсаторы", "§23", `
<p><b>Конденсаторы</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 3+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p22', 'p24');
html += readButton('p23');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p23');
}
function build_p24(){
const box = document.getElementById('p24-body');
let html = '';
html += makeCard('theory', "Энергия поля конденсатора", "§24", `
<p><b>Энергия поля конденсатора</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 3+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p23', 'final3');
html += readButton('p24');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p24');
}
function build_final3(){
const box = document.getElementById('final3-body');
let html = '';
html += makeCard('theory', "Финал главы 3", "★", `
<p><b>Финал главы 3</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 3+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p24', null);
html += readButton('final3');
box.innerHTML = html;
renderMath(box);
wireReadBtn('final3');
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
return arr;
})();
function initSearch(){
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
if(!modal||!inp||!out) return;
let cur=0,rows=[];
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'…':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
function close(){ modal.classList.remove('show'); }
btn&&btn.addEventListener('click',open);
modal.addEventListener('click',e=>{if(e.target===modal)close();});
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
/* === PHYS10 POLISH JS === */
(function(){
function bumpScore(el){
if(!el) return;
el.classList.remove('bump');
void el.offsetWidth;
el.classList.add('bump');
setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
}
window.__phys10BumpScore = bumpScore;
function observeScores(root){
root = root || document;
var nodes = root.querySelectorAll('.score-display b');
nodes.forEach(function(b){
if(b.__scoreObs) return;
b.__scoreObs = true;
var last = b.textContent;
try{
var mo = new MutationObserver(function(){
var nv = b.textContent;
if(nv !== last){ last = nv; bumpScore(b); }
});
mo.observe(b, {childList:true, characterData:true, subtree:true});
}catch(e){}
});
}
function rescanScores(){ try{ observeScores(document); }catch(e){} }
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
else rescanScores();
try{
var rootObs = new MutationObserver(function(muts){
var need = false;
for(var i=0;i<muts.length && !need;i++){
var m = muts[i];
for(var j=0;j<m.addedNodes.length;j++){
var n = m.addedNodes[j];
if(n.nodeType===1){
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
}
}
}
if(need) rescanScores();
});
rootObs.observe(document.body, {childList:true, subtree:true});
}catch(e){}
function refreshDoneMarks(){
try{
if(typeof STATE === 'undefined' || !STATE.progress) return;
document.querySelectorAll('.psel-card').forEach(function(c){
var id = c.dataset.id || c.dataset.progCard;
if(!id) return;
var pct = +STATE.progress[id] || 0;
if(!c.querySelector('.psel-done')){
var s = document.createElement('span');
s.className = 'psel-done';
s.setAttribute('title','Прочитано');
s.innerHTML = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
c.appendChild(s);
}
c.classList.toggle('done', pct >= 50);
});
}catch(e){}
}
try{
if(typeof window.refreshProgressUI === 'function'){
var _origRP = window.refreshProgressUI;
window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
}
}catch(e){}
setTimeout(refreshDoneMarks, 600);
setTimeout(refreshDoneMarks, 1800);
window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
document.addEventListener('click', function(e){
var card = e.target.closest && e.target.closest('.psel-card');
if(!card) return;
var id = card.dataset.id;
if(!id) return;
setTimeout(function(){
var sec = document.getElementById('sec-' + id);
if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
}, 60);
});
})();
</script>
</body>
</html>