Files
Learn_System/frontend/textbooks/physics_10_ch3.html
T

1637 lines
117 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 = '';
html += makeCard('theory', "Электростатическое поле", "§18", `
<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('p17', 'p19');
html += readButton('p18');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p18');
}
function build_p19(){
const box = document.getElementById('p19-body');
let html = '';
html += makeCard('theory', "Напряжённость поля. Принцип суперпозиции", "§19", `
<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('p18', 'p20');
html += readButton('p19');
box.innerHTML = html;
renderMath(box);
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>