Files
Learn_System/frontend/textbooks/physics_10_ch5.html
T

1645 lines
115 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 · Глава 5 · «Магнитное поле и ЭМИ»</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:#0891b2; --pri2:#0e7490; --pri-soft:#cffafe;
--acc:#22d3ee; --acc2:#0891b2; --acc-soft:#cffafe;
--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,#164e63 0%,#0891b2 55%,#22d3ee 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 5';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:'B';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-p27"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p28"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p29"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p30"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p31"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p32"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p33"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-final5"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.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 · Глава 5</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('p27')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 27</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-p27" class="sec" data-watermark="Эрстед"><div class="sec-header"><span class="sec-num">§ 27</span><h2 class="sec-h">Магнитное поле тока</h2></div><div id="p27-body"></div></section>
<section id="sec-p28" class="sec" data-watermark="B"><div class="sec-header"><span class="sec-num">§ 28</span><h2 class="sec-h">Индукция магнитного поля</h2></div><div id="p28-body"></div></section>
<section id="sec-p29" class="sec" data-watermark="Ампер"><div class="sec-header"><span class="sec-num">§ 29</span><h2 class="sec-h">Сила Ампера</h2></div><div id="p29-body"></div></section>
<section id="sec-p30" class="sec" data-watermark="Лоренц"><div class="sec-header"><span class="sec-num">§ 30</span><h2 class="sec-h">Сила Лоренца</h2></div><div id="p30-body"></div></section>
<section id="sec-p31" class="sec" data-watermark="&Phi;"><div class="sec-header"><span class="sec-num">§ 31</span><h2 class="sec-h">Магнитный поток. Электромагнитная индукция</h2></div><div id="p31-body"></div></section>
<section id="sec-p32" class="sec" data-watermark="Фарадей"><div class="sec-header"><span class="sec-num">§ 32</span><h2 class="sec-h">Правило Ленца. Закон Фарадея</h2></div><div id="p32-body"></div></section>
<section id="sec-p33" class="sec" data-watermark="L"><div class="sec-header"><span class="sec-num">§ 33</span><h2 class="sec-h">Самоиндукция</h2></div><div id="p33-body"></div></section>
<section id="sec-final5" class="sec" data-watermark="&#9733;"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#0891b2,#22d3ee)"></span><h2 class="sec-h">Финал главы</h2></div><div id="final5-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» · Глава 5 · «Магнитное поле и ЭМИ» · 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:'p27', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 8;
const _TB_SLUG = 'physics-10-ch5';
const PARAS = [
{ id:'p27', num:'\u00a7 27', name:"Магнитное поле тока", sub:'Опыт Эрстеда' },
{ id:'p28', num:'\u00a7 28', name:"Индукция магнитного поля", sub:'$\\vec{B}$' },
{ id:'p29', num:'\u00a7 29', name:"Сила Ампера", sub:'$F = BIL\\sin\\alpha$' },
{ id:'p30', num:'\u00a7 30', name:"Сила Лоренца", sub:'$F = qvB$' },
{ id:'p31', num:'\u00a7 31', name:"Магнитный поток. Электромагнитная индукция", sub:'$\\Phi = BS\\cos\\alpha$' },
{ id:'p32', num:'\u00a7 32', name:"Правило Ленца. Закон Фарадея", sub:'$\\mathcal{E}_i = -d\\Phi/dt$' },
{ id:'p33', num:'\u00a7 33', name:"Самоиндукция", sub:'$L$, $W_L = LI^2/2$' },
{ id:'final5', num:'\u2605', name:'Финал главы', sub:'Итоги \u00b7 боссы главы 5', 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:"Начало главы 5!",
p27_done:"Магнитное поле тока освоен!",
p28_done:"Индукция магнитного поля освоен!",
p29_done:"Сила Ампера освоен!",
p30_done:"Сила Лоренца освоен!",
p31_done:"Магнитный поток. Электромагнитная индукция освоен!",
p32_done:"Правило Ленца. Закон Фарадея освоен!",
p33_done:"Самоиндукция освоен!",
ch5_done:"Глава 5 пройдена!"
};
function loadProgress(){
try{
const s=localStorage.getItem('physics10_ch5_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics10_ch5_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_ch5_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics10_ch5_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-ch5-'+(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 = { p27:()=>build_p27(), p28:()=>build_p28(), p29:()=>build_p29(), p30:()=>build_p30(), p31:()=>build_p31(), p32:()=>build_p32(), p33:()=>build_p33(), final5:()=>build_final5() };
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 = {
p27:{title:"Шпаргалка §27",rows:[["Эрстед","ток отклоняет стрелку"],["Поле тока","вихревое, $\\vec{B}$"],["Правая рука","для проводника"]]},
p28:{title:"Шпаргалка §28",rows:[["$\\vec{B}$","индукция магн. поля — Тл"],["Линии","замкнутые, без начала/конца"],["Опр.","$B = F_{max}/(IL)$"]]},
p29:{title:"Шпаргалка §29",rows:[["$F_A = BIL\\sin\\alpha$","сила Ампера"],["Левая рука","для напр-я силы"],["$F_A \\bot \\vec{B}, \\vec{I}$",""]]},
p30:{title:"Шпаргалка §30",rows:[["$F_л = qvB\\sin\\alpha$","сила Лоренца"],["Радиус","$r = mv/(qB)$"],["$F_л \\bot \\vec{v}$","траектория — окружность/спираль"]]},
p31:{title:"Шпаргалка §31",rows:[["$\\Phi = BS\\cos\\alpha$","магн. поток — Вб"],["ЭМИ","при $\\Delta\\Phi \\ne 0$ возникает $\\mathcal{E}_i$"],["Опыт Фарадея",""]]},
p32:{title:"Шпаргалка §32",rows:[["Ленц","$I_{инд}$ противодействует причине"],["$\\mathcal{E}_i = -d\\Phi/dt$","закон Фарадея"],["Знак","определяет Ленц"]]},
p33:{title:"Шпаргалка §33",rows:[["$\\mathcal{E}_{si} = -L\\dfrac{dI}{dt}$","самоиндукция"],["$L$","индуктивность — Гн"],["$W_L = LI^2/2$","энергия магн. поля"]]},
final5:{title:"Финал главы 5",rows:[["§§2733","теория главы 5"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p27',html:"Опыт Эрстеда показал: ток создаёт магн. поле. Линии $\\vec{B}$ вокруг тока — концентр. окружности."},
{sec:'p28',html:"$\\vec{B}$ — индукция магн. поля, измер. в Тл. Линии замкнуты (магн. поле — вихревое)."},
{sec:'p29',html:"Сила Ампера: $F_A = BIL\\sin\\alpha$. Направление — по правилу левой руки."},
{sec:'p30',html:"Сила Лоренца: $F_л = qvB\\sin\\alpha$. Заряд движется по окружности с $r = mv/(qB)$."},
{sec:'p31',html:"Магн. поток: $\\Phi = BS\\cos\\alpha$. Измеряется в Вб. ЭМИ возникает при $\\Delta\\Phi \\ne 0$."},
{sec:'p32',html:"Закон Фарадея: $\\mathcal{E}_i = -\\dfrac{d\\Phi}{dt}$. Правило Ленца: $I_{инд}$ противодействует $\\Delta\\Phi$."},
{sec:'p33',html:"Самоиндукция: $\\mathcal{E}_{si} = -L\\dfrac{dI}{dt}$. Энергия магн. поля катушки: $W_L = LI^2/2$."},
{sec:'final5',html:"Финал главы 5 — интегрированные задачи по §§27–33. В разработке (Phase 5+)."}
];
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_ch5_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_ch5_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 = {p27:'\xA727',p28:'\xA728',p29:'\xA729',p30:'\xA730',p31:'\xA731',p32:'\xA732',p33:'\xA733',final5:'Финал'};
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_p27(){
const box = document.getElementById('p27-body');
let html = '';
/* THEORY 1 — Магнитное поле и опыт Эрстеда */
html += makeCard('theory', "Магнитное поле и опыт Эрстеда", "§27", `
<p><b>Магнитное поле</b> — особый вид материи, возникающий вокруг движущихся электрических зарядов и магнитов. Действует на другие движущиеся заряды и магниты.</p>
<p style="margin-top:10px"><b>Открытие связи электричества и магнетизма.</b> В 1820 году датский физик обнаружил, что магнитная стрелка возле проводника с током <b>отклоняется</b>. Это доказало, что электрический ток создаёт магнитное поле.</p>
<p style="margin-top:10px">До этого опыта электричество и магнетизм считались разными явлениями. После — стало ясно, что это <b>разные проявления одного электромагнитного взаимодействия</b>.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Главная мысль.</b> Магнитное поле <b>создаётся движущимися зарядами</b> (током, движущимися частицами, постоянными магнитами) и <b>действует на движущиеся заряды</b> и магниты.</p>
`);
/* THEORY 2 — Источники и свойства поля */
html += makeCard('rule', "Источники и свойства поля", "§27", `
<p><b>Источники магнитного поля:</b></p>
<ul style="margin:6px 0 6px 22px">
<li><b>Постоянные магниты</b> (магнетит, ферриты, неодимовые магниты).</li>
<li><b>Проводники с током</b> (любые).</li>
<li><b>Движущиеся заряженные частицы</b>.</li>
<li><b>Земля</b> — гигантский магнит, создающий геомагнитное поле.</li>
</ul>
<p style="margin-top:10px"><b>Главные свойства:</b></p>
<ul style="margin:6px 0 6px 22px">
<li>Поле — <b>векторная</b> характеристика, обозначается $\\vec{B}$ — <b>магнитная индукция</b>.</li>
<li>Линии поля <b>замкнуты</b> (нет «магнитных зарядов» — северного или южного по отдельности).</li>
<li>Поле действует <b>только</b> на движущиеся заряды и магниты.</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Сравни.</b> Электрическое поле создают <b>любые</b> заряды (даже неподвижные). Магнитное — только <b>движущиеся</b>.</p>
`);
/* THEORY 3 — Взаимодействие проводников с током */
html += makeCard('example', "Взаимодействие двух проводников с током", "§27", `
<p><b>Опыт Ампера</b> (1820): два параллельных проводника с током <b>взаимодействуют</b>.</p>
<ul style="margin:6px 0 6px 22px">
<li><b>Параллельные токи</b> (в одном направлении) — <b>притягиваются</b>.</li>
<li><b>Антипараллельные токи</b> (в разных направлениях) — <b>отталкиваются</b>.</li>
</ul>
<p style="margin-top:10px">Это <b>противоположно</b> зарядам! (Одноимённые заряды отталкиваются, одноимённо направленные токи — притягиваются.)</p>
<p style="margin-top:10px"><b>Объяснение.</b> Первый проводник создаёт магнитное поле в области второго; это поле действует с силой на ток во втором проводнике. Эта сила называется <b>силой Ампера</b> (§29).</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Определение Ампера через силу</b> (СИ): $1$ А — сила тока, при которой два параллельных проводника длиной $1$ м, расположенные на расстоянии $1$ м, взаимодействуют с силой $F = 2 \\cdot 10^{-7}$ Н.</p>
`);
/* INTERACTIVE 1 — Опыт Эрстеда */
html += `<div class="wg" id="p27-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Опыт Эрстеда: ток отклоняет стрелку</div></div>
<div class="wg-help">Переключай ток в проводнике и смотри, как ведёт себя магнитная стрелка под ним. Без тока — стрелка показывает на север.</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:10px;flex-wrap:wrap">
<button class="btn" id="p27-iv1-off">Ток выкл.</button>
<button class="btn primary" id="p27-iv1-right">Ток вправо</button>
<button class="btn" id="p27-iv1-left">Ток влево</button>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p27-iv1-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p27-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.65;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Взаимодействие проводников */
html += `<div class="wg" id="p27-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Два проводника: притяжение или отталкивание?</div></div>
<div class="wg-help">Переключай направление тока в каждом проводнике. Параллельные токи притягиваются, антипараллельные — отталкиваются.</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px">
<div style="text-align:center"><div style="font-size:.88rem;color:var(--muted);margin-bottom:4px">$I_1$ (слева)</div>
<button class="btn primary" id="p27-iv2-1up" style="margin:2px">Вверх ↑</button>
<button class="btn" id="p27-iv2-1dn" style="margin:2px">Вниз ↓</button>
</div>
<div style="text-align:center"><div style="font-size:.88rem;color:var(--muted);margin-bottom:4px">$I_2$ (справа)</div>
<button class="btn primary" id="p27-iv2-2up" style="margin:2px">Вверх ↑</button>
<button class="btn" id="p27-iv2-2dn" style="margin:2px">Вниз ↓</button>
</div>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p27-iv2-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p27-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.65;text-align:center"></div>
</div>`;
/* INTERACTIVE 3 — Откуда берётся поле? */
html += `<div class="wg" id="p27-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Откуда берётся магнитное поле?</div></div>
<div class="wg-help">Определи источник магнитного поля в каждой ситуации.</div>
<div class="score-display"><span>Задача <b id="p27-iv3-i">1</b> / 6</span><span>Очки: <b id="p27-iv3-s">0</b> / 6</span></div>
<div id="p27-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="p27-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px"></div>
<div class="feedback" id="p27-iv3-fb"></div>
<div class="actions"><button class="btn" id="p27-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр */
html += `<div class="wg" id="p27-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: магнитное поле тока</div></div>
<div class="wg-help">5 вопросов на ключевые понятия §27.</div>
<div class="score-display"><span>Задача <b id="p27-iv4-i">1</b> / 5</span><span>Очки: <b id="p27-iv4-s">0</b> / 5</span></div>
<div id="p27-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p27-iv4-opts" style="display:grid;grid-template-columns:1fr;gap:8px"></div>
<div class="feedback" id="p27-iv4-fb"></div>
<div class="actions"><button class="btn" id="p27-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav(null, 'p28');
html += readButton('p27');
box.innerHTML = html;
renderMath(box);
/* IV1 — Опыт Эрстеда */
(function(){
const svg = document.getElementById('p27-iv1-svg');
const out = document.getElementById('p27-iv1-out');
const bOff = document.getElementById('p27-iv1-off');
const bR = document.getElementById('p27-iv1-right');
const bL = document.getElementById('p27-iv1-left');
const seen = new Set();
let _done = false;
let state = 'right'; // 'off' | 'right' | 'left'
function setState(s){
state = s;
[bOff,bR,bL].forEach(b => b.classList.remove('primary'));
({off:bOff, right:bR, left:bL}[s]).classList.add('primary');
render();
seen.add(s);
if(!_done && seen.size >= 3){ _done = true; addXp(10, 'p27-iv1'); bumpProgress('p27', 15); }
}
function render(){
const W = 480, H = 280;
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#fafafa"/>';
g += '<text x="240" y="22" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Опыт Эрстеда</text>';
// Провод горизонтальный
const wireY = 110;
g += '<line x1="40" y1="'+wireY+'" x2="440" y2="'+wireY+'" stroke="#0f172a" stroke-width="4" stroke-linecap="round"/>';
g += '<text x="50" y="'+(wireY-12)+'" font-family="Inter,sans-serif" font-size="12" fill="#0f172a">Проводник</text>';
// Стрелка направления тока на проводе
if(state === 'right'){
g += PHYS.drawArrow(180, wireY, 300, wireY, '#dc2626', 3, 14);
g += '<text x="240" y="'+(wireY-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">I →</text>';
} else if(state === 'left'){
g += PHYS.drawArrow(300, wireY, 180, wireY, '#dc2626', 3, 14);
g += '<text x="240" y="'+(wireY-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">← I</text>';
} else {
g += '<text x="240" y="'+(wireY-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#64748b">I = 0</text>';
}
// Магнитная стрелка под проводом — крутится
const compassX = 240, compassY = 210;
const compassR = 42;
// По умолчанию (off) — север (вверх). При токе вправо — отклоняется влево (на нас, по правилу буравчика снизу). При токе влево — вправо.
let angle = 0; // 0 = север (вверх), измеряется по часовой стрелке
if(state === 'right') angle = -75; // отклонение влево
else if(state === 'left') angle = 75; // отклонение вправо
// Корпус компаса
g += '<circle cx="'+compassX+'" cy="'+compassY+'" r="'+compassR+'" fill="#fff" stroke="#0f172a" stroke-width="2"/>';
g += '<text x="'+compassX+'" y="'+(compassY-compassR-4)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#64748b">С</text>';
g += '<text x="'+compassX+'" y="'+(compassY+compassR+12)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#64748b">Ю</text>';
g += '<text x="'+(compassX-compassR-6)+'" y="'+(compassY+3)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#64748b">З</text>';
g += '<text x="'+(compassX+compassR+6)+'" y="'+(compassY+3)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#64748b">В</text>';
// Стрелка компаса (вращается)
const rad = angle * Math.PI / 180;
const nx = compassX + Math.sin(rad) * (compassR - 6);
const ny = compassY - Math.cos(rad) * (compassR - 6);
const sx = compassX - Math.sin(rad) * (compassR - 6);
const sy = compassY + Math.cos(rad) * (compassR - 6);
g += '<line x1="'+sx.toFixed(1)+'" y1="'+sy.toFixed(1)+'" x2="'+nx.toFixed(1)+'" y2="'+ny.toFixed(1)+'" stroke="#0f172a" stroke-width="2"/>';
// Северный конец — красный
g += '<polygon points="'+nx.toFixed(1)+','+ny.toFixed(1)+' '+(compassX + Math.sin(rad)*(compassR-22) - Math.cos(rad)*5).toFixed(1)+','+(compassY - Math.cos(rad)*(compassR-22) - Math.sin(rad)*5).toFixed(1)+' '+(compassX + Math.sin(rad)*(compassR-22) + Math.cos(rad)*5).toFixed(1)+','+(compassY - Math.cos(rad)*(compassR-22) + Math.sin(rad)*5).toFixed(1)+'" fill="#dc2626"/>';
// центр
g += '<circle cx="'+compassX+'" cy="'+compassY+'" r="3" fill="#0f172a"/>';
svg.innerHTML = g;
let txt = '';
if(state === 'off'){
txt = '<b>Ток выключен.</b> Стрелка показывает на север (магнитное поле Земли).';
} else if(state === 'right'){
txt = '<b>Ток вправо</b> → стрелка отклонилась влево. Проводник создал магнитное поле, повернувшее стрелку.';
} else {
txt = '<b>Ток влево</b> → стрелка отклонилась вправо. Смена направления тока — смена направления поля.';
}
out.innerHTML = txt;
}
bOff.addEventListener('click', () => setState('off'));
bR.addEventListener('click', () => setState('right'));
bL.addEventListener('click', () => setState('left'));
setState('right');
})();
/* IV2 — Взаимодействие проводников */
(function(){
const svg = document.getElementById('p27-iv2-svg');
const out = document.getElementById('p27-iv2-out');
const b1u = document.getElementById('p27-iv2-1up');
const b1d = document.getElementById('p27-iv2-1dn');
const b2u = document.getElementById('p27-iv2-2up');
const b2d = document.getElementById('p27-iv2-2dn');
const seen = new Set();
let _done = false;
let I1 = 'up', I2 = 'up';
function setI1(v){ I1 = v; b1u.classList.toggle('primary', v==='up'); b1d.classList.toggle('primary', v==='dn'); render(); }
function setI2(v){ I2 = v; b2u.classList.toggle('primary', v==='up'); b2d.classList.toggle('primary', v==='dn'); render(); }
function render(){
const W = 480, H = 280;
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#fafafa"/>';
g += '<text x="240" y="22" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Два параллельных проводника</text>';
// Два вертикальных провода
const x1 = 170, x2 = 310;
const yTop = 50, yBot = 230;
g += '<line x1="'+x1+'" y1="'+yTop+'" x2="'+x1+'" y2="'+yBot+'" stroke="#0f172a" stroke-width="4" stroke-linecap="round"/>';
g += '<line x1="'+x2+'" y1="'+yTop+'" x2="'+x2+'" y2="'+yBot+'" stroke="#0f172a" stroke-width="4" stroke-linecap="round"/>';
// Стрелки токов
if(I1 === 'up'){
g += PHYS.drawArrow(x1, yBot-20, x1, yTop+20, '#dc2626', 3, 12);
g += '<text x="'+(x1-22)+'" y="'+(yTop+12)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">I₁ ↑</text>';
} else {
g += PHYS.drawArrow(x1, yTop+20, x1, yBot-20, '#dc2626', 3, 12);
g += '<text x="'+(x1-22)+'" y="'+(yBot)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">I₁ ↓</text>';
}
if(I2 === 'up'){
g += PHYS.drawArrow(x2, yBot-20, x2, yTop+20, '#dc2626', 3, 12);
g += '<text x="'+(x2+22)+'" y="'+(yTop+12)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">I₂ ↑</text>';
} else {
g += PHYS.drawArrow(x2, yTop+20, x2, yBot-20, '#dc2626', 3, 12);
g += '<text x="'+(x2+22)+'" y="'+(yBot)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">I₂ ↓</text>';
}
// Силы взаимодействия
const sameDir = (I1 === I2);
const fY = 140;
if(sameDir){
// Притяжение: стрелки направлены друг к другу
g += PHYS.drawArrow(x1+8, fY, x1+58, fY, '#7c3aed', 3, 12);
g += PHYS.drawArrow(x2-8, fY, x2-58, fY, '#7c3aed', 3, 12);
g += '<text x="240" y="'+(fY-12)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="#7c3aed">Притяжение</text>';
g += '<text x="'+(x1+33)+'" y="'+(fY+18)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#7c3aed">F</text>';
g += '<text x="'+(x2-33)+'" y="'+(fY+18)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#7c3aed">F</text>';
} else {
// Отталкивание: стрелки направлены в стороны
g += PHYS.drawArrow(x1+8, fY, x1-42, fY, '#0ea5e9', 3, 12);
g += PHYS.drawArrow(x2-8, fY, x2+42, fY, '#0ea5e9', 3, 12);
g += '<text x="240" y="'+(fY-12)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="#0ea5e9">Отталкивание</text>';
g += '<text x="'+(x1-17)+'" y="'+(fY+18)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0ea5e9">F</text>';
g += '<text x="'+(x2+17)+'" y="'+(fY+18)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0ea5e9">F</text>';
}
svg.innerHTML = g;
let txt = '';
if(sameDir){
txt = '<b>Токи параллельны (в одну сторону)</b> → проводники <b>притягиваются</b>.';
} else {
txt = '<b>Токи антипараллельны (в разные стороны)</b> → проводники <b>отталкиваются</b>.';
}
out.innerHTML = txt;
seen.add(I1+I2);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p27-iv2'); bumpProgress('p27', 15); }
}
b1u.addEventListener('click', () => setI1('up'));
b1d.addEventListener('click', () => setI1('dn'));
b2u.addEventListener('click', () => setI2('up'));
b2d.addEventListener('click', () => setI2('dn'));
render();
})();
/* IV3 — Откуда берётся поле? */
(function(){
const OPTS = ['От постоянных магнитов','От движущихся зарядов','Не существует'];
const Q = [
{ q:'Возле железного магнита (на холодильнике).', ans:0, why:'Постоянный магнит — источник магнитного поля.' },
{ q:'Вокруг проводника, по которому течёт ток.', ans:1, why:'Ток — это движущиеся заряды, создающие поле.' },
{ q:'Возле неподвижного заряженного шарика в вакууме.', ans:2, why:'Неподвижный заряд создаёт только электрическое поле, магнитного нет.' },
{ q:'Вокруг Земли (геомагнитное поле).', ans:1, why:'Поле создаётся токами в жидком металлическом ядре Земли.' },
{ q:'Возле сверхпроводящего кольца без тока.', ans:2, why:'Без тока — нет движущихся зарядов, значит, нет и магнитного поля.' },
{ q:'Возле летящего на большой скорости протона.', ans:1, why:'Движущийся заряд — то же, что ток, и тоже создаёт магнитное поле.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p27-iv3-q');
const oEl = document.getElementById('p27-iv3-opts');
const fb = document.getElementById('p27-iv3-fb');
const iEl = document.getElementById('p27-iv3-i');
const sEl = document.getElementById('p27-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p27-iv3'); bumpProgress('p27', 25); }
else if(score >= 4){ addXp(8, 'p27-iv3'); bumpProgress('p27', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
oEl.innerHTML = OPTS.map((t, k) => '<button class="btn primary" data-v="' + k + '">' + t + '</button>').join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + OPTS[Q[i].ans] + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1800);
});
});
}
document.getElementById('p27-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'Магнитное поле создают:', opts:['любые заряды','движущиеся заряды','масса тел'], ans:1, why:'Только движущиеся заряды создают магнитное поле.' },
{ q:'Кто в 1820 году открыл связь электричества и магнетизма?', opts:['Ньютон','Эрстед','Фарадей'], ans:1, why:'Эрстед заметил отклонение магнитной стрелки возле проводника с током.' },
{ q:'Два параллельных проводника, токи в одну сторону. Что происходит?', opts:['притягиваются','отталкиваются','ничего'], ans:0, why:'Параллельные токи — притягиваются (опыт Ампера).' },
{ q:'Два проводника, токи в противоположных направлениях:', opts:['притягиваются','отталкиваются','ничего'], ans:1, why:'Антипараллельные токи — отталкиваются.' },
{ q:'Вектор магнитной индукции обозначается:', opts:['$\\vec{E}$','$\\vec{B}$','$\\vec{F}$'], ans:1, why:'$\\vec{E}$ — электрическое поле, $\\vec{F}$ — сила, $\\vec{B}$ — магнитная индукция.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p27-iv4-q');
const oEl = document.getElementById('p27-iv4-opts');
const fb = document.getElementById('p27-iv4-fb');
const iEl = document.getElementById('p27-iv4-i');
const sEl = document.getElementById('p27-iv4-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p27-iv4'); bumpProgress('p27', 25); }
else if(score >= 3){ addXp(8, 'p27-iv4'); bumpProgress('p27', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
oEl.innerHTML = Q[i].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 === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].opts[Q[i].ans] + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1800);
});
});
}
document.getElementById('p27-iv4-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p27');
}
function build_p28(){
const box = document.getElementById('p28-body');
let html = '';
/* THEORY 1 — Магнитная индукция */
html += makeCard('theory', "Магнитная индукция $\\vec{B}$", "§28", `
<p><b>Магнитная индукция</b> $\\vec{B}$ — векторная характеристика магнитного поля. Определяет силу, действующую на движущуюся заряженную частицу или на проводник с током.</p>
<p><b>Единица</b> в СИ — <b>Тесла</b> (Тл):</p>
<p style="text-align:center;margin:8px 0">$$[B] = 1\\text{ Тл} = \\dfrac{1\\text{ Н}}{1\\text{ А}\\cdot 1\\text{ м}} = \\dfrac{1\\text{ кг}}{1\\text{ А}\\cdot 1\\text{ с}^2}$$</p>
<p style="margin-top:10px"><b>Порядки величин:</b></p>
<ul style="margin:6px 0 6px 22px">
<li>Магнитное поле Земли: $\\sim 5\\cdot 10^{-5}$ Тл.</li>
<li>Магнит холодильника: $\\sim 5\\cdot 10^{-3}$ Тл.</li>
<li>Сильный электромагнит: $\\sim 1-2$ Тл.</li>
<li>Сверхпроводящий магнит МРТ: $\\sim 3$ Тл и выше.</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Направление $\\vec{B}$</b>:
в постоянном магните <b>снаружи</b> — от северного (N) к южному (S); <b>внутри</b> магнита — от S к N. Линии замкнуты!</p>
`);
/* THEORY 2 — Правило буравчика */
html += makeCard('rule', "Правило буравчика (правой руки)", "§28", `
<p><b>Правило буравчика</b> определяет направление $\\vec{B}$ возле проводника с током.</p>
<p style="margin-top:8px">Возьми проводник правой рукой так, чтобы <b>большой палец</b> указывал по направлению тока. Остальные <b>пальцы</b> покажут направление $\\vec{B}$ (закручивается вокруг проводника).</p>
<p style="margin-top:10px"><b>Альтернатива</b>: представь штопор (буравчик), который вкручивается в направлении тока — ручка вращается в направлении $\\vec{B}$.</p>
<p style="margin-top:10px"><b>Линии магнитной индукции вокруг прямого проводника</b>: <b>концентрические окружности</b> в плоскости, перпендикулярной проводнику.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Свойства линий индукции:</b></p>
<ol style="margin:6px 0 6px 22px">
<li><b>Замкнуты</b> — главное отличие от электрических линий (магнитных монополей не существует).</li>
<li>Не пересекаются.</li>
<li>Густота линий $\\propto B$ (чем гуще, тем сильнее поле).</li>
<li>Направлены от N к S снаружи магнита.</li>
</ol>
`);
/* THEORY 3 — Поле кругового тока и соленоида */
html += makeCard('example', "Поле кругового тока и соленоида", "§28", `
<p><b>Поле кругового тока</b> (витка): линии индукции образуют картину, похожую на поле магнитного диполя. Виток ведёт себя как <b>маленький магнит</b>:</p>
<ul style="margin:6px 0 6px 22px">
<li>Одна сторона — N (магнитные линии выходят).</li>
<li>Другая сторона — S (входят).</li>
</ul>
<p style="margin-top:10px"><b>Соленоид</b> — катушка из многих витков. Поле <b>внутри</b>:</p>
<ul style="margin:6px 0 6px 22px">
<li>Почти <b>однородное</b> (одинаковое по величине и направлению).</li>
<li>Очень похоже на поле в постоянном магните.</li>
<li>Направление — правило правой руки для тока в витках.</li>
</ul>
<p style="margin-top:10px">Модуль индукции внутри длинного соленоида:</p>
<p style="text-align:center;margin:8px 0">$$B = \\dfrac{\\mu_0 N I}{L}$$</p>
<p>где $N$ — число витков, $L$ — длина соленоида, $\\mu_0 = 4\\pi \\cdot 10^{-7}$ Тл·м/А — <b>магнитная постоянная</b>.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Электромагнит</b> — соленоид + железный сердечник. Сердечник усиливает поле в сотни и тысячи раз. Применение: моторы, реле, динамики, МРТ-сканеры, ускорители частиц, левитирующие поезда.</p>
`);
/* INTERACTIVE 1 — Правило буравчика */
html += `<div class="wg" id="p28-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Правило буравчика: $\\vec{B}$ возле проводника</div></div>
<div class="wg-help">Переключай направление тока. Смотри, как меняется направление линий $\\vec{B}$ (вид сверху, ток идёт сквозь экран).</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:10px;flex-wrap:wrap">
<button class="btn primary" id="p28-iv1-out">Ток ИЗ экрана (•)</button>
<button class="btn" id="p28-iv1-in">Ток В экран (×)</button>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p28-iv1-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p28-iv1-out-txt" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.65;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Соленоид */
html += `<div class="wg" id="p28-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Магнитное поле соленоида</div></div>
<div class="wg-help">Меняй ток $I$ и число витков $N$. Длина соленоида $L = 0{,}1$ м. Формула: $B = \\mu_0 N I / L$.</div>
<div class="sliders">
<label>$I$: <b id="p28-iv2-IL">2.0</b> А <input type="range" id="p28-iv2-I" min="0" max="5" value="2" step="0.1"></label>
<label>$N$: <b id="p28-iv2-NL">50</b> витков <input type="range" id="p28-iv2-N" min="10" max="100" value="50" step="1"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p28-iv2-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p28-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 3 — Куда направлено B? */
html += `<div class="wg" id="p28-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Куда направлено $\\vec{B}$?</div></div>
<div class="wg-help">Выбери, как определяется (или существует ли вообще) магнитное поле в каждой ситуации.</div>
<div class="score-display"><span>Задача <b id="p28-iv3-i">1</b> / 6</span><span>Очки: <b id="p28-iv3-s">0</b> / 6</span></div>
<div id="p28-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="p28-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p28-iv3-fb"></div>
<div class="actions"><button class="btn" id="p28-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр */
html += `<div class="wg" id="p28-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: индукция магнитного поля</div></div>
<div class="wg-help">5 вопросов на ключевые понятия §28.</div>
<div class="score-display"><span>Задача <b id="p28-iv4-i">1</b> / 5</span><span>Очки: <b id="p28-iv4-s">0</b> / 5</span></div>
<div id="p28-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p28-iv4-opts" style="display:grid;grid-template-columns:1fr;gap:8px"></div>
<div class="feedback" id="p28-iv4-fb"></div>
<div class="actions"><button class="btn" id="p28-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p27', 'p29');
html += readButton('p28');
box.innerHTML = html;
renderMath(box);
/* IV1 — Правило буравчика */
(function(){
const svg = document.getElementById('p28-iv1-svg');
const out = document.getElementById('p28-iv1-out-txt');
const bOut = document.getElementById('p28-iv1-out');
const bIn = document.getElementById('p28-iv1-in');
const seen = new Set();
let _done = false;
let dir = 'out'; // 'out' or 'in'
function setDir(d){
dir = d;
bOut.classList.toggle('primary', d==='out');
bIn.classList.toggle('primary', d==='in');
render();
seen.add(d);
if(!_done && seen.size >= 2){ _done = true; addXp(10, 'p28-iv1'); bumpProgress('p28', 15); }
}
function render(){
const W = 480, H = 280;
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#fafafa"/>';
g += '<text x="240" y="22" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Поле прямого тока (вид вдоль проводника)</text>';
// Центральный проводник
const cx = 240, cy = 150;
const wireColor = '#0f172a';
// Концентрические окружности — линии B
const rings = [40, 70, 100, 130];
const Bcolor = '#7c3aed';
rings.forEach(r => {
g += '<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="none" stroke="'+Bcolor+'" stroke-width="1.5" opacity=".55"/>';
});
// Стрелки касательные к окружностям — направление B по правилу буравчика
// При токе ИЗ экрана (•) — B по часовой стрелке снизу? Нет: правая рука, большой палец на нас (out), пальцы — против часовой стрелки.
// Считаю: ток OUT (на наблюдателя) → B против часовой стрелки.
// ток IN (от наблюдателя) → B по часовой стрелке.
const ccw = (dir === 'out');
const arrowAngles = [0, Math.PI/2, Math.PI, 3*Math.PI/2]; // 4 стрелки на каждой окружности
rings.forEach((r, idx) => {
if(idx % 2 !== 0) return; // только на двух окружностях
arrowAngles.forEach((a, k) => {
const ax = cx + r * Math.cos(a);
const ay = cy + r * Math.sin(a);
// Касательный вектор
const tx = -Math.sin(a) * (ccw ? -1 : 1);
const ty = Math.cos(a) * (ccw ? -1 : 1);
const len = 16;
const x2 = ax + tx * len;
const y2 = ay + ty * len;
g += PHYS.drawArrow(ax, ay, x2, y2, Bcolor, 2, 8);
});
});
// Проводник в центре — крест или точка
g += '<circle cx="'+cx+'" cy="'+cy+'" r="14" fill="white" stroke="'+wireColor+'" stroke-width="2.4"/>';
if(dir === 'in'){
// Крест ×
g += '<line x1="'+(cx-8)+'" y1="'+(cy-8)+'" x2="'+(cx+8)+'" y2="'+(cy+8)+'" stroke="'+wireColor+'" stroke-width="3"/>';
g += '<line x1="'+(cx-8)+'" y1="'+(cy+8)+'" x2="'+(cx+8)+'" y2="'+(cy-8)+'" stroke="'+wireColor+'" stroke-width="3"/>';
} else {
// Точка •
g += '<circle cx="'+cx+'" cy="'+cy+'" r="4" fill="'+wireColor+'"/>';
}
// Подпись
const lbl = dir === 'out' ? 'Ток I из экрана (•)' : 'Ток I в экран (×)';
g += '<text x="'+cx+'" y="'+(cy+45)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+wireColor+'">'+lbl+'</text>';
// Подпись B
g += '<text x="'+(cx + 150)+'" y="'+(cy)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="'+Bcolor+'">B</text>';
svg.innerHTML = g;
let txt = '';
if(dir === 'out'){
txt = '<b>Ток направлен ИЗ экрана</b> (на тебя). Правило буравчика: большой палец на тебя → линии $\\vec{B}$ <b>против часовой стрелки</b>.';
} else {
txt = '<b>Ток направлен В экран</b> (от тебя). Правило буравчика: большой палец от тебя → линии $\\vec{B}$ <b>по часовой стрелке</b>.';
}
out.innerHTML = txt;
renderMath(out);
}
bOut.addEventListener('click', () => setDir('out'));
bIn.addEventListener('click', () => setDir('in'));
setDir('out');
})();
/* IV2 — Соленоид */
(function(){
const svg = document.getElementById('p28-iv2-svg');
const out = document.getElementById('p28-iv2-out');
const IS = document.getElementById('p28-iv2-I');
const NS = document.getElementById('p28-iv2-N');
const IL = document.getElementById('p28-iv2-IL');
const NL = document.getElementById('p28-iv2-NL');
const seen = new Set();
let _done = false;
function render(){
const I = +IS.value, N = +NS.value;
IL.textContent = I.toFixed(1);
NL.textContent = N.toFixed(0);
const L = 0.1; // м
const mu0 = 4 * Math.PI * 1e-7;
const B = mu0 * N * I / L;
const W = 480, H = 280;
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#fafafa"/>';
g += '<text x="240" y="22" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">Соленоид: однородное поле внутри</text>';
// Соленоид — нарисуем как ряд овалов (витков) горизонтально
const cy = 145;
const xL = 90, xR = 390;
const numVisible = Math.min(20, Math.max(5, Math.round(N/5)));
const stepX = (xR - xL) / (numVisible - 1);
const Bcolor = '#7c3aed';
// Корпус соленоида
g += '<rect x="'+xL+'" y="'+(cy-40)+'" width="'+(xR-xL)+'" height="80" fill="rgba(124,58,237,0.05)" stroke="#94a3b8" stroke-width="1.4" stroke-dasharray="4 3"/>';
// Витки как петли
for(let i = 0; i < numVisible; i++){
const x = xL + i * stepX;
g += '<ellipse cx="'+x+'" cy="'+cy+'" rx="6" ry="35" fill="none" stroke="#0f172a" stroke-width="1.6"/>';
// Точка на верхушке (ток на нас) или крест внизу (от нас) — упрощённо
g += '<circle cx="'+x+'" cy="'+(cy-35)+'" r="2.5" fill="#dc2626"/>';
// Крест внизу
g += '<line x1="'+(x-3)+'" y1="'+(cy+33)+'" x2="'+(x+3)+'" y2="'+(cy+37)+'" stroke="#dc2626" stroke-width="1.2"/>';
g += '<line x1="'+(x-3)+'" y1="'+(cy+37)+'" x2="'+(x+3)+'" y2="'+(cy+33)+'" stroke="#dc2626" stroke-width="1.2"/>';
}
// Внутреннее поле — стрелки слева направо (зависит от направления намотки, считаем что N справа)
if(I > 0.01){
const nArrows = 5;
for(let k = 0; k < nArrows; k++){
const ax = xL + 30 + k * ((xR-xL-60)/(nArrows-1));
const arrowLen = Math.min(50, 20 + (B * 1e3) * 8);
g += PHYS.drawArrow(ax - arrowLen/2, cy, ax + arrowLen/2, cy, Bcolor, 2.5, 10);
}
// Подписи полюсов
g += '<text x="'+(xL-18)+'" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="16" font-weight="800" fill="#0ea5e9">S</text>';
g += '<text x="'+(xR+18)+'" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="16" font-weight="800" fill="#dc2626">N</text>';
// Линии поля снаружи (упрощённо — две дуги)
g += '<path d="M '+xR+' '+(cy-35)+' Q '+(xR+60)+' '+(cy-90)+' '+(xL+ (xR-xL)/2)+' '+(cy-100)+' Q '+(xL-60)+' '+(cy-90)+' '+xL+' '+(cy-35)+'" fill="none" stroke="'+Bcolor+'" stroke-width="1.4" opacity=".55"/>';
g += '<path d="M '+xR+' '+(cy+35)+' Q '+(xR+60)+' '+(cy+90)+' '+(xL+ (xR-xL)/2)+' '+(cy+100)+' Q '+(xL-60)+' '+(cy+90)+' '+xL+' '+(cy+35)+'" fill="none" stroke="'+Bcolor+'" stroke-width="1.4" opacity=".55"/>';
} else {
g += '<text x="240" y="'+(cy+5)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" fill="#64748b">I = 0 → поля нет</text>';
}
// Метка B
if(I > 0.01) g += '<text x="240" y="'+(cy-50)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="'+Bcolor+'">B</text>';
svg.innerHTML = g;
out.innerHTML = '<b>$B = \\dfrac{\\mu_0 N I}{L} = \\dfrac{4\\pi\\cdot 10^{-7}\\cdot '+N.toFixed(0)+'\\cdot '+I.toFixed(2)+'}{0{,}1} = '+B.toExponential(2).replace('e','\\cdot 10^{').replace('+','')+'}$ Тл</b>';
renderMath(out);
seen.add(I.toFixed(1)+':'+N.toFixed(0));
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p28-iv2'); bumpProgress('p28', 15); }
}
IS.addEventListener('input', render);
NS.addEventListener('input', render);
render();
})();
/* IV3 — Куда направлено B? */
(function(){
const OPTS = ['От N к S','По правилу буравчика','От заряда','$\\vec{B} = 0$'];
const Q = [
{ q:'Снаружи постоянного магнита (вне корпуса).', ans:0, why:'Снаружи магнита линии $\\vec{B}$ идут от северного полюса к южному.' },
{ q:'Возле прямого проводника с током.', ans:1, why:'Используем правило правой руки (буравчика).' },
{ q:'Внутри сверхпроводящего кольца без тока.', ans:3, why:'Нет тока — нет источника поля, $\\vec{B} = 0$.' },
{ q:'В вакууме вдали от любых источников.', ans:3, why:'Без источников $\\vec{B} = 0$.' },
{ q:'Внутри соленоида с током.', ans:1, why:'Направление определяется правилом правой руки для тока в витках.' },
{ q:'В точке посередине между двумя одинаковыми параллельными токами одного направления.', ans:3, why:'Поля от двух проводников в этой точке направлены противоположно и взаимно компенсируются.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p28-iv3-q');
const oEl = document.getElementById('p28-iv3-opts');
const fb = document.getElementById('p28-iv3-fb');
const iEl = document.getElementById('p28-iv3-i');
const sEl = document.getElementById('p28-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p28-iv3'); bumpProgress('p28', 25); }
else if(score >= 4){ addXp(8, 'p28-iv3'); bumpProgress('p28', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].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 === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + OPTS[Q[i].ans] + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1800);
});
});
}
document.getElementById('p28-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'Единица измерения магнитной индукции в СИ:', opts:['Тесла (Тл)','Вольт (В)','Ампер (А)'], ans:0, why:'$1$ Тл $= 1$ Н/(А·м).' },
{ q:'Магнитное поле Земли по порядку величины (в Тл):', opts:['$5\\cdot 10^{-5}$','$5\\cdot 10^{-2}$','$5\\cdot 10^{1}$'], ans:0, why:'Геомагнитное поле слабое: $\\sim 5\\cdot 10^{-5}$ Тл.' },
{ q:'Направление $\\vec{B}$ возле прямого проводника находят:', opts:['по закону Ома','по правилу буравчика','по второму закону Ньютона'], ans:1, why:'Правило правой руки (буравчика): большой палец — по току, пальцы — по $\\vec{B}$.' },
{ q:'Коэффициент $\\mu_0$ в формуле $B = \\mu_0 N I / L$ равен:', opts:['$4\\pi\\cdot 10^{-7}$ Тл·м/А','$8{,}85\\cdot 10^{-12}$ Ф/м','$9\\cdot 10^{9}$ Н·м²/Кл²'], ans:0, why:'$\\mu_0 = 4\\pi\\cdot 10^{-7}$ Тл·м/А — магнитная постоянная.' },
{ q:'Линии магнитной индукции замкнуты, потому что:', opts:['поле слабое','магнитных монополей не существует','так удобно рисовать'], ans:1, why:'В природе нет отдельных «северных» или «южных» зарядов, поэтому линии $\\vec{B}$ всегда замкнуты.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p28-iv4-q');
const oEl = document.getElementById('p28-iv4-opts');
const fb = document.getElementById('p28-iv4-fb');
const iEl = document.getElementById('p28-iv4-i');
const sEl = document.getElementById('p28-iv4-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p28-iv4'); bumpProgress('p28', 25); }
else if(score >= 3){ addXp(8, 'p28-iv4'); bumpProgress('p28', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
oEl.innerHTML = Q[i].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 === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].opts[Q[i].ans] + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1800);
});
});
}
document.getElementById('p28-iv4-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p28');
}
function build_p29(){
const box = document.getElementById('p29-body');
let html = '';
html += makeCard('theory', "Сила Ампера", "§29", `
<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 5+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p28', 'p30');
html += readButton('p29');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p29');
}
function build_p30(){
const box = document.getElementById('p30-body');
let html = '';
html += makeCard('theory', "Сила Лоренца", "§30", `
<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 5+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p29', 'p31');
html += readButton('p30');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p30');
}
function build_p31(){
const box = document.getElementById('p31-body');
let html = '';
html += makeCard('theory', "Магнитный поток. Электромагнитная индукция", "§31", `
<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 5+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p30', 'p32');
html += readButton('p31');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p31');
}
function build_p32(){
const box = document.getElementById('p32-body');
let html = '';
html += makeCard('theory', "Правило Ленца. Закон Фарадея", "§32", `
<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 5+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p31', 'p33');
html += readButton('p32');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p32');
}
function build_p33(){
const box = document.getElementById('p33-body');
let html = '';
html += makeCard('theory', "Самоиндукция", "§33", `
<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 5+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p32', 'final5');
html += readButton('p33');
box.innerHTML = html;
renderMath(box);
wireReadBtn('p33');
}
function build_final5(){
const box = document.getElementById('final5-body');
let html = '';
html += makeCard('theory', "Финал главы 5", "★", `
<p><b>Финал главы 5</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 5+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
`);
html += secNav('p33', null);
html += readButton('final5');
box.innerHTML = html;
renderMath(box);
wireReadBtn('final5');
}
/* ===== 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>