Files
Maxim Dolgolyov 9d5a2959e1 fix(textbooks): кнопка «Шпаргалка» не открывала контент на desktop
На десктопе (>980px) .col-side уже видна как sticky-колонка справа в grid 1fr 280px.
Клик по кнопке #sidebar-btn добавлял .col-side-backdrop.show — backdrop с
z-index:9990 затемнял всю страницу, перекрывая sticky-aside. Со стороны
выглядело как «ничего не открылось» — на самом деле появлялась чёрная вуаль.

Фикс: @media(min-width:981px) скрывает #sidebar-btn и подавляет показ backdrop.
На мобайле (≤980px) кнопка и overlay работают как раньше.

Применено в 51 файле: physics 8/9/10 chN, algebra 7/9/10/11 chN + 8 ch2-3,
geometry 7/8/9/11 chN, geometry_10 r1-4.
2026-05-30 09:51:04 +03:00

3639 lines
256 KiB
HTML
Raw Permalink 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(min-width:981px){#sidebar-btn{display:none}.col-side-backdrop.show{display:none}}
@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" data-gamified></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-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" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('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 = '';
/* THEORY 1 — Сила Ампера */
html += makeCard('theory', "Сила Ампера", "§29", `
<p><b>Сила Ампера</b> — сила, действующая на проводник с током, помещённый в магнитное поле.</p>
<p style="margin-top:8px"><b>Модуль</b>:</p>
<p style="text-align:center;margin:8px 0">$$F_A = B I L \\sin\\alpha$$</p>
<ul style="margin:6px 0 6px 22px">
<li>$B$ — индукция магнитного поля (Тл),</li>
<li>$I$ — сила тока в проводнике (А),</li>
<li>$L$ — длина участка проводника в поле (м),</li>
<li>$\\alpha$ — угол между направлением тока и вектором $\\vec{B}$.</li>
</ul>
<p style="margin-top:10px"><b>Особые случаи:</b></p>
<ul style="margin:6px 0 6px 22px">
<li>$\\alpha = 90°$ (ток $\\perp \\vec{B}$): $F_{max} = B I L$ — максимум.</li>
<li>$\\alpha = 0°$ или $180°$ (ток $\\parallel \\vec{B}$): $F_A = 0$ — сила отсутствует.</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Главная мысль.</b> Магнитное поле действует на ток. Чем сильнее поле, длиннее провод, больше ток и удачнее ориентация (ближе к $90°$), тем больше сила.</p>
`);
/* THEORY 2 — Правило левой руки */
html += makeCard('rule', "Правило левой руки", "§29", `
<p><b>Правило левой руки</b> — приём для определения направления силы Ампера.</p>
<p style="margin-top:8px">Расположи <b>левую руку</b> так, чтобы:</p>
<ol style="margin:6px 0 6px 22px">
<li>Линии магнитной индукции $\\vec{B}$ <b>входили в ладонь</b> (поле «прокалывало» ладонь).</li>
<li>Четыре <b>выпрямленных пальца</b> показывали направление <b>тока</b> $I$.</li>
<li>Тогда отогнутый под $90°$ <b>большой палец</b> покажет направление <b>силы Ампера</b> $\\vec{F_A}$.</li>
</ol>
<p style="margin-top:10px"><b>Свойство.</b> Сила Ампера $\\vec{F_A}$ всегда <b>перпендикулярна</b> и проводнику с током, и вектору $\\vec{B}$.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Важно.</b> Это же правило (с заменой «тока» на «скорость положительного заряда») работает для силы Лоренца (§30). Для отрицательного заряда направление противоположное.</p>
`);
/* THEORY 3 — Применение */
html += makeCard('example', "Где работает сила Ампера", "§29", `
<p><b>Электродвигатель.</b> В магнитном поле находится рамка с током. Силы Ампера, действующие на её стороны, создают <b>вращающий момент</b>. Рамка вращается — получается механическое движение из электрического тока. От детских игрушек до электровозов и промышленных приводов — везде это устройство.</p>
<p style="margin-top:10px"><b>Электроизмерительные приборы.</b> В <b>гальванометре</b> рамка с током отклоняется в поле постоянного магнита; угол отклонения пропорционален силе тока, и стрелка показывает его на шкале.</p>
<p style="margin-top:10px"><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>Принцип суперпозиции для $\\vec{B}$.</b> Магнитное поле от нескольких источников равно <b>векторной сумме</b> полей от каждого. Поэтому сложные конфигурации (катушки, рамки, провода) разбиваются на простые и складываются.</p>
`);
/* INTERACTIVE 1 — Правило левой руки (визуализатор) */
html += `<div class="wg" id="p29-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Правило левой руки: куда направлена $\\vec{F_A}$?</div></div>
<div class="wg-help">Магнитное поле $\\vec{B}$ направлено <b>в экран</b> (×). Выбери направление тока и параметры — увидишь силу Ампера.</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:10px;flex-wrap:wrap">
<button class="btn primary" id="p29-iv1-up" data-d="up">Ток вверх ↑</button>
<button class="btn" id="p29-iv1-dn" data-d="dn">Ток вниз ↓</button>
<button class="btn" id="p29-iv1-rt" data-d="rt">Ток вправо →</button>
<button class="btn" id="p29-iv1-lt" data-d="lt">Ток влево ←</button>
</div>
<div class="sliders">
<label>$I$: <b id="p29-iv1-IL">5.0</b> А <input type="range" id="p29-iv1-I" min="0" max="10" value="5" step="0.1"></label>
<label>$B$: <b id="p29-iv1-BL">1.0</b> Тл <input type="range" id="p29-iv1-B" min="0" max="2" value="1" step="0.05"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p29-iv1-svg" viewBox="0 0 480 320" width="100%" style="height:auto"></svg>
</div>
<div id="p29-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="p29-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор силы Ампера</div></div>
<div class="wg-help">Введи параметры — получи $F_A = B I L \\sin\\alpha$ в Ньютонах.</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px">
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$B$ (Тл):<input type="number" id="p29-iv2-B" value="0.5" step="0.01" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$I$ (А):<input type="number" id="p29-iv2-I" value="2" step="0.1" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$L$ (м):<input type="number" id="p29-iv2-L" value="0.1" step="0.01" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$\\alpha$ (°):<input type="number" id="p29-iv2-A" value="90" step="1" min="0" max="180" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
</div>
<div class="actions"><button class="btn primary" id="p29-iv2-calc">Вычислить $F_A$</button></div>
<div id="p29-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.96rem;line-height:1.7;text-align:center;min-height:40px"></div>
</div>`;
/* INTERACTIVE 3 — Куда направлена сила Ампера? */
html += `<div class="wg" id="p29-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Куда направлена сила Ампера?</div></div>
<div class="wg-help">Используй правило левой руки. 6 ситуаций.</div>
<div class="score-display"><span>Задача <b id="p29-iv3-i">1</b> / 6</span><span>Очки: <b id="p29-iv3-s">0</b> / 6</span></div>
<div id="p29-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="p29-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p29-iv3-fb"></div>
<div class="actions"><button class="btn" id="p29-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр (числовые ответы) */
html += `<div class="wg" id="p29-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: сила Ампера</div></div>
<div class="wg-help">5 числовых задач. Допуск ±5%. Введи число и нажми Enter.</div>
<div class="score-display"><span>Задача <b id="p29-iv4-i">1</b> / 5</span><span>Очки: <b id="p29-iv4-s">0</b> / 5</span></div>
<div id="p29-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="p29-iv4-form" style="display:flex;gap:8px;justify-content:center;margin-bottom:8px;flex-wrap:wrap">
<input type="number" id="p29-iv4-inp" step="any" style="padding:8px 10px;border:1px solid var(--border);border-radius:6px;width:140px;font-size:1rem">
<button class="btn primary" id="p29-iv4-go">Проверить</button>
</div>
<div class="feedback" id="p29-iv4-fb"></div>
<div class="actions"><button class="btn" id="p29-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p28', 'p30');
html += readButton('p29');
box.innerHTML = html;
renderMath(box);
/* IV1 — Визуализатор правила левой руки */
(function(){
const svg = document.getElementById('p29-iv1-svg');
const out = document.getElementById('p29-iv1-out');
const bUp = document.getElementById('p29-iv1-up');
const bDn = document.getElementById('p29-iv1-dn');
const bRt = document.getElementById('p29-iv1-rt');
const bLt = document.getElementById('p29-iv1-lt');
const IS = document.getElementById('p29-iv1-I');
const BS = document.getElementById('p29-iv1-B');
const IL = document.getElementById('p29-iv1-IL');
const BL = document.getElementById('p29-iv1-BL');
const seen = new Set();
let _done = false;
let dir = 'up';
function setDir(d){
dir = d;
[bUp,bDn,bRt,bLt].forEach(b => b.classList.remove('primary'));
({up:bUp,dn:bDn,rt:bRt,lt:bLt}[d]).classList.add('primary');
render();
}
function render(){
const I = +IS.value, B = +BS.value;
IL.textContent = I.toFixed(1);
BL.textContent = B.toFixed(2);
const W = 480, H = 320;
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">$\\vec{B}$ направлено в экран (×). Правило левой руки</text>'.replace('$\\vec{B}$','B');
// Сетка магнитного поля (×)
g += PHYS.magneticFieldGrid(50, 50, 380, 220, 8, 5, 'in');
const cx = 240, cy = 160;
// Проводник с током
const L = 90; // длина рисунка
let x1,y1,x2,y2; // конечные точки тока (стрелка от начала к концу)
if(dir === 'up'){ x1=cx; y1=cy+L; x2=cx; y2=cy-L; }
else if(dir === 'dn'){ x1=cx; y1=cy-L; x2=cx; y2=cy+L; }
else if(dir === 'rt'){ x1=cx-L; y1=cy; x2=cx+L; y2=cy; }
else { x1=cx+L; y1=cy; x2=cx-L; y2=cy; }
// Сам провод (в нейтральном цвете)
g += '<line x1="'+(x1)+'" y1="'+(y1)+'" x2="'+(x2)+'" y2="'+(y2)+'" stroke="#0f172a" stroke-width="5" stroke-linecap="round" opacity=".25"/>';
// Стрелка тока
g += PHYS.drawArrow(x1, y1, x2, y2, '#dc2626', 3.5, 16);
// Метка I
const Imx = x2 + (dir==='rt'?14:dir==='lt'?-14:0);
const Imy = y2 + (dir==='up'?-12:dir==='dn'?16:0);
g += '<text x="'+Imx+'" y="'+Imy+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="14" font-weight="800" fill="#dc2626">I</text>';
// Направление силы по правилу левой руки (B в экран):
// F = I × B. B = -z (в экран). I вверх (+y) → F = y × (-z) = -(y×z) = -x → влево
// I вниз (-y) → F вправо. I вправо (+x) → F = x×(-z) = -(x×z) = +y? В экранных координатах y растёт вниз; но физический y вверх. В физической системе F=qv×B; в экранных: вверх — это -y_экр. Будем выводить так:
// Take I dir (ix,iy экранные) and B = into page (z-out screen is "к нам", into page = -z_screen).
// Use formula: F_screen = I_screen × B_into_screen. Cross with -ẑ: (ix, iy, 0) × (0,0,-1) = (iy*(-1) - 0, 0 - ix*(-1), 0) = (-iy, ix, 0)
// For 'up': I=(0,-1) → F=(-(-1), 0)=(1,0) → вправо. But правило левой руки: B в экран, I вверх → F влево.
// The discrepancy is sign of B. "В экран" we treated as B = -ẑ_screen. Let's check by physics: B is into page → in standard right-handed coord (x right, y up, z out of page) B = -ẑ. I up = +ŷ. F = IL × B = ŷ × (-ẑ) = -(ŷ×ẑ) = -x̂ → влево. Yes that matches.
// In SCREEN coordinates: x_screen=x, y_screen=-y. So I_up means y_screen=-1, y_phys=+1. Convert back: ix_screen=0,iy_screen=-1 → ix_phys=0, iy_phys=+1.
// Compute F in phys: ix_phys × B(=-ẑ) per above formula F_phys=(-iy_phys, ix_phys). For ix_phys=0,iy_phys=+1: F_phys=(-1,0) → x_phys=-1 → влево. ✓
// To get F_screen we negate y: F_screen=(F_x_phys, -F_y_phys)=(-1,0) → влево. ✓
// Generalize: given i_screen=(ix,iy), iy_phys=-iy. F_phys=(-iy_phys, ix_phys)=(iy, ix?). Wait ix_phys=ix. F_phys=(-iy_phys, ix_phys)=(iy, ix). Then F_screen=(F_x_phys, -F_y_phys)=(iy, -ix).
// Test: up i_screen=(0,-1)→F_screen=(-1, 0)=влево ✓. down (0,1)→(1,0) вправо ✓. right (1,0)→(0,-1)=вверх ✓. left(-1,0)→(0,1)=вниз ✓.
const map = { up:{ix:0,iy:-1, label:'вверх'}, dn:{ix:0,iy:1, label:'вниз'}, rt:{ix:1,iy:0, label:'вправо'}, lt:{ix:-1,iy:0, label:'влево'} };
const cur = map[dir];
const fx = cur.iy, fy = -cur.ix;
const fLabel = (fx===1)?'вправо':(fx===-1)?'влево':(fy===-1)?'вверх':'вниз';
// Рисуем силу: начинается из центра провода
const fLen = 70;
const fEndX = cx + fx * fLen;
const fEndY = cy + fy * fLen;
g += PHYS.drawArrow(cx, cy, fEndX, fEndY, '#0891b2', 4, 18);
g += '<text x="'+(fEndX + fx*16)+'" y="'+(fEndY + fy*16 + (fy===0?4:0))+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="15" font-weight="800" fill="#0891b2">F</text>';
// Подпись внизу
const Fmod = B * I * 1.0; // L=1 м
g += '<text x="240" y="305" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#64748b">L = 1 м, $\\alpha = 90°$</text>'.replace('$\\alpha = 90°$','α=90°');
svg.innerHTML = g;
out.innerHTML = '<b>Ток '+cur.label+'</b>, $\\vec{B}$ в экран → сила Ампера <b>направлена '+fLabel+'</b>.<br>$F_A = B \\cdot I \\cdot L = '+B.toFixed(2)+' \\cdot '+I.toFixed(2)+' \\cdot 1 = '+Fmod.toFixed(2)+'$ Н.';
renderMath(out);
seen.add(dir);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p29-iv1'); bumpProgress('p29', 15); }
}
[bUp,bDn,bRt,bLt].forEach(b => b.addEventListener('click', () => setDir(b.dataset.d)));
IS.addEventListener('input', render);
BS.addEventListener('input', render);
setDir('up');
})();
/* IV2 — Калькулятор */
(function(){
const out = document.getElementById('p29-iv2-out');
const bGo = document.getElementById('p29-iv2-calc');
const inB = document.getElementById('p29-iv2-B');
const inI = document.getElementById('p29-iv2-I');
const inL = document.getElementById('p29-iv2-L');
const inA = document.getElementById('p29-iv2-A');
let count = 0, _done = false;
function calc(){
const B = +inB.value, I = +inI.value, L = +inL.value, A = +inA.value;
if(!isFinite(B)||!isFinite(I)||!isFinite(L)||!isFinite(A)){
out.innerHTML = '<b style="color:#dc2626">Введи все значения.</b>';
return;
}
const rad = A * Math.PI / 180;
const F = B * I * L * Math.sin(rad);
out.innerHTML = '$F_A = B \\cdot I \\cdot L \\cdot \\sin\\alpha = '+B+' \\cdot '+I+' \\cdot '+L+' \\cdot \\sin '+A+'° = \\mathbf{'+F.toFixed(4)+'}$ <b>Н</b>';
renderMath(out);
count++;
if(!_done && count >= 3){ _done = true; addXp(10, 'p29-iv2'); bumpProgress('p29', 15); }
}
bGo.addEventListener('click', calc);
[inB,inI,inL,inA].forEach(x => x.addEventListener('keydown', e => { if(e.key==='Enter') calc(); }));
})();
/* IV3 — Куда направлена F_A? */
(function(){
const OPTS = ['Вправо','Влево','Вверх','Вниз'];
const Q = [
{ q:'$\\vec{B}$ в экран (×), ток вверх. Куда направлена $\\vec{F_A}$?', ans:1, why:'По правилу левой руки: B входит в ладонь, 4 пальца — вверх (ток), большой — влево.' },
{ q:'$\\vec{B}$ из экрана (•), ток вверх. Куда направлена $\\vec{F_A}$?', ans:0, why:'B теперь из ладони, четыре пальца — вверх; большой палец — вправо.' },
{ q:'$\\vec{B}$ в экран (×), ток вправо. Куда направлена $\\vec{F_A}$?', ans:2, why:'B входит в ладонь, пальцы — вправо; большой палец — вверх.' },
{ q:'$\\vec{B}$ в экран (×), ток влево. Куда направлена $\\vec{F_A}$?', ans:3, why:'B входит в ладонь, пальцы — влево; большой палец — вниз.' },
{ q:'$\\vec{B}$ из экрана (•), ток вниз. Куда направлена $\\vec{F_A}$?', ans:1, why:'B из ладони, пальцы — вниз; большой палец — влево.' },
{ q:'$\\vec{B}$ из экрана (•), ток вправо. Куда направлена $\\vec{F_A}$?', ans:3, why:'B из ладони, пальцы — вправо; большой палец — вниз.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p29-iv3-q');
const oEl = document.getElementById('p29-iv3-opts');
const fb = document.getElementById('p29-iv3-fb');
const iEl = document.getElementById('p29-iv3-i');
const sEl = document.getElementById('p29-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p29-iv3'); bumpProgress('p29', 25); }
else if(score >= 4){ addXp(8, 'p29-iv3'); bumpProgress('p29', 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('p29-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр числовой */
(function(){
const Q = [
{ q:'$B = 0{,}5$ Тл, $I = 2$ А, $L = 0{,}1$ м, $\\alpha = 90°$. Найди $F_A$ (Н).', ans:0.1, tol:0.005, why:'$F_A = 0{,}5 \\cdot 2 \\cdot 0{,}1 \\cdot 1 = 0{,}1$ Н.' },
{ q:'Те же данные, но $\\alpha = 30°$. Найди $F_A$ (Н).', ans:0.05, tol:0.005, why:'$F_A = 0{,}1 \\cdot \\sin 30° = 0{,}1 \\cdot 0{,}5 = 0{,}05$ Н.' },
{ q:'При каком угле $\\alpha$ (в °, $0\\le\\alpha\\le 90$) сила Ампера равна нулю?', ans:0, tol:1, why:'$F_A = 0$ при $\\alpha = 0°$ (или $180°$): ток параллелен $\\vec{B}$.' },
{ q:'$F_A = 0{,}4$ Н, $I = 2$ А, $L = 0{,}2$ м, $\\alpha = 90°$. Найди $B$ (Тл).', ans:1.0, tol:0.05, why:'$B = F/(IL) = 0{,}4/(2\\cdot 0{,}2) = 1$ Тл.' },
{ q:'Проводник с током «вверх» в поле $\\vec{B}$, направленном в экран. Куда отклонится? Введи: 1 — вправо, 2 — влево, 3 — в экран, 4 — из экрана.', ans:2, tol:0, why:'По правилу левой руки сила направлена влево (ответ 2).' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p29-iv4-q');
const fb = document.getElementById('p29-iv4-fb');
const iEl = document.getElementById('p29-iv4-i');
const sEl = document.getElementById('p29-iv4-s');
const inp = document.getElementById('p29-iv4-inp');
const bGo = document.getElementById('p29-iv4-go');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
inp.disabled = true; bGo.disabled = true;
if(score === Q.length){ addXp(15, 'p29-iv4'); bumpProgress('p29', 25); }
else if(score >= 3){ addXp(8, 'p29-iv4'); bumpProgress('p29', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
fb.style.display = 'none';
inp.value = ''; inp.disabled = false; bGo.disabled = false; inp.focus();
renderMath(qEl);
}
function check(){
if(inp.disabled) return;
const v = parseFloat(inp.value.replace(',','.'));
if(!isFinite(v)){ feedback(fb, false, 'Введи число.'); return; }
const ok = Math.abs(v - Q[i].ans) <= Math.max(Q[i].tol, Math.abs(Q[i].ans)*0.05);
if(ok){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].ans + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
inp.disabled = true; bGo.disabled = true;
i++;
setTimeout(show, 1900);
}
bGo.addEventListener('click', check);
inp.addEventListener('keydown', e => { if(e.key==='Enter') check(); });
document.getElementById('p29-iv4-restart').addEventListener('click', () => { i = 0; score = 0; inp.disabled=false; bGo.disabled=false; show(); });
show();
})();
wireReadBtn('p29');
}
function build_p30(){
const box = document.getElementById('p30-body');
let html = '';
/* THEORY 1 — Сила Лоренца */
html += makeCard('theory', "Сила Лоренца", "§30", `
<p><b>Сила Лоренца</b> — сила, действующая на электрический заряд $q$, движущийся со скоростью $\\vec{v}$ в магнитном поле $\\vec{B}$.</p>
<p style="margin-top:8px"><b>Модуль</b>:</p>
<p style="text-align:center;margin:8px 0">$$F_L = |q|\\, v\\, B\\, \\sin\\alpha$$</p>
<p>где $\\alpha$ — угол между $\\vec{v}$ и $\\vec{B}$.</p>
<p style="margin-top:10px"><b>Направление</b>:</p>
<ul style="margin:6px 0 6px 22px">
<li>Для <b>положительного</b> заряда — по <b>правилу левой руки</b> (ладонь — $\\vec{B}$ входит, 4 пальца — направление $\\vec{v}$, большой палец — $\\vec{F_L}$).</li>
<li>Для <b>отрицательного</b> заряда — <b>противоположно</b>.</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Важнейшее свойство.</b> $\\vec{F_L} \\perp \\vec{v}$ всегда, значит <b>работа силы Лоренца равна нулю</b>. Магнитное поле <b>не меняет</b> модуль скорости (и кинетическую энергию) — только <b>направление</b> движения.</p>
`);
/* THEORY 2 — Движение по окружности */
html += makeCard('rule', "Движение заряда по окружности", "§30", `
<p>Если заряд $q$ влетает в однородное магнитное поле <b>перпендикулярно</b> $\\vec{B}$ ($\\alpha = 90°$), он движется по <b>окружности</b>.</p>
<p style="margin-top:8px">Сила Лоренца играет роль <b>центростремительной</b>:</p>
<p style="text-align:center;margin:8px 0">$$|q|\\,v\\,B = \\dfrac{m v^2}{R}$$</p>
<p>Отсюда <b>радиус окружности</b>:</p>
<p style="text-align:center;margin:8px 0">$$R = \\dfrac{m v}{|q|\\,B}$$</p>
<p style="margin-top:10px"><b>Период обращения</b> (циклотронный период):</p>
<p style="text-align:center;margin:8px 0">$$T = \\dfrac{2\\pi R}{v} = \\dfrac{2\\pi m}{|q|\\,B}$$</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Удивительный факт.</b> Период $T$ <b>не зависит</b> от скорости $v$! Это используется в <b>циклотронах</b>: частицы ускоряются, но за каждый оборот всё равно проходят за одно и то же время.</p>
`);
/* THEORY 3 — Спираль и применение */
html += makeCard('example', "Спираль и применение", "§30", `
<p>Если $\\vec{v}$ направлена под произвольным углом к $\\vec{B}$ (не $0°$ и не $90°$), движение — по <b>винтовой линии</b> (спирали):</p>
<ul style="margin:6px 0 6px 22px">
<li>Компонента $v_{\\parallel}$ (вдоль $\\vec{B}$) даёт <b>прямолинейное</b> движение.</li>
<li>Компонента $v_{\\perp}$ (поперёк $\\vec{B}$) — <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> — ускорители заряженных частиц (Большой адронный коллайдер).</li>
<li><b>Масс-спектрометр</b>: ионы одинаковой энергии, но разной массы движутся по окружностям разного радиуса $R \\propto m$ — это позволяет их разделить и измерить массы.</li>
<li><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>Связь с §29.</b> Сила Ампера — это сумма сил Лоренца, действующих на все заряды-носители тока внутри проводника.</p>
`);
/* INTERACTIVE 1 — 3D-движение заряда */
html += `<div class="wg" id="p30-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Заряд в магнитном поле: окружность или прямая?</div></div>
<div class="wg-help">$\\vec{B}$ направлено в экран (×). Выбери знак заряда, скорость и угол вхождения $\\alpha$. Смотри траекторию.</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:10px;flex-wrap:wrap">
<button class="btn primary" id="p30-iv1-pos">Заряд $q > 0$</button>
<button class="btn" id="p30-iv1-neg">Заряд $q < 0$</button>
</div>
<div class="sliders">
<label>$v$: <b id="p30-iv1-vL">5.0</b> усл. <input type="range" id="p30-iv1-v" min="1" max="10" value="5" step="0.1"></label>
<label>$\\alpha$ (°): <b id="p30-iv1-aL">90</b> <input type="range" id="p30-iv1-a" min="0" max="90" value="90" step="5"></label>
</div>
<div style="display:flex;gap:6px;justify-content:center;margin-bottom:8px;flex-wrap:wrap">
<button class="btn" id="p30-iv1-pause">Пауза</button>
<button class="btn" id="p30-iv1-reset">Сброс</button>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p30-iv1-svg" viewBox="0 0 480 320" width="100%" style="height:auto"></svg>
</div>
<div id="p30-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор */
html += `<div class="wg" id="p30-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор силы Лоренца, $R$ и $T$</div></div>
<div class="wg-help">Введи параметры. Заряд $q$ — в единицах $e = 1{,}6\\cdot 10^{-19}$ Кл.</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px">
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$q$ (в $e$):<input type="number" id="p30-iv2-q" value="1" step="0.1" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$v$ (м/с):<input type="number" id="p30-iv2-v" value="1e6" step="any" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$B$ (Тл):<input type="number" id="p30-iv2-B" value="0.1" step="0.01" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$\\alpha$ (°):<input type="number" id="p30-iv2-A" value="90" step="1" min="0" max="180" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$m$ (кг):<input type="number" id="p30-iv2-m" value="9.1e-31" step="any" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
</div>
<div class="actions"><button class="btn primary" id="p30-iv2-calc">Вычислить</button></div>
<div id="p30-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.96rem;line-height:1.8;text-align:center;min-height:40px"></div>
</div>`;
/* INTERACTIVE 3 — Влияет ли поле на скорость? */
html += `<div class="wg" id="p30-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Влияет ли поле на модуль скорости?</div></div>
<div class="wg-help">6 ситуаций. Магнитное поле не меняет $|\\vec{v}|$, электрическое — меняет.</div>
<div class="score-display"><span>Задача <b id="p30-iv3-i">1</b> / 6</span><span>Очки: <b id="p30-iv3-s">0</b> / 6</span></div>
<div id="p30-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="p30-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p30-iv3-fb"></div>
<div class="actions"><button class="btn" id="p30-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр числовой */
html += `<div class="wg" id="p30-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: сила Лоренца</div></div>
<div class="wg-help">5 числовых задач. Допуск ±5%. Используй $e = 1{,}6\\cdot 10^{-19}$ Кл.</div>
<div class="score-display"><span>Задача <b id="p30-iv4-i">1</b> / 5</span><span>Очки: <b id="p30-iv4-s">0</b> / 5</span></div>
<div id="p30-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="p30-iv4-form" style="display:flex;gap:8px;justify-content:center;margin-bottom:8px;flex-wrap:wrap">
<input type="number" id="p30-iv4-inp" step="any" style="padding:8px 10px;border:1px solid var(--border);border-radius:6px;width:140px;font-size:1rem">
<button class="btn primary" id="p30-iv4-go">Проверить</button>
</div>
<div class="feedback" id="p30-iv4-fb"></div>
<div class="actions"><button class="btn" id="p30-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p29', 'p31');
html += readButton('p30');
box.innerHTML = html;
renderMath(box);
/* IV1 — Симуляция движения заряда */
(function(){
const svg = document.getElementById('p30-iv1-svg');
const out = document.getElementById('p30-iv1-out');
const bPos = document.getElementById('p30-iv1-pos');
const bNeg = document.getElementById('p30-iv1-neg');
const bPause = document.getElementById('p30-iv1-pause');
const bReset = document.getElementById('p30-iv1-reset');
const vS = document.getElementById('p30-iv1-v');
const aS = document.getElementById('p30-iv1-a');
const vL = document.getElementById('p30-iv1-vL');
const aL = document.getElementById('p30-iv1-aL');
const seen = new Set();
let _done = false;
let sign = +1;
let paused = false;
let t0 = performance.now();
let trail = [];
function setSign(s){ sign = s; bPos.classList.toggle('primary', s>0); bNeg.classList.toggle('primary', s<0); reset(); }
function reset(){ t0 = performance.now(); trail = []; }
function draw(){
const W = 480, H = 320;
const v = +vS.value, aDeg = +aS.value;
vL.textContent = v.toFixed(1);
aL.textContent = aDeg.toFixed(0);
const alpha = aDeg * Math.PI / 180;
const now = performance.now();
if(!paused){
// Эта функция всё равно вызывается через rAF, accumulator подразумевается через t0
}
const tSec = (now - t0) / 1000;
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">B в экран (×). q = '+(sign>0?'+':'')+', α='+aDeg+'°</text>';
g += PHYS.magneticFieldGrid(50, 50, 380, 230, 8, 5, 'in');
// Параметры
const cx = 240, cy = 165;
const vPerp = Math.sin(alpha) * v;
const vPar = Math.cos(alpha) * v;
// Радиус и угловая скорость (упрощённо, безразмерно)
const Rsim = Math.max(8, vPerp * 12); // px
const omega = (vPerp > 0.001) ? (sign * 0.9) : 0; // рад/с (направление: + → против часовой? Для q>0, B в экран — по часовой)
// Для q>0, B в экран: F=qv×B. Скорость вправо (+x), B=-z (в экран). F=q(x×-z)=q(+y)=вверх. Значит, частица идущая вправо испытывает силу вверх — она поворачивает вверх. Это против часовой стрелки в экранных координатах (y растёт вниз). Проверим: для CCW (против часовой в экране) с угла θ=0 → x=R, y=0 (вправо); скорость в этот момент должна быть вверх (-y_экр). dθ/dt > 0 → x=Rcos(θ),y=-Rsin(θ)? Нет, упрощу — выберу знак.
// В экранных координатах: для q>0 (B в экран) движение по часовой стрелке. Возьмём знак omega = -sign (по часовой = в экране отрицательный угол).
const dir_sign = -sign; // для anim
const ang0 = Math.PI; // начинать слева
const ang = ang0 + dir_sign * 1.2 * tSec * (vPerp/5);
// Спираль: смещение по линии B → в нашем 2D представлении сместим центр окружности вдоль некоторого направления (вверх — представим как «вдоль B», условно)
// Поскольку B в экран — vPar выходит из экрана; в 2D не виден. Покажем как точку, мигающую с прогрессом.
const px = cx + Rsim * Math.cos(ang);
const py = cy + Rsim * Math.sin(ang);
if(!paused){
trail.push([px,py]);
if(trail.length > 200) trail.shift();
}
if(aDeg === 0){
// Прямолинейное движение (v параллельно B → точка просто стоит/уходит "в экран")
g += '<text x="240" y="160" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" fill="#64748b">$\\vec{v} \\parallel \\vec{B}$ → сила не действует, движение прямолинейное.</text>'.replace('$\\vec{v} \\parallel \\vec{B}$','v ∥ B');
g += PHYS.chargeMark(cx, cy, sign, 14, sign>0?'+':'');
} else if(aDeg === 90){
// Окружность
g += '<circle cx="'+cx+'" cy="'+cy+'" r="'+Rsim.toFixed(1)+'" fill="none" stroke="#7c3aed" stroke-width="1.4" stroke-dasharray="4 3" opacity=".55"/>';
// Трейл
if(trail.length >= 2){
let path = 'M '+trail[0][0].toFixed(1)+' '+trail[0][1].toFixed(1);
for(let k=1;k<trail.length;k++) path += ' L '+trail[k][0].toFixed(1)+' '+trail[k][1].toFixed(1);
g += '<path d="'+path+'" stroke="#0891b2" stroke-width="2" fill="none" opacity=".85"/>';
}
// Текущая позиция — заряд
g += PHYS.chargeMark(px, py, sign, 11, sign>0?'+':'');
// Вектор скорости (касательная)
const vAng = ang + dir_sign * Math.PI/2;
const vEx = px + 28 * Math.cos(vAng);
const vEy = py + 28 * Math.sin(vAng);
g += PHYS.drawArrow(px, py, vEx, vEy, '#dc2626', 2, 10);
g += '<text x="'+(vEx+8)+'" y="'+(vEy-4)+'" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#dc2626">v</text>';
// Вектор силы (центростремительная — к центру)
const fEx = px + 28 * Math.cos(ang + Math.PI);
const fEy = py + 28 * Math.sin(ang + Math.PI);
g += PHYS.drawArrow(px, py, fEx, fEy, '#0891b2', 2, 10);
g += '<text x="'+(fEx-8)+'" y="'+(fEy+12)+'" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0891b2">F</text>';
} else {
// Спираль — рисуем как смещённую окружность
g += '<circle cx="'+cx+'" cy="'+cy+'" r="'+Rsim.toFixed(1)+'" fill="none" stroke="#7c3aed" stroke-width="1.2" stroke-dasharray="3 3" opacity=".4"/>';
if(trail.length >= 2){
let path = 'M '+trail[0][0].toFixed(1)+' '+trail[0][1].toFixed(1);
for(let k=1;k<trail.length;k++) path += ' L '+trail[k][0].toFixed(1)+' '+trail[k][1].toFixed(1);
g += '<path d="'+path+'" stroke="#0891b2" stroke-width="2" fill="none" opacity=".8"/>';
}
g += PHYS.chargeMark(px, py, sign, 11, sign>0?'+':'');
g += '<text x="240" y="305" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#64748b">Спираль (винтовая линия): $v_{\\perp}$ → круг, $v_{\\parallel}$ → вдоль B</text>'.replace('$v_{\\perp}$','v⊥').replace('$v_{\\parallel}$','v∥');
}
svg.innerHTML = g;
let txt = '';
if(aDeg === 0){
txt = '<b>$\\alpha = 0°$:</b> $\\vec{v} \\parallel \\vec{B}$, $F_L = 0$. Прямолинейное движение, поле не действует.';
} else if(aDeg === 90){
txt = '<b>$\\alpha = 90°$:</b> чистая окружность. $R = mv/(|q|B)$, $T = 2\\pi m/(|q|B)$ (не зависит от $v$).';
} else {
txt = '<b>$\\alpha = '+aDeg+'°$:</b> винтовая линия. $v_{\\parallel}$ вдоль $\\vec{B}$ + $v_{\\perp}$ по окружности.';
}
out.innerHTML = txt;
renderMath(out);
seen.add(sign+':'+aDeg);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p30-iv1'); bumpProgress('p30', 15); }
}
function loop(){
if(!paused) draw();
requestAnimationFrame(loop);
}
bPos.addEventListener('click', () => setSign(+1));
bNeg.addEventListener('click', () => setSign(-1));
bPause.addEventListener('click', () => { paused = !paused; bPause.textContent = paused ? 'Продолжить' : 'Пауза'; });
bReset.addEventListener('click', reset);
vS.addEventListener('input', () => { reset(); draw(); });
aS.addEventListener('input', () => { reset(); draw(); });
setSign(+1);
draw();
requestAnimationFrame(loop);
})();
/* IV2 — Калькулятор */
(function(){
const out = document.getElementById('p30-iv2-out');
const bGo = document.getElementById('p30-iv2-calc');
const inQ = document.getElementById('p30-iv2-q');
const inV = document.getElementById('p30-iv2-v');
const inB = document.getElementById('p30-iv2-B');
const inA = document.getElementById('p30-iv2-A');
const inM = document.getElementById('p30-iv2-m');
let count = 0, _done = false;
function calc(){
const qe = +inQ.value, v = +inV.value, B = +inB.value, aDeg = +inA.value, m = +inM.value;
if(![qe,v,B,aDeg,m].every(isFinite)){ out.innerHTML = '<b style="color:#dc2626">Введи все значения.</b>'; return; }
const e = PHYS.CONST.e;
const q = qe * e;
const rad = aDeg * Math.PI / 180;
const F = Math.abs(q) * v * B * Math.sin(rad);
let html = '$F_L = |q|\\,v\\,B\\,\\sin\\alpha = '+F.toExponential(3)+'$ <b>Н</b>';
if(Math.abs(aDeg - 90) < 1e-6 && Math.abs(q) > 0 && B > 0){
const R = m * v / (Math.abs(q) * B);
const T = 2 * Math.PI * m / (Math.abs(q) * B);
html += '<br>При $\\alpha = 90°$: $R = mv/(|q|B) = '+R.toExponential(3)+'$ м, $T = 2\\pi m/(|q|B) = '+T.toExponential(3)+'$ с.';
}
out.innerHTML = html;
renderMath(out);
count++;
if(!_done && count >= 3){ _done = true; addXp(10, 'p30-iv2'); bumpProgress('p30', 15); }
}
bGo.addEventListener('click', calc);
[inQ,inV,inB,inA,inM].forEach(x => x.addEventListener('keydown', e => { if(e.key==='Enter') calc(); }));
})();
/* IV3 — Влияет ли поле на скорость? */
(function(){
const OPTS = ['Меняет модуль скорости','Не меняет (только направление, или сила = 0)'];
const Q = [
{ q:'Электрон, влетевший в однородное магнитное поле перпендикулярно $\\vec{B}$.', ans:1, why:'Сила Лоренца $\\perp \\vec{v}$, работа = 0, $|\\vec{v}|$ постоянен.' },
{ q:'Электрон в однородном электрическом поле $\\vec{E}$.', ans:0, why:'Электрическое поле совершает работу, $|\\vec{v}|$ меняется.' },
{ q:'Протон движется параллельно $\\vec{B}$.', ans:1, why:'$\\alpha = 0$ → $F_L = 0$. Прямолинейное равномерное движение.' },
{ q:'Нейтрон (заряд = 0) в магнитном поле.', ans:1, why:'$q = 0$ → $F_L = 0$. Поле никак не действует.' },
{ q:'Положительный заряд в однородном поле $\\vec{B}$, $\\vec{v} \\perp \\vec{B}$.', ans:1, why:'Движение по окружности, $|\\vec{v}|$ постоянен — только направление меняется.' },
{ q:'Электрон в комбинированном поле $\\vec{E} + \\vec{B}$.', ans:0, why:'Из-за $\\vec{E}$ работа $\\ne 0$, поэтому $|\\vec{v}|$ меняется.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p30-iv3-q');
const oEl = document.getElementById('p30-iv3-opts');
const fb = document.getElementById('p30-iv3-fb');
const iEl = document.getElementById('p30-iv3-i');
const sEl = document.getElementById('p30-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p30-iv3'); bumpProgress('p30', 25); }
else if(score >= 4){ addXp(8, 'p30-iv3'); bumpProgress('p30', 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('p30-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр числовой */
(function(){
const Q = [
{ q:'$q = e$, $v = 10^6$ м/с, $B = 0{,}1$ Тл, $\\alpha = 90°$. Найди $F_L$. Введи мантиссу при $10^{-14}$ Н.', ans:1.6, tol:0.1, why:'$F_L = 1{,}6\\cdot 10^{-19}\\cdot 10^6\\cdot 0{,}1 = 1{,}6\\cdot 10^{-14}$ Н.' },
{ q:'Электрон ($m = 9{,}1\\cdot 10^{-31}$ кг) в $B = 10^{-3}$ Тл, $v = 10^7$ м/с, $\\alpha = 90°$. Найди $R$ (см).', ans:5.7, tol:0.5, why:'$R = mv/(|q|B) = 9{,}1\\cdot 10^{-31}\\cdot 10^7 / (1{,}6\\cdot 10^{-19}\\cdot 10^{-3}) \\approx 5{,}7\\cdot 10^{-2}$ м = 5,7 см.' },
{ q:'При $\\alpha = 0°$ сила Лоренца равна (в Н)?', ans:0, tol:1e-30, why:'$F_L = qvB\\sin 0° = 0$. Сила не действует.' },
{ q:'Работа силы Лоренца за период обращения частицы по окружности (в Дж)?', ans:0, tol:1e-30, why:'$\\vec{F_L} \\perp \\vec{v}$, поэтому $A = 0$ всегда.' },
{ q:'Если скорость заряда удвоить (при $\\alpha=90°$), во сколько раз изменится радиус $R$?', ans:2, tol:0.1, why:'$R = mv/(|q|B) \\propto v$ → удваивается. Период $T$ не меняется.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p30-iv4-q');
const fb = document.getElementById('p30-iv4-fb');
const iEl = document.getElementById('p30-iv4-i');
const sEl = document.getElementById('p30-iv4-s');
const inp = document.getElementById('p30-iv4-inp');
const bGo = document.getElementById('p30-iv4-go');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
inp.disabled = true; bGo.disabled = true;
if(score === Q.length){ addXp(15, 'p30-iv4'); bumpProgress('p30', 25); }
else if(score >= 3){ addXp(8, 'p30-iv4'); bumpProgress('p30', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
fb.style.display = 'none';
inp.value = ''; inp.disabled = false; bGo.disabled = false; inp.focus();
renderMath(qEl);
}
function check(){
if(inp.disabled) return;
const v = parseFloat(inp.value.replace(',','.'));
if(!isFinite(v)){ feedback(fb, false, 'Введи число.'); return; }
const tol = Math.max(Q[i].tol, Math.abs(Q[i].ans)*0.05);
const ok = Math.abs(v - Q[i].ans) <= tol;
if(ok){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].ans + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
inp.disabled = true; bGo.disabled = true;
i++;
setTimeout(show, 1900);
}
bGo.addEventListener('click', check);
inp.addEventListener('keydown', e => { if(e.key==='Enter') check(); });
document.getElementById('p30-iv4-restart').addEventListener('click', () => { i = 0; score = 0; inp.disabled=false; bGo.disabled=false; show(); });
show();
})();
wireReadBtn('p30');
}
function build_p31(){
const box = document.getElementById('p31-body');
let html = '';
/* THEORY 1 — Магнитный поток */
html += makeCard('theory', "Магнитный поток", "§31", `
<p><b>Магнитный поток</b> $\\Phi$ через плоскую площадку площадью $S$ в <b>однородном</b> магнитном поле $\\vec{B}$:</p>
<p style="text-align:center;margin:8px 0">$$\\Phi = B\\,S\\,\\cos\\alpha$$</p>
<p>где $\\alpha$ — угол между $\\vec{B}$ и <b>нормалью</b> $\\vec{n}$ к площадке.</p>
<p style="margin-top:10px"><b>Единица</b>: <b>Вебер</b> (Вб) = Тл·м².</p>
<p style="margin-top:10px"><b>Особые случаи</b>:</p>
<ul style="margin:6px 0 6px 22px">
<li>$\\alpha = 0°$ ($\\vec{B} \\parallel \\vec{n}$): $\\Phi_{max} = BS$.</li>
<li>$\\alpha = 90°$ ($\\vec{B} \\perp \\vec{n}$, поле параллельно площадке): $\\Phi = 0$.</li>
<li>$\\alpha = 180°$: $\\Phi = -BS$ (отрицательный поток).</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Наглядно.</b> Можно представлять $\\Phi$ как «количество силовых линий, пронизывающих площадку».</p>
`);
/* THEORY 2 — Явление электромагнитной индукции */
html += makeCard('rule', "Явление электромагнитной индукции", "§31", `
<p><b>Электромагнитная индукция</b> — возникновение электрического тока в замкнутом проводящем контуре при <b>изменении</b> магнитного потока через него.</p>
<p style="margin-top:8px">Открыта <b>Майклом Фарадеем</b> в 1831 году.</p>
<p style="margin-top:10px"><b>Главный принцип</b>: ток в контуре возникает <b>только</b> при изменении $\\Phi$, а не от самого $\\Phi$. Если магнит неподвижен в катушке — тока нет.</p>
<p style="margin-top:10px">Возникший ток называется <b>индукционным</b>. ЭДС, которая его вызывает — <b>ЭДС индукции</b> $\\mathcal{E}_i$.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Ключ.</b> Чем <b>быстрее</b> меняется $\\Phi$ — тем <b>больше</b> индукционный ток.</p>
`);
/* THEORY 3 — Опыты Фарадея и способы изменения Ф */
html += makeCard('example', "Опыты Фарадея и способы изменения Ф", "§31", `
<p><b>Классические опыты Фарадея</b>:</p>
<ol style="margin:6px 0 6px 22px">
<li><b>Магнит вдвигают в катушку</b> → возникает индукционный ток (одного знака).</li>
<li><b>Магнит выдвигают</b> → ток противоположного знака.</li>
<li><b>Магнит ближе к катушке = больше скорость изменения $\\Phi$</b> → больше ток.</li>
<li><b>Без движения</b> — тока нет.</li>
</ol>
<p style="margin-top:10px"><b>Способы изменения $\\Phi$</b>:</p>
<ul style="margin:6px 0 6px 22px">
<li><b>Изменение $B$</b>: движение магнита, изменение тока в соседней катушке.</li>
<li><b>Изменение $S$</b>: деформация контура, перемещение проводника-перемычки.</li>
<li><b>Изменение $\\alpha$</b>: поворот контура в поле (основа работы генератора).</li>
</ul>
<p style="margin-top:10px"><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>
<li><b>Микрофоны</b> и звукосниматели.</li>
</ul>
`);
/* INTERACTIVE 1 — Магнитный поток через рамку */
html += `<div class="wg" id="p31-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Магнитный поток через рамку</div></div>
<div class="wg-help">Меняй $B$, $S$ и угол $\\alpha$ между $\\vec{B}$ и нормалью $\\vec{n}$ к рамке. Смотри, как меняется поток $\\Phi = BS\\cos\\alpha$.</div>
<div class="sliders">
<label>$B$ (Тл): <b id="p31-iv1-BL">1.0</b> <input type="range" id="p31-iv1-B" min="0" max="2" value="1" step="0.05"></label>
<label>$S$ (м²): <b id="p31-iv1-SL">0.20</b> <input type="range" id="p31-iv1-S" min="0.01" max="1" value="0.2" step="0.01"></label>
<label>$\\alpha$ (°): <b id="p31-iv1-aL">30</b> <input type="range" id="p31-iv1-a" min="0" max="180" value="30" step="5"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p31-iv1-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p31-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.96rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Опыт Фарадея */
html += `<div class="wg" id="p31-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Симуляция опыта Фарадея</div></div>
<div class="wg-help">Двигай магнит к катушке и от неё. Когда поток меняется — в катушке возникает индукционный ток. Когда магнит неподвижен — тока нет.</div>
<div class="sliders">
<label>Положение магнита: <b id="p31-iv2-xL">0.20</b> <input type="range" id="p31-iv2-x" min="-0.50" max="0.30" value="-0.20" step="0.01"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p31-iv2-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p31-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.96rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 3 — Возникает ли индукционный ток? */
html += `<div class="wg" id="p31-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Возникает ли индукционный ток?</div></div>
<div class="wg-help">6 ситуаций. Ток возникает только если меняется магнитный поток $\\Phi$ через контур.</div>
<div class="score-display"><span>Задача <b id="p31-iv3-i">1</b> / 6</span><span>Очки: <b id="p31-iv3-s">0</b> / 6</span></div>
<div id="p31-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="p31-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p31-iv3-fb"></div>
<div class="actions"><button class="btn" id="p31-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр */
html += `<div class="wg" id="p31-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр магнитного потока</div></div>
<div class="wg-help">5 числовых задач. Допуск ±5%. Используй $\\Phi = BS\\cos\\alpha$.</div>
<div class="score-display"><span>Задача <b id="p31-iv4-i">1</b> / 5</span><span>Очки: <b id="p31-iv4-s">0</b> / 5</span></div>
<div id="p31-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="p31-iv4-form" style="display:flex;gap:8px;justify-content:center;margin-bottom:8px;flex-wrap:wrap">
<input type="number" id="p31-iv4-inp" step="any" style="padding:8px 10px;border:1px solid var(--border);border-radius:6px;width:140px;font-size:1rem">
<button class="btn primary" id="p31-iv4-go">Проверить</button>
</div>
<div class="feedback" id="p31-iv4-fb"></div>
<div class="actions"><button class="btn" id="p31-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p30', 'p32');
html += readButton('p31');
box.innerHTML = html;
renderMath(box);
/* IV1 — Магнитный поток через рамку */
(function(){
const svg = document.getElementById('p31-iv1-svg');
const out = document.getElementById('p31-iv1-out');
const bS = document.getElementById('p31-iv1-B');
const sS = document.getElementById('p31-iv1-S');
const aS = document.getElementById('p31-iv1-a');
const bL = document.getElementById('p31-iv1-BL');
const sL = document.getElementById('p31-iv1-SL');
const aL = document.getElementById('p31-iv1-aL');
const seen = new Set();
let _done = false;
function draw(){
const W = 480, H = 280;
const B = +bS.value, S = +sS.value, aDeg = +aS.value;
bL.textContent = B.toFixed(2);
sL.textContent = S.toFixed(2);
aL.textContent = aDeg.toFixed(0);
const alpha = aDeg * Math.PI / 180;
const Phi = B * S * Math.cos(alpha);
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">B вправо →. Угол между B и нормалью n = '+aDeg+'°</text>';
// Силовые линии поля B (горизонтальные стрелки слева → направо)
for(let i=0;i<5;i++){
const yL = 60 + i*40;
g += PHYS.drawArrow(40, yL, 440, yL, '#7c3aed', 1.3, 8);
}
// подпись B
g += '<text x="450" y="148" font-family="JetBrains Mono,monospace" font-size="14" font-weight="700" fill="#7c3aed">B</text>';
// Рамка (вид сверху, поворот вокруг вертикальной оси на угол alpha)
// Ширина проекции рамки = w0 * |sin(alpha)| — это «как мы видим её сбоку». Но удобнее
// показывать рамку с нормалью под углом alpha к B. Изобразим рамку как ромб (3D-проекция).
const cx = 240, cy = 150;
const halfW = 60; // полуширина рамки в плоскости поля
const halfH = 50; // высота рамки (не меняется)
// При alpha=0 рамка видна как горизонтальная линия (поток max); при alpha=90 рамка перпенд. → видна как прямоугольник
const projW = Math.max(2, halfW * Math.abs(Math.sin(alpha)));
// верхний и нижний край рамки — горизонтальные линии длиной 2*projW
// боковые стороны — наклонные (для эффекта 3D)
const xL = cx - projW, xR = cx + projW;
// Заполнение рамки
g += '<polygon points="'+xL.toFixed(1)+','+(cy-halfH).toFixed(1)+' '+xR.toFixed(1)+','+(cy-halfH).toFixed(1)+' '+xR.toFixed(1)+','+(cy+halfH).toFixed(1)+' '+xL.toFixed(1)+','+(cy+halfH).toFixed(1)+'" fill="rgba(8,145,178,0.13)" stroke="#0891b2" stroke-width="2.2"/>';
// Нормаль n
const nLen = 60;
// Направление нормали — повёрнуто на alpha от B (B вдоль +x). Тогда n = (cos(alpha), sin(alpha)).
const nx = cx + nLen * Math.cos(alpha);
const ny = cy - nLen * Math.sin(alpha);
g += PHYS.drawArrow(cx, cy, nx, ny, '#dc2626', 2.4, 11);
g += '<text x="'+(nx+8)+'" y="'+(ny-4)+'" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#dc2626">n</text>';
// Дуга угла между n и B (B = +x)
const arcR = 28;
const a1 = 0, a2 = -alpha; // угол в SVG-координатах (y вниз)
const aStart = Math.min(a1, a2), aEnd = Math.max(a1, a2);
g += '<path d="M '+(cx+arcR)+' '+cy+' A '+arcR+' '+arcR+' 0 0 0 '+(cx+arcR*Math.cos(a2)).toFixed(1)+' '+(cy+arcR*Math.sin(a2)).toFixed(1)+'" fill="none" stroke="#0f172a" stroke-width="1.4"/>';
g += '<text x="'+(cx+arcR+8)+'" y="'+(cy - 4).toFixed(1)+'" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">α</text>';
// Подпись параметров рамки
g += '<text x="240" y="248" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="#0f172a">B = '+B.toFixed(2)+' Тл, S = '+S.toFixed(2)+' м², α = '+aDeg+'°</text>';
g += '<text x="240" y="266" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="14" font-weight="700" fill="#0891b2">Φ = '+(Phi*1000).toFixed(2)+' мВб</text>';
svg.innerHTML = g;
let note = '';
if(aDeg === 0) note = '<b>α = 0°:</b> $\\vec{B} \\parallel \\vec{n}$ — поток максимален: $\\Phi_{max} = BS = '+(B*S*1000).toFixed(2)+'$ мВб.';
else if(aDeg === 90) note = '<b>α = 90°:</b> $\\vec{B} \\perp \\vec{n}$ — линии поля скользят вдоль рамки. $\\Phi = 0$.';
else if(aDeg === 180) note = '<b>α = 180°:</b> $\\vec{B}$ и $\\vec{n}$ направлены противоположно. $\\Phi = -BS = '+(-B*S*1000).toFixed(2)+'$ мВб.';
else note = '<b>α = '+aDeg+'°:</b> $\\Phi = BS\\cos\\alpha = '+B.toFixed(2)+'\\cdot '+S.toFixed(2)+'\\cdot \\cos '+aDeg+'° = '+(Phi*1000).toFixed(2)+'$ мВб.';
out.innerHTML = note;
renderMath(out);
const key = (B>0.5?'B+':'B-') + ':' + (S>0.3?'S+':'S-') + ':' + Math.round(aDeg/30);
seen.add(key);
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p31-iv1'); bumpProgress('p31', 15); }
}
bS.addEventListener('input', draw);
sS.addEventListener('input', draw);
aS.addEventListener('input', draw);
draw();
})();
/* IV2 — Опыт Фарадея */
(function(){
const svg = document.getElementById('p31-iv2-svg');
const out = document.getElementById('p31-iv2-out');
const xS = document.getElementById('p31-iv2-x');
const xL = document.getElementById('p31-iv2-xL');
let lastX = +xS.value, lastT = performance.now();
let movesPos = 0, movesNeg = 0, _done = false;
function draw(){
const W = 480, H = 280;
const x = +xS.value;
xL.textContent = x.toFixed(2);
const now = performance.now();
const dt = Math.max(0.01, (now - lastT) / 1000);
const dx = x - lastX;
const vel = dx / dt; // м/с (условно)
lastX = x; lastT = now;
// Катушка справа: центр около (340, 140), длина 130
const coilCx = 340, coilCy = 140, coilL = 130, coilR = 40;
// Магнит — прямоугольник; позиция: магнит-середина по x
// Сопоставим x ∈ [-0.5 .. 0.3] м со SVG-координатой от 70 (далеко слева) до 320 (входит в катушку)
const magW = 90, magH = 40;
const magCx = 70 + (x + 0.5) * (320 - 70) / 0.8;
const magCy = coilCy;
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 turns = 10;
for(let i=0;i<turns;i++){
const cx = coilCx - coilL/2 + i * (coilL / (turns - 1));
g += '<ellipse cx="'+cx.toFixed(1)+'" cy="'+coilCy+'" rx="14" ry="'+coilR+'" fill="none" stroke="#0f172a" stroke-width="1.6"/>';
}
// Боковые «проводки» катушки к амперметру
g += PHYS.wire(coilCx - coilL/2 - 4, coilCy + coilR, coilCx - coilL/2 - 4, coilCy + coilR + 30);
g += PHYS.wire(coilCx + coilL/2 + 4, coilCy + coilR, coilCx + coilL/2 + 4, coilCy + coilR + 30);
g += PHYS.wire(coilCx - coilL/2 - 4, coilCy + coilR + 30, 405, coilCy + coilR + 30);
g += PHYS.wire(coilCx + coilL/2 + 4, coilCy + coilR + 30, 435, coilCy + coilR + 30);
// Амперметр
g += PHYS.ammeterSymbol(420, coilCy + coilR + 30, 14);
// Магнит: N (красный) | S (синий)
g += '<rect x="'+(magCx - magW/2).toFixed(1)+'" y="'+(magCy - magH/2).toFixed(1)+'" width="'+(magW/2).toFixed(1)+'" height="'+magH+'" fill="#fecaca" stroke="#dc2626" stroke-width="1.8"/>';
g += '<rect x="'+magCx.toFixed(1)+'" y="'+(magCy - magH/2).toFixed(1)+'" width="'+(magW/2).toFixed(1)+'" height="'+magH+'" fill="#bfdbfe" stroke="#2563eb" stroke-width="1.8"/>';
g += '<text x="'+(magCx - magW/4).toFixed(1)+'" y="'+(magCy + 5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="800" fill="#dc2626">N</text>';
g += '<text x="'+(magCx + magW/4).toFixed(1)+'" y="'+(magCy + 5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="800" fill="#2563eb">S</text>';
// Поле магнита (стрелка из N) — направление движения магнита
if(Math.abs(vel) > 0.02){
const dir = vel > 0 ? 1 : -1;
const arrFromX = magCx + dir * (magW/2 + 6);
const arrToX = arrFromX + dir * 40;
g += PHYS.drawArrow(arrFromX, magCy - 30, arrToX, magCy - 30, '#0f172a', 2, 9);
g += '<text x="'+((arrFromX+arrToX)/2).toFixed(1)+'" y="'+(magCy - 36)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#0f172a">движение</text>';
}
// Стрелка амперметра + ток в катушке
let curSign = 0; // -1, 0, +1
let curMag = 0;
if(Math.abs(vel) > 0.02){
// приближение N-полюса справа → поток через катушку растёт. Индукционный ток создаёт поле S к магниту,
// то есть ток виден против часовой стрелки (со стороны магнита). Возьмём знак произвольно.
curSign = vel > 0 ? +1 : -1;
curMag = Math.min(1, Math.abs(vel) * 8);
}
// Стрелка на амперметре
const ndlAng = curSign * curMag * 0.7; // рад от вертикали
const ax = 420, ay = coilCy + coilR + 30;
const ndlEx = ax + 11 * Math.sin(ndlAng);
const ndlEy = ay - 11 * Math.cos(ndlAng);
g += '<line x1="'+ax+'" y1="'+ay+'" x2="'+ndlEx.toFixed(1)+'" y2="'+ndlEy.toFixed(1)+'" stroke="#0f172a" stroke-width="2"/>';
// Стрелочки тока в первом витке катушки
if(curMag > 0){
const dirColor = curSign > 0 ? '#dc2626' : '#0891b2';
const ccx = coilCx, ccy = coilCy;
const r = coilR + 4;
// Верх витка — стрелка вправо/влево
const dxArr = curSign > 0 ? +14 : -14;
g += PHYS.drawArrow(ccx - dxArr/2, ccy - r, ccx + dxArr/2, ccy - r, dirColor, 2, 8);
g += PHYS.drawArrow(ccx + dxArr/2, ccy + r, ccx - dxArr/2, ccy + r, dirColor, 2, 8);
g += '<text x="240" y="266" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="'+dirColor+'">Индукционный ток '+(curSign>0?'→':'←')+'</text>';
} else {
g += '<text x="240" y="266" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="700" fill="#64748b">Тока нет — магнит покоится</text>';
}
svg.innerHTML = g;
let note = '';
if(curMag < 0.05) note = '<b>Магнит покоится.</b> Поток $\\Phi$ через катушку не меняется → индукционного тока <b>нет</b>.';
else if(curSign > 0) note = '<b>Магнит приближается</b> к катушке. Поток $\\Phi$ растёт → возникает индукционный ток (одного знака).';
else note = '<b>Магнит удаляется</b> от катушки. Поток $\\Phi$ убывает → возникает индукционный ток <b>противоположного</b> знака.';
out.innerHTML = note;
renderMath(out);
if(curSign > 0) movesPos = 1;
if(curSign < 0) movesNeg = 1;
if(!_done && movesPos && movesNeg){ _done = true; addXp(10, 'p31-iv2'); bumpProgress('p31', 15); }
}
xS.addEventListener('input', draw);
// Тикать чаще, чтобы дать стрелке амперметра «остыть»
setInterval(draw, 250);
draw();
})();
/* IV3 — Возникает ли индукционный ток? */
(function(){
const OPTS = ['Возникает', 'Не возникает'];
const Q = [
{ q:'Магнит вдвигают в катушку.', ans:0, why:'$\\Phi$ через катушку меняется → ток есть.' },
{ q:'Магнит покоится в катушке.', ans:1, why:'$\\Phi$ не меняется → тока нет.' },
{ q:'Катушка вращается в постоянном магнитном поле.', ans:0, why:'Меняется угол $\\alpha$ → меняется $\\Phi$ → ток есть. (Принцип генератора.)' },
{ q:'Ток в соседней катушке плавно растёт.', ans:0, why:'$B$ растёт → $\\Phi$ меняется → ток в нашей катушке есть.' },
{ q:'В соседней катушке течёт постоянный ток.', ans:1, why:'$B$ постоянно → $\\Phi$ не меняется → тока нет.' },
{ q:'Площадь контура в постоянном поле плавно уменьшается.', ans:0, why:'$S$ меняется → $\\Phi = BS\\cos\\alpha$ меняется → ток есть.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p31-iv3-q');
const oEl = document.getElementById('p31-iv3-opts');
const fb = document.getElementById('p31-iv3-fb');
const iEl = document.getElementById('p31-iv3-i');
const sEl = document.getElementById('p31-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p31-iv3'); bumpProgress('p31', 25); }
else if(score >= 4){ addXp(8, 'p31-iv3'); bumpProgress('p31', 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('p31-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'$B = 0{,}5$ Тл, $S = 0{,}04$ м², $\\alpha = 0°$. Найди $\\Phi$ (в <b>мВб</b>).', ans:20, tol:1, why:'$\\Phi = BS\\cos 0° = 0{,}5\\cdot 0{,}04 = 0{,}02$ Вб = 20 мВб.' },
{ q:'То же поле и площадь, но $\\alpha = 60°$. Найди $\\Phi$ (в <b>мВб</b>).', ans:10, tol:0.6, why:'$\\Phi = 20\\cdot \\cos 60° = 20\\cdot 0{,}5 = 10$ мВб.' },
{ q:'$\\alpha = 90°$. Чему равен $\\Phi$ (в мВб)?', ans:0, tol:0.01, why:'$\\cos 90° = 0$ → $\\Phi = 0$. Линии поля скользят вдоль рамки.' },
{ q:'При $B = 0$ Тл — каким будет $\\Phi$ (в Вб)?', ans:0, tol:0.01, why:'Нет поля → нет потока. $\\Phi = 0$.' },
{ q:'Если площадь $S$ удвоить (при тех же $B$ и $\\alpha$), во сколько раз изменится $\\Phi$?', ans:2, tol:0.1, why:'$\\Phi \\propto S$ → удвоится.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p31-iv4-q');
const fb = document.getElementById('p31-iv4-fb');
const iEl = document.getElementById('p31-iv4-i');
const sEl = document.getElementById('p31-iv4-s');
const inp = document.getElementById('p31-iv4-inp');
const bGo = document.getElementById('p31-iv4-go');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
inp.disabled = true; bGo.disabled = true;
if(score === Q.length){ addXp(15, 'p31-iv4'); bumpProgress('p31', 25); }
else if(score >= 3){ addXp(8, 'p31-iv4'); bumpProgress('p31', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
fb.style.display = 'none';
inp.value = ''; inp.disabled = false; bGo.disabled = false; inp.focus();
renderMath(qEl);
}
function check(){
if(inp.disabled) return;
const v = parseFloat(inp.value.replace(',','.'));
if(!isFinite(v)){ feedback(fb, false, 'Введи число.'); return; }
const tol = Math.max(Q[i].tol, Math.abs(Q[i].ans)*0.05);
const ok = Math.abs(v - Q[i].ans) <= tol;
if(ok){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].ans + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
inp.disabled = true; bGo.disabled = true;
i++;
setTimeout(show, 1900);
}
bGo.addEventListener('click', check);
inp.addEventListener('keydown', e => { if(e.key==='Enter') check(); });
document.getElementById('p31-iv4-restart').addEventListener('click', () => { i = 0; score = 0; inp.disabled=false; bGo.disabled=false; show(); });
show();
})();
wireReadBtn('p31');
}
function build_p32(){
const box = document.getElementById('p32-body');
let html = '';
/* THEORY 1 — Правило Ленца */
html += makeCard('theory', "Правило Ленца", "§32", `
<p><b>Правило Ленца</b> (1834): <b>индукционный ток направлен так, чтобы своим магнитным полем препятствовать изменению магнитного потока, его вызвавшему</b>.</p>
<p style="margin-top:10px">Это правило отражает <b>закон сохранения энергии</b>: если бы ток усиливал изменение, можно было бы получать неограниченную энергию из ничего.</p>
<p style="margin-top:10px"><b>Пример 1</b>: магнит N-полюсом <b>приближается</b> к катушке.</p>
<ul style="margin:6px 0 6px 22px">
<li>Поток $\\Phi$ через катушку растёт.</li>
<li>Индукционный ток создаёт <b>встречное</b> магнитное поле (его «N-полюс» обращён к магниту).</li>
<li>Магнит <b>отталкивается</b> — приходится приложить силу, чтобы продолжать движение.</li>
<li>Эта работа преобразуется в энергию индукционного тока.</li>
</ul>
<p style="margin-top:10px"><b>Пример 2</b>: магнит <b>удаляется</b>. Поток убывает. Индукционный ток создаёт поле, <b>поддерживающее</b> убывающий поток — катушка <b>притягивает</b> магнит.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Короткая мнемоника.</b> «Природа сопротивляется изменению»: ток всегда <b>против</b> того, кто его вызвал.</p>
`);
/* THEORY 2 — Закон Фарадея */
html += makeCard('rule', "Закон электромагнитной индукции (Фарадея)", "§32", `
<p>Величина <b>ЭДС индукции</b> в контуре равна <b>скорости изменения</b> магнитного потока:</p>
<p style="text-align:center;margin:8px 0">$$\\mathcal{E}_i = -\\dfrac{\\Delta\\Phi}{\\Delta t}$$</p>
<p>Знак «минус» — математическая запись правила Ленца: ЭДС <b>противодействует</b> изменению потока.</p>
<p style="margin-top:10px"><b>Для катушки из $N$ витков</b>:</p>
<p style="text-align:center;margin:8px 0">$$\\mathcal{E}_i = -N\\,\\dfrac{\\Delta\\Phi}{\\Delta t}$$</p>
<p>Сила индукционного тока: $I_i = \\mathcal{E}_i / R$, где $R$ — сопротивление катушки.</p>
<p style="margin-top:10px"><b>Единица</b>: вольт (В).</p>
<p style="margin-top:10px"><b>Большая ЭДС</b> возникает при:</p>
<ul style="margin:6px 0 6px 22px">
<li>быстром изменении $\\Phi$ (резкое движение, быстрое вращение);</li>
<li>большом числе витков $N$.</li>
</ul>
`);
/* THEORY 3 — Применение и пример */
html += makeCard('example', "Применение и пример", "§32", `
<p><b>Генератор переменного тока.</b> Рамка вращается в магнитном поле, угол $\\alpha$ непрерывно меняется: $\\Phi = BS\\cos(\\omega t)$. Тогда ЭДС:</p>
<p style="text-align:center;margin:8px 0">$$\\mathcal{E}_i = -\\dfrac{d\\Phi}{dt} = BS\\omega\\sin(\\omega t)$$</p>
<p>Это <b>переменная синусоидальная</b> ЭДС. Так работают электростанции.</p>
<p style="margin-top:10px"><b>Трансформатор.</b> Переменный ток в первичной катушке создаёт переменный $\\Phi$ через сердечник → во вторичной катушке возникает ЭДС. Отношение напряжений равно отношению чисел витков:</p>
<p style="text-align:center;margin:8px 0">$$\\dfrac{U_2}{U_1} = \\dfrac{N_2}{N_1}$$</p>
<p style="margin-top:10px"><b>Пример расчёта.</b> За $\\Delta t = 0{,}1$ с магнитный поток через катушку из $N = 50$ витков изменился с $0$ до $0{,}4$ Вб. Найди модуль ЭДС индукции.</p>
<p style="text-align:center;margin:8px 0">$$|\\mathcal{E}_i| = N\\,\\dfrac{|\\Delta\\Phi|}{\\Delta t} = 50\\cdot \\dfrac{0{,}4}{0{,}1} = 200 \\text{ В}$$</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Вывод.</b> Индукция позволяет получать высокие напряжения «из ничего» — но не нарушая закона сохранения энергии: работу совершает тот, кто меняет $\\Phi$.</p>
`);
/* INTERACTIVE 1 — Правило Ленца */
html += `<div class="wg" id="p32-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Правило Ленца в действии</div></div>
<div class="wg-help">Двигай магнит к катушке и от неё. Индукционный ток создаёт поле, <b>препятствующее</b> изменению потока — магнит чувствует силу отталкивания (приближается) или притяжения (удаляется).</div>
<div class="sliders">
<label>Положение магнита: <b id="p32-iv1-xL">0.20</b> <input type="range" id="p32-iv1-x" min="-0.50" max="0.30" value="-0.20" step="0.01"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p32-iv1-svg" viewBox="0 0 480 280" width="100%" style="height:auto"></svg>
</div>
<div id="p32-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.96rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор ЭДС */
html += `<div class="wg" id="p32-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор ЭДС индукции</div></div>
<div class="wg-help">$|\\mathcal{E}_i| = N\\,|\\Delta\\Phi|/\\Delta t$. Введи число витков, изменение потока и время.</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:10px">
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$N$ (витков):<input type="number" id="p32-iv2-N" value="100" step="1" min="1" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$\\Delta\\Phi$ (Вб):<input type="number" id="p32-iv2-dP" value="0.01" step="any" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$\\Delta t$ (с):<input type="number" id="p32-iv2-dt" value="0.1" step="any" min="0" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
</div>
<div class="actions"><button class="btn primary" id="p32-iv2-calc">Вычислить $|\\mathcal{E}_i|$</button></div>
<div id="p32-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.98rem;line-height:1.8;text-align:center;min-height:40px"></div>
</div>`;
/* INTERACTIVE 3 — Куда направлен ток? */
html += `<div class="wg" id="p32-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Куда направлен индукционный ток?</div></div>
<div class="wg-help">По правилу Ленца ток своим полем <b>противодействует</b> изменению потока (рост → встречное поле; убывание → поддерживающее).</div>
<div class="score-display"><span>Задача <b id="p32-iv3-i">1</b> / 6</span><span>Очки: <b id="p32-iv3-s">0</b> / 6</span></div>
<div id="p32-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="p32-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p32-iv3-fb"></div>
<div class="actions"><button class="btn" id="p32-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр */
html += `<div class="wg" id="p32-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: закон Фарадея</div></div>
<div class="wg-help">5 задач. Допуск ±5%. Используй $|\\mathcal{E}_i| = N\\,|\\Delta\\Phi|/\\Delta t$.</div>
<div class="score-display"><span>Задача <b id="p32-iv4-i">1</b> / 5</span><span>Очки: <b id="p32-iv4-s">0</b> / 5</span></div>
<div id="p32-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="p32-iv4-form" style="display:flex;gap:8px;justify-content:center;margin-bottom:8px;flex-wrap:wrap">
<input type="number" id="p32-iv4-inp" step="any" style="padding:8px 10px;border:1px solid var(--border);border-radius:6px;width:140px;font-size:1rem">
<button class="btn primary" id="p32-iv4-go">Проверить</button>
</div>
<div class="feedback" id="p32-iv4-fb"></div>
<div class="actions"><button class="btn" id="p32-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p31', 'p33');
html += readButton('p32');
box.innerHTML = html;
renderMath(box);
/* IV1 — Правило Ленца */
(function(){
const svg = document.getElementById('p32-iv1-svg');
const out = document.getElementById('p32-iv1-out');
const xS = document.getElementById('p32-iv1-x');
const xL = document.getElementById('p32-iv1-xL');
let lastX = +xS.value, lastT = performance.now();
let movesPos = 0, movesNeg = 0, _done = false;
function draw(){
const W = 480, H = 280;
const x = +xS.value;
xL.textContent = x.toFixed(2);
const now = performance.now();
const dt = Math.max(0.01, (now - lastT) / 1000);
const dx = x - lastX;
const vel = dx / dt;
lastX = x; lastT = now;
// Катушка слева, магнит справа подъезжает влево (или удаляется вправо)
const coilCx = 160, coilCy = 140, coilL = 120, coilR = 40;
const magW = 90, magH = 40;
// x: -0.5..0.3, отображаем магнит между 430 (далеко справа) и 240 (внутри катушки правой стороной)
const magCx = 430 - (x + 0.5) * (430 - 240) / 0.8;
const magCy = coilCy;
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 turns = 9;
for(let i=0;i<turns;i++){
const cx = coilCx - coilL/2 + i * (coilL / (turns - 1));
g += '<ellipse cx="'+cx.toFixed(1)+'" cy="'+coilCy+'" rx="12" ry="'+coilR+'" fill="none" stroke="#0f172a" stroke-width="1.6"/>';
}
// Магнит: левая сторона = N (красный, обращён к катушке), правая = S (синий)
g += '<rect x="'+(magCx - magW/2).toFixed(1)+'" y="'+(magCy - magH/2).toFixed(1)+'" width="'+(magW/2).toFixed(1)+'" height="'+magH+'" fill="#fecaca" stroke="#dc2626" stroke-width="1.8"/>';
g += '<rect x="'+magCx.toFixed(1)+'" y="'+(magCy - magH/2).toFixed(1)+'" width="'+(magW/2).toFixed(1)+'" height="'+magH+'" fill="#bfdbfe" stroke="#2563eb" stroke-width="1.8"/>';
g += '<text x="'+(magCx - magW/4).toFixed(1)+'" y="'+(magCy + 5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="800" fill="#dc2626">N</text>';
g += '<text x="'+(magCx + magW/4).toFixed(1)+'" y="'+(magCy + 5).toFixed(1)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="800" fill="#2563eb">S</text>';
// Направление движения магнита (стрелка над магнитом)
const moving = Math.abs(vel) > 0.02;
// приближение: vel > 0 (x растёт) → магнит едет влево к катушке
const approaching = vel > 0.02;
const receding = vel < -0.02;
if(moving){
const dir = approaching ? -1 : +1; // экранно: влево или вправо
const arrFromX = magCx + dir * (magW/2 + 6);
const arrToX = arrFromX + dir * 40;
g += PHYS.drawArrow(arrFromX, magCy - 32, arrToX, magCy - 32, '#0f172a', 2, 9);
g += '<text x="'+((arrFromX+arrToX)/2).toFixed(1)+'" y="'+(magCy - 38)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#0f172a">движение</text>';
}
// Сила Ленца на магнит (отталкивание при приближ., притяжение при удалении)
// отталкивание: вправо (от катушки); притяжение: влево (к катушке)
if(moving){
const forceDir = approaching ? +1 : -1;
const fLabel = approaching ? 'отталкивание' : 'притяжение';
const fColor = approaching ? '#dc2626' : '#0891b2';
const fFromX = magCx + (forceDir > 0 ? +magW/2 + 6 : -magW/2 - 6);
const fToX = fFromX + forceDir * 36;
g += PHYS.drawArrow(fFromX, magCy + 36, fToX, magCy + 36, fColor, 2.4, 10);
g += '<text x="'+((fFromX+fToX)/2).toFixed(1)+'" y="'+(magCy + 56)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="'+fColor+'">F ('+fLabel+')</text>';
}
// Индукционный ток в витках катушки (стрелочки)
let curSign = 0;
if(approaching) curSign = +1;
else if(receding) curSign = -1;
if(curSign !== 0){
const dirColor = '#0891b2';
const ccx = coilCx, ccy = coilCy;
const r = coilR + 4;
const dxArr = curSign > 0 ? +14 : -14;
g += PHYS.drawArrow(ccx - dxArr/2, ccy - r, ccx + dxArr/2, ccy - r, dirColor, 2, 8);
g += PHYS.drawArrow(ccx + dxArr/2, ccy + r, ccx - dxArr/2, ccy + r, dirColor, 2, 8);
g += '<text x="'+coilCx+'" y="'+(coilCy - r - 8)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="'+dirColor+'">индукц. ток</text>';
}
// Эквивалентный «магнит-катушка» — какой полюс смотрит на магнит
if(curSign !== 0){
// приближение: на магнит смотрит N (отталкивает) → правая сторона катушки = N
// удаление: на магнит смотрит S (притягивает) → правая сторона катушки = S
const eqPole = approaching ? 'N' : 'S';
const eqColor = approaching ? '#dc2626' : '#2563eb';
g += '<text x="'+(coilCx + coilL/2 + 10)+'" y="'+(coilCy + 28)+'" font-family="Inter,sans-serif" font-size="13" font-weight="800" fill="'+eqColor+'">↔ '+eqPole+'</text>';
}
svg.innerHTML = g;
let note = '';
if(!moving) note = '<b>Магнит покоится.</b> $\\Phi$ не меняется — индукционного тока и силы нет.';
else if(approaching) note = '<b>Магнит приближается</b> N-полюсом. $\\Phi$ растёт → индукционный ток создаёт <b>встречное</b> поле (N навстречу N). Катушка <b>отталкивает</b> магнит.';
else note = '<b>Магнит удаляется.</b> $\\Phi$ убывает → индукционный ток создаёт <b>поддерживающее</b> поле (S к N). Катушка <b>притягивает</b> магнит.';
out.innerHTML = note;
renderMath(out);
if(approaching) movesPos = 1;
if(receding) movesNeg = 1;
if(!_done && movesPos && movesNeg){ _done = true; addXp(10, 'p32-iv1'); bumpProgress('p32', 15); }
}
xS.addEventListener('input', draw);
setInterval(draw, 250);
draw();
})();
/* IV2 — Калькулятор ЭДС индукции */
(function(){
const out = document.getElementById('p32-iv2-out');
const bGo = document.getElementById('p32-iv2-calc');
const inN = document.getElementById('p32-iv2-N');
const inP = document.getElementById('p32-iv2-dP');
const inT = document.getElementById('p32-iv2-dt');
let count = 0, _done = false;
function calc(){
const N = +inN.value, dP = +inP.value, dt = +inT.value;
if(![N,dP,dt].every(isFinite) || dt <= 0 || N < 1){
out.innerHTML = '<b style="color:#dc2626">Проверь ввод: $N \\ge 1$, $\\Delta t > 0$.</b>';
renderMath(out);
return;
}
const E = N * Math.abs(dP) / dt;
let html = '$|\\mathcal{E}_i| = N\\,\\dfrac{|\\Delta\\Phi|}{\\Delta t} = '+N+'\\cdot \\dfrac{'+Math.abs(dP)+'}{'+dt+'} = $ <b>'+E.toFixed(3)+' В</b>';
// Скорость изменения
html += '<br><span style="font-size:.9rem;color:#64748b">Скорость изменения потока: $\\Delta\\Phi/\\Delta t = '+(Math.abs(dP)/dt).toFixed(4)+'$ Вб/с.</span>';
out.innerHTML = html;
renderMath(out);
count++;
if(!_done && count >= 3){ _done = true; addXp(10, 'p32-iv2'); bumpProgress('p32', 15); }
}
bGo.addEventListener('click', calc);
[inN,inP,inT].forEach(x => x.addEventListener('keydown', e => { if(e.key==='Enter') calc(); }));
})();
/* IV3 — Куда направлен индукционный ток? */
(function(){
const OPTS = ['Противодействует (встречное поле)', 'Усиливает (поддерживающее поле)'];
const Q = [
{ q:'Магнит N-полюсом <b>приближается</b> к катушке.', ans:0, why:'$\\Phi$ растёт → ток создаёт встречное поле, навстречу N-полюсу. Магнит отталкивается.' },
{ q:'Магнит S-полюсом <b>приближается</b> к катушке.', ans:0, why:'$\\Phi$ растёт (с другим знаком) → ток создаёт встречное поле. Магнит отталкивается.' },
{ q:'Магнит N-полюсом <b>удаляется</b> от катушки.', ans:1, why:'$\\Phi$ убывает → ток создаёт поле, поддерживающее убывающий поток. Магнит притягивается.' },
{ q:'Магнит S-полюсом <b>удаляется</b> от катушки.', ans:1, why:'$\\Phi$ убывает → ток создаёт поддерживающее поле. Магнит притягивается.' },
{ q:'Ток в соседней катушке <b>растёт</b>.', ans:0, why:'$B$ через нашу катушку растёт → $\\Phi$ растёт → ток противодействует.' },
{ q:'Ток в соседней катушке <b>убывает</b>.', ans:1, why:'$B$ убывает → $\\Phi$ убывает → ток поддерживает (усиливает) поле.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p32-iv3-q');
const oEl = document.getElementById('p32-iv3-opts');
const fb = document.getElementById('p32-iv3-fb');
const iEl = document.getElementById('p32-iv3-i');
const sEl = document.getElementById('p32-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p32-iv3'); bumpProgress('p32', 25); }
else if(score >= 4){ addXp(8, 'p32-iv3'); bumpProgress('p32', 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, 1900);
});
});
}
document.getElementById('p32-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр Фарадея */
(function(){
const Q = [
{ q:'$N = 100$, $\\Delta\\Phi = 0{,}01$ Вб, $\\Delta t = 0{,}1$ с. Найди $|\\mathcal{E}_i|$ (в <b>В</b>).', ans:10, tol:0.6, why:'$|\\mathcal{E}| = 100\\cdot 0{,}01/0{,}1 = 10$ В.' },
{ q:'$N = 50$, $\\Delta\\Phi = 0{,}4$ Вб, $\\Delta t = 0{,}1$ с. Найди $|\\mathcal{E}_i|$ (в <b>В</b>).', ans:200, tol:10, why:'$|\\mathcal{E}| = 50\\cdot 0{,}4/0{,}1 = 200$ В.' },
{ q:'$N = 200$. За 0,5 с поток изменился от 0,2 Вб до 0,6 Вб. Найди $|\\mathcal{E}_i|$ (в <b>В</b>).', ans:160, tol:8, why:'$|\\Delta\\Phi| = 0{,}4$ Вб → $|\\mathcal{E}| = 200\\cdot 0{,}4/0{,}5 = 160$ В.' },
{ q:'Знак «−» в формуле $\\mathcal{E}_i = -\\Delta\\Phi/\\Delta t$ означает: <br>1 = правило Ленца, 2 = отрицательный заряд, 3 = ничего. Введи номер.', ans:1, tol:0.1, why:'Минус — математическая форма правила Ленца: ЭДС противодействует изменению потока.' },
{ q:'Если скорость изменения потока удвоить, во сколько раз изменится $|\\mathcal{E}_i|$?', ans:2, tol:0.1, why:'$|\\mathcal{E}| \\propto |\\Delta\\Phi|/\\Delta t$ — удвоится.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p32-iv4-q');
const fb = document.getElementById('p32-iv4-fb');
const iEl = document.getElementById('p32-iv4-i');
const sEl = document.getElementById('p32-iv4-s');
const inp = document.getElementById('p32-iv4-inp');
const bGo = document.getElementById('p32-iv4-go');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
inp.disabled = true; bGo.disabled = true;
if(score === Q.length){ addXp(15, 'p32-iv4'); bumpProgress('p32', 25); }
else if(score >= 3){ addXp(8, 'p32-iv4'); bumpProgress('p32', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
fb.style.display = 'none';
inp.value = ''; inp.disabled = false; bGo.disabled = false; inp.focus();
renderMath(qEl);
}
function check(){
if(inp.disabled) return;
const v = parseFloat(inp.value.replace(',','.'));
if(!isFinite(v)){ feedback(fb, false, 'Введи число.'); return; }
const tol = Math.max(Q[i].tol, Math.abs(Q[i].ans)*0.05);
const ok = Math.abs(v - Q[i].ans) <= tol;
if(ok){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].ans + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
inp.disabled = true; bGo.disabled = true;
i++;
setTimeout(show, 1900);
}
bGo.addEventListener('click', check);
inp.addEventListener('keydown', e => { if(e.key==='Enter') check(); });
document.getElementById('p32-iv4-restart').addEventListener('click', () => { i = 0; score = 0; inp.disabled=false; bGo.disabled=false; show(); });
show();
})();
wireReadBtn('p32');
}
function build_p33(){
const box = document.getElementById('p33-body');
let html = '';
/* THEORY 1 — Самоиндукция и индуктивность */
html += makeCard('theory', "Самоиндукция и индуктивность", "§33", `
<p><b>Самоиндукция</b> — возникновение ЭДС индукции в катушке при изменении тока в <b>этой же</b> катушке.</p>
<p style="margin-top:10px">Когда ток меняется, магнитный поток через катушку (созданный её собственным током) тоже меняется — это вызывает ЭДС индукции по закону Фарадея.</p>
<p style="margin-top:10px"><b>Индуктивность</b> $L$ — характеристика катушки, показывающая, какой магнитный поток создаётся в ней при данном токе:</p>
<p style="text-align:center;margin:8px 0">$$\\Phi = LI$$</p>
<p>где $\\Phi$ — магнитный поток через все витки катушки, $I$ — ток в ней.</p>
<p style="margin-top:10px"><b>Единица индуктивности</b>: <b>Генри</b> (Гн) = Вб/А. 1 Гн — индуктивность катушки, в которой ток 1 А создаёт магнитный поток 1 Вб.</p>
<p style="margin-top:10px">$L$ зависит от:</p>
<ul style="margin:6px 0 6px 22px">
<li>числа витков $N$ (квадратично — $L \\propto N^2$);</li>
<li>геометрии (длина $\\ell$, площадь сечения $S$);</li>
<li>магнитной проницаемости сердечника (с ферромагнитным сердечником $L$ в сотни раз больше).</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Аналогия.</b> Индуктивность $L$ для тока — как масса $m$ для скорости: чем больше $L$, тем «тяжелее» меняется ток. Это «электромагнитная инерция».</p>
`);
/* THEORY 2 — ЭДС самоиндукции */
html += makeCard('rule', "ЭДС самоиндукции", "§33", `
<p>Если ток в катушке меняется со скоростью $\\Delta I/\\Delta t$, в ней возникает ЭДС самоиндукции:</p>
<p style="text-align:center;margin:8px 0">$$\\mathcal{E}_{si} = -L\\,\\dfrac{\\Delta I}{\\Delta t}$$</p>
<p>Знак «минус» — правило Ленца: ЭДС самоиндукции <b>препятствует</b> изменению тока.</p>
<p style="margin-top:10px"><b>При замыкании цепи</b> (ток растёт от 0):</p>
<ul style="margin:6px 0 6px 22px">
<li>самоиндукция тормозит рост тока;</li>
<li>ток нарастает <b>постепенно</b>, а не мгновенно;</li>
<li>характерное время: $\\tau = L/R$ — <b>постоянная времени</b> $RL$-цепи.</li>
</ul>
<p style="margin-top:10px"><b>При размыкании цепи</b> (ток резко падает):</p>
<ul style="margin:6px 0 6px 22px">
<li>самоиндукция поддерживает ток;</li>
<li>может возникнуть <b>искра</b> или большой импульс напряжения на размыкаемом ключе;</li>
<li>в мощных катушках при размыкании может быть электрическая дуга или удар.</li>
</ul>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Мнемоника.</b> Большая $L$ — большая «инерция тока». Ток в катушке не может меняться скачком, как скорость массы не может меняться мгновенно.</p>
`);
/* THEORY 3 — Энергия магнитного поля катушки */
html += makeCard('example', "Энергия магнитного поля катушки", "§33", `
<p>Заряженный конденсатор запасает энергию в <b>электрическом</b> поле. Катушка с током запасает энергию в <b>магнитном</b> поле:</p>
<p style="text-align:center;margin:8px 0">$$W = \\dfrac{LI^2}{2}$$</p>
<p>Эта энергия запасена в магнитном поле <b>внутри катушки</b>. При размыкании цепи она возвращается обратно — может уйти в искру, тепло или работу.</p>
<p style="margin-top:10px"><b>Аналогия с механикой:</b></p>
<ul style="margin:6px 0 6px 22px">
<li>$W_k = \\dfrac{mv^2}{2}$ — кинетическая энергия (механика);</li>
<li>$W_L = \\dfrac{LI^2}{2}$ — энергия магнитного поля (электродинамика);</li>
<li>$L \\leftrightarrow m$, $I \\leftrightarrow v$.</li>
</ul>
<p style="margin-top:10px"><b>Пример.</b> $L = 0{,}5$ Гн, $I = 4$ А. Тогда $W = LI^2/2 = 0{,}5 \\cdot 16 / 2 = 4$ Дж.</p>
<p style="margin-top:10px"><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> (Qi) — передача энергии через переменное магнитное поле;</li>
<li><b>Электромагнитные пусковые устройства</b> — огромные токи запасают много энергии для импульсного применения.</li>
</ul>
`);
/* INTERACTIVE 1 — LR-цепь: нарастание тока */
html += `<div class="wg" id="p33-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">LR-цепь: нарастание тока при замыкании</div></div>
<div class="wg-help">При замыкании цепи ток не достигает $I_{\\max} = \\mathcal{E}/R$ мгновенно — самоиндукция тормозит его рост: $I(t) = I_{\\max}(1 - e^{-t/\\tau})$, где $\\tau = L/R$.</div>
<div class="sliders" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
<label>$\\mathcal{E}$ (В): <b id="p33-iv1-EL">6.0</b><input type="range" id="p33-iv1-E" min="1" max="12" value="6" step="0.5"></label>
<label>$R$ (Ом): <b id="p33-iv1-RL">10</b><input type="range" id="p33-iv1-R" min="1" max="100" value="10" step="1"></label>
<label>$L$ (мГн): <b id="p33-iv1-LL">100</b><input type="range" id="p33-iv1-L" min="1" max="1000" value="100" step="1"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px;margin-top:8px">
<svg id="p33-iv1-svg" viewBox="0 0 480 260" width="100%" style="height:auto"></svg>
</div>
<div class="actions" style="margin-top:8px"><button class="btn primary" id="p33-iv1-go">Замкнуть цепь</button><button class="btn" id="p33-iv1-reset">Сброс</button></div>
<div id="p33-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор индуктивности и энергии */
html += `<div class="wg" id="p33-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор: $\\Phi$, $W$ и ЭДС самоиндукции</div></div>
<div class="wg-help">$\\Phi = LI$, $W = LI^2/2$, $|\\mathcal{E}_{si}| = L\\,|\\Delta I|/\\Delta t$.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:10px">
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$L$ (Гн):<input type="number" id="p33-iv2-L" value="0.5" step="any" min="0" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$I$ (А):<input type="number" id="p33-iv2-I" value="4" step="any" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$\\Delta I$ (А):<input type="number" id="p33-iv2-dI" value="2" step="any" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:.92rem">$\\Delta t$ (с):<input type="number" id="p33-iv2-dt" value="0.1" step="any" min="0" style="padding:7px;border:1px solid var(--border);border-radius:6px"></label>
</div>
<div class="actions"><button class="btn primary" id="p33-iv2-calc">Вычислить $\\Phi$, $W$, $|\\mathcal{E}_{si}|$</button></div>
<div id="p33-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.98rem;line-height:1.8;text-align:center;min-height:40px"></div>
</div>`;
/* INTERACTIVE 3 — Что произойдёт с током? */
html += `<div class="wg" id="p33-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Что произойдёт с током?</div></div>
<div class="wg-help">Самоиндукция всегда <b>препятствует</b> изменению тока. При $\\Delta I = 0$ — ЭДС нет.</div>
<div class="score-display"><span>Задача <b id="p33-iv3-i">1</b> / 6</span><span>Очки: <b id="p33-iv3-s">0</b> / 6</span></div>
<div id="p33-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="p33-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px"></div>
<div class="feedback" id="p33-iv3-fb"></div>
<div class="actions"><button class="btn" id="p33-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр индуктивности */
html += `<div class="wg" id="p33-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: индуктивность и энергия</div></div>
<div class="wg-help">5 задач. Допуск ±5%. Формулы: $\\Phi = LI$, $W = LI^2/2$, $|\\mathcal{E}_{si}| = L\\,|\\Delta I|/\\Delta t$, $\\tau = L/R$.</div>
<div class="score-display"><span>Задача <b id="p33-iv4-i">1</b> / 5</span><span>Очки: <b id="p33-iv4-s">0</b> / 5</span></div>
<div id="p33-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="p33-iv4-form" style="display:flex;gap:8px;justify-content:center;margin-bottom:8px;flex-wrap:wrap">
<input type="number" id="p33-iv4-inp" step="any" style="padding:8px 10px;border:1px solid var(--border);border-radius:6px;width:140px;font-size:1rem">
<button class="btn primary" id="p33-iv4-go">Проверить</button>
</div>
<div class="feedback" id="p33-iv4-fb"></div>
<div class="actions"><button class="btn" id="p33-iv4-restart">Начать заново</button></div>
</div>`;
html += secNav('p32', 'final5');
html += readButton('p33');
box.innerHTML = html;
renderMath(box);
/* IV1 — LR-цепь */
(function(){
const svg = document.getElementById('p33-iv1-svg');
const out = document.getElementById('p33-iv1-out');
const EI = document.getElementById('p33-iv1-E'), EL = document.getElementById('p33-iv1-EL');
const RI = document.getElementById('p33-iv1-R'), RL = document.getElementById('p33-iv1-RL');
const LI = document.getElementById('p33-iv1-L'), LL = document.getElementById('p33-iv1-LL');
const bGo = document.getElementById('p33-iv1-go');
const bRs = document.getElementById('p33-iv1-reset');
let animT0 = null, running = false, rafId = null;
let configs = new Set();
let _done = false;
function draw(progress){
const W = 480, H = 260;
const E = +EI.value, R = +RI.value, L = (+LI.value) / 1000; // Гн
EL.textContent = E.toFixed(1);
RL.textContent = R.toString();
LL.textContent = (+LI.value).toString();
const Imax = E / R;
const tau = L / R;
// total animation time ~ 5*tau, мин 0.6 с, макс 3 с
const animDur = Math.max(0.6, Math.min(3, 5*tau));
const tReal = progress * animDur;
const I = Imax * (1 - Math.exp(-tReal / tau));
let g = '';
g += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#fafafa"/>';
g += '<text x="240" y="20" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">RL-цепь: ток нарастает как $I(t) = I_{max}(1 - e^{-t/τ})$</text>';
// Schema slot (left): battery + switch + R + L (квадрат)
// Координаты схемы
const sx = 30, sy = 60, sw = 200, sh = 160;
g += '<rect x="'+sx+'" y="'+sy+'" width="'+sw+'" height="'+sh+'" fill="white" stroke="#cbd5e1" stroke-width="1" rx="6"/>';
// прямоугольный контур цепи
const cx1 = sx+30, cx2 = sx+sw-20;
const cy1 = sy+40, cy2 = sy+sh-30;
// верх
g += PHYS.wire(cx1, cy1, cx1+50, cy1); // от + батареи направо
g += PHYS.wire(cx1+50+10, cy1, cx2, cy1); // через ключ к правому верх. углу (промежуток для ключа)
// правый бок (L)
g += PHYS.wire(cx2, cy1, cx2, cy1+30);
// L symbol в правой стороне (горизонталь нарисуем посередине правой стороны)
g += PHYS.inductorSymbol((cx2), (cy1+cy2)/2, undefined, 'h');
// соединение L и правого бока
g += PHYS.wire(cx2, (cy1+cy2)/2 + 12, cx2, cy2);
// низ
g += PHYS.wire(cx2, cy2, cx1+90, cy2);
// R в нижней части
g += PHYS.resistor(cx1+70, cy2);
g += PHYS.wire(cx1+70-20, cy2, cx1, cy2);
// левый бок (батарея)
g += PHYS.wire(cx1, cy2, cx1, cy1+30);
g += PHYS.batteryEMF(cx1, (cy1+cy2)/2, undefined, 'v');
g += PHYS.wire(cx1, (cy1+cy2)/2 - 12, cx1, cy1);
// Ключ (наклонная линия): закрыт если running или progress>0
const swX1 = cx1+50, swX2 = cx1+50+10;
if(running || progress > 0){
g += '<line x1="'+swX1+'" y1="'+cy1+'" x2="'+swX2+'" y2="'+cy1+'" stroke="#10b981" stroke-width="2"/>';
} else {
g += '<line x1="'+swX1+'" y1="'+cy1+'" x2="'+(swX2-2)+'" y2="'+(cy1-12)+'" stroke="#dc2626" stroke-width="2"/>';
}
g += '<circle cx="'+swX1+'" cy="'+cy1+'" r="2.5" fill="#0f172a"/>';
g += '<circle cx="'+swX2+'" cy="'+cy1+'" r="2.5" fill="#0f172a"/>';
// Подписи параметров
g += '<text x="'+(cx1-2)+'" y="'+(sy+sh-6)+'" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">ε='+E.toFixed(1)+' В</text>';
g += '<text x="'+(cx1+70)+'" y="'+(cy2+18)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">R='+R+' Ом</text>';
g += '<text x="'+(cx2+10)+'" y="'+((cy1+cy2)/2 + 4)+'" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">L='+(+LI.value)+' мГн</text>';
// График справа
const gx = 260, gy = 50, gw = 200, gh = 170;
g += '<rect x="'+gx+'" y="'+gy+'" width="'+gw+'" height="'+gh+'" fill="white" stroke="#cbd5e1" stroke-width="1" rx="6"/>';
// оси
g += '<line x1="'+(gx+30)+'" y1="'+(gy+gh-25)+'" x2="'+(gx+gw-10)+'" y2="'+(gy+gh-25)+'" stroke="#0f172a" stroke-width="1.4"/>';
g += '<line x1="'+(gx+30)+'" y1="'+(gy+15)+'" x2="'+(gx+30)+'" y2="'+(gy+gh-25)+'" stroke="#0f172a" stroke-width="1.4"/>';
g += '<text x="'+(gx+gw-12)+'" y="'+(gy+gh-10)+'" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="10" fill="#0f172a">t</text>';
g += '<text x="'+(gx+28)+'" y="'+(gy+14)+'" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="10" fill="#0f172a">I</text>';
// линия Imax (пунктир)
const imaxY = gy + 22;
g += '<line x1="'+(gx+30)+'" y1="'+imaxY+'" x2="'+(gx+gw-10)+'" y2="'+imaxY+'" stroke="#dc2626" stroke-width="1" stroke-dasharray="4 3"/>';
g += '<text x="'+(gx+gw-14)+'" y="'+(imaxY-3)+'" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="10" fill="#dc2626">I_max</text>';
// линия 0.63 Imax — для подписи τ
const tauY = gy + 22 + 0.37 * ((gy+gh-25) - imaxY);
g += '<line x1="'+(gx+30)+'" y1="'+tauY+'" x2="'+(gx+gw-10)+'" y2="'+tauY+'" stroke="#0891b2" stroke-width="0.8" stroke-dasharray="2 3"/>';
g += '<text x="'+(gx+34)+'" y="'+(tauY-3)+'" font-family="JetBrains Mono,monospace" font-size="9" fill="#0891b2">0.63 I_max</text>';
// кривая до текущего progress (или полная при progress=1)
const steps = 60;
let path = '';
const tFinal = progress * 5*tau / Math.max(1e-9, animDur); // нормированно к τ
const sMax = Math.min(5, 5 * progress * (animDur / (5*tau))); // безразмерное по τ
for(let k=0; k<=steps; k++){
const s = (k/steps) * sMax; // s = t/τ
const iv = 1 - Math.exp(-s);
const px = (gx+30) + (s/5) * ((gx+gw-10) - (gx+30));
const py = imaxY + (1 - iv) * ((gy+gh-25) - imaxY);
path += (k===0?'M ':'L ') + px.toFixed(1) + ' ' + py.toFixed(1) + ' ';
}
if(progress > 0) g += '<path d="'+path+'" fill="none" stroke="#7c3aed" stroke-width="2.2"/>';
// подпись τ — отметка x = τ на оси (s=1 в нормировке)
const tauX = (gx+30) + (1/5) * ((gx+gw-10) - (gx+30));
g += '<line x1="'+tauX+'" y1="'+(gy+gh-25)+'" x2="'+tauX+'" y2="'+(gy+gh-22)+'" stroke="#0f172a" stroke-width="1.2"/>';
g += '<text x="'+tauX+'" y="'+(gy+gh-12)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#0f172a">τ</text>';
svg.innerHTML = g;
const tauMs = tau * 1000;
out.innerHTML = '$I_{max} = \\mathcal{E}/R = '+Imax.toFixed(3)+'$ А &nbsp; $\\tau = L/R = '+tauMs.toFixed(2)+'$ мс &nbsp; <b>Текущий ток</b>: $I = '+I.toFixed(3)+'$ А';
renderMath(out);
}
function step(now){
if(!running){ return; }
if(!animT0) animT0 = now;
const E = +EI.value, R = +RI.value, L = (+LI.value)/1000;
const tau = L/R;
const animDur = Math.max(0.6, Math.min(3, 5*tau));
const elapsed = (now - animT0) / 1000;
const p = Math.min(1, elapsed / animDur);
draw(p);
if(p < 1){ rafId = requestAnimationFrame(step); }
else {
running = false;
const key = (+EI.value).toFixed(1)+'|'+RI.value+'|'+LI.value;
configs.add(key);
if(!_done && configs.size >= 4){ _done = true; addXp(15, 'p33-iv1'); bumpProgress('p33', 20); }
}
}
function start(){
if(running) return;
animT0 = null; running = true;
rafId = requestAnimationFrame(step);
}
function reset(){
running = false; if(rafId) cancelAnimationFrame(rafId);
draw(0);
}
bGo.addEventListener('click', start);
bRs.addEventListener('click', reset);
[EI, RI, LI].forEach(el => el.addEventListener('input', ()=>{ reset(); }));
draw(0);
})();
/* IV2 — Калькулятор */
(function(){
const out = document.getElementById('p33-iv2-out');
const bGo = document.getElementById('p33-iv2-calc');
const iL = document.getElementById('p33-iv2-L');
const iI = document.getElementById('p33-iv2-I');
const iDI = document.getElementById('p33-iv2-dI');
const iDT = document.getElementById('p33-iv2-dt');
let count = 0, _done = false;
function calc(){
const L = +iL.value, I = +iI.value, dI = +iDI.value, dt = +iDT.value;
if(![L,I,dI,dt].every(isFinite) || L < 0 || dt <= 0){
out.innerHTML = '<b style="color:#dc2626">Проверь ввод: $L \\ge 0$, $\\Delta t > 0$.</b>';
renderMath(out);
return;
}
const phi = L * I;
const W = L * I * I / 2;
const Esi = L * Math.abs(dI) / dt;
let html = '';
html += '$\\Phi = LI = '+L+'\\cdot '+I+' = $ <b>'+phi.toFixed(4)+' Вб</b><br>';
html += '$W = \\dfrac{LI^2}{2} = \\dfrac{'+L+'\\cdot '+(I*I)+'}{2} = $ <b>'+W.toFixed(4)+' Дж</b><br>';
html += '$|\\mathcal{E}_{si}| = L\\,\\dfrac{|\\Delta I|}{\\Delta t} = '+L+'\\cdot \\dfrac{'+Math.abs(dI)+'}{'+dt+'} = $ <b>'+Esi.toFixed(4)+' В</b>';
out.innerHTML = html;
renderMath(out);
count++;
if(!_done && count >= 3){ _done = true; addXp(10, 'p33-iv2'); bumpProgress('p33', 15); }
}
bGo.addEventListener('click', calc);
[iL, iI, iDI, iDT].forEach(x => x.addEventListener('keydown', e => { if(e.key==='Enter') calc(); }));
})();
/* IV3 — Что произойдёт с током? */
(function(){
const OPTS = ['Растёт постепенно', 'Падает постепенно', 'Не меняется / нет ЭДС'];
const Q = [
{ q:'Цепь с $L$ <b>замыкают</b> (был разомкнут). Что происходит с током?', ans:0, why:'Самоиндукция тормозит рост, но ток постепенно растёт от 0 до $I_{max} = \\mathcal{E}/R$.' },
{ q:'Цепь с $L$ <b>размыкают</b>. Что происходит с током до размыкания (в катушке)?', ans:1, why:'Самоиндукция поддерживает ток, но он постепенно падает до 0 (может проскочить искра).' },
{ q:'В катушке течёт <b>постоянный</b> ток $I = 5$ А ($\\Delta I = 0$). Чему равна $\\mathcal{E}_{si}$?', ans:2, why:'$\\mathcal{E}_{si} = -L\\,\\Delta I/\\Delta t = 0$ при $\\Delta I = 0$. ЭДС самоиндукции не возникает.' },
{ q:'В цепи <b>нет</b> индуктивности ($L = 0$). Замкнули — что с током?', ans:2, why:'$\\tau = L/R = 0$. Ток мгновенно устанавливается на $I_{max} = \\mathcal{E}/R$. Постепенного изменения нет.' },
{ q:'$L$ увеличили в 2 раза (при тех же $\\mathcal{E}$ и $R$). После замыкания цепи — как ведёт себя ток?', ans:0, why:'$\\tau = L/R$ удвоится → ток растёт <b>медленнее</b>, но всё равно растёт до того же $I_{max}$.' },
{ q:'Катушку с током отключили от источника, но замкнули на резистор. Что с током в катушке сразу после?', ans:1, why:'Энергия $LI^2/2$ постепенно рассеивается в $R$ → ток экспоненциально падает: $I(t) = I_0 e^{-t/\\tau}$.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p33-iv3-q');
const oEl = document.getElementById('p33-iv3-opts');
const fb = document.getElementById('p33-iv3-fb');
const iEl = document.getElementById('p33-iv3-i');
const sEl = document.getElementById('p33-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p33-iv3'); bumpProgress('p33', 25); }
else if(score >= 4){ addXp(8, 'p33-iv3'); bumpProgress('p33', 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, 1900);
});
});
}
document.getElementById('p33-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр индуктивности */
(function(){
const Q = [
{ q:'$L = 0{,}5$ Гн, $I = 4$ А. Найди энергию магнитного поля $W$ (в <b>Дж</b>).', ans:4, tol:0.25, why:'$W = LI^2/2 = 0{,}5\\cdot 16/2 = 4$ Дж.' },
{ q:'$L = 100$ мГн, $I = 2$ А. Найди магнитный поток $\\Phi$ (в <b>мВб</b>).', ans:200, tol:10, why:'$\\Phi = LI = 0{,}1\\cdot 2 = 0{,}2$ Вб $= 200$ мВб.' },
{ q:'$L = 1$ Гн, $\\Delta I = 5$ А за $\\Delta t = 0{,}1$ с. Найди $|\\mathcal{E}_{si}|$ (в <b>В</b>).', ans:50, tol:2.5, why:'$|\\mathcal{E}_{si}| = L\\,|\\Delta I|/\\Delta t = 1\\cdot 5/0{,}1 = 50$ В.' },
{ q:'Постоянная времени RL-цепи: $L = 0{,}5$ Гн, $R = 10$ Ом. Найди $\\tau$ (в <b>мс</b>).', ans:50, tol:2.5, why:'$\\tau = L/R = 0{,}5/10 = 0{,}05$ с $= 50$ мс.' },
{ q:'Во сколько раз изменится энергия магнитного поля $W = LI^2/2$, если ток $I$ <b>удвоить</b> (при том же $L$)?', ans:4, tol:0.2, why:'$W \\propto I^2$ → удвоение $I$ даёт рост $W$ в $2^2 = 4$ раза.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p33-iv4-q');
const fb = document.getElementById('p33-iv4-fb');
const iEl = document.getElementById('p33-iv4-i');
const sEl = document.getElementById('p33-iv4-s');
const inp = document.getElementById('p33-iv4-inp');
const bGo = document.getElementById('p33-iv4-go');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
inp.disabled = true; bGo.disabled = true;
if(score === Q.length){ addXp(15, 'p33-iv4'); bumpProgress('p33', 25); }
else if(score >= 3){ addXp(8, 'p33-iv4'); bumpProgress('p33', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
fb.style.display = 'none';
inp.value = ''; inp.disabled = false; bGo.disabled = false; inp.focus();
renderMath(qEl);
}
function check(){
if(inp.disabled) return;
const v = parseFloat(inp.value.replace(',','.'));
if(!isFinite(v)){ feedback(fb, false, 'Введи число.'); return; }
const tol = Math.max(Q[i].tol, Math.abs(Q[i].ans)*0.05);
const ok = Math.abs(v - Q[i].ans) <= tol;
if(ok){ score++; feedback(fb, true, '&#10003; Верно! ' + Q[i].why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Верно: ' + Q[i].ans + '. ' + Q[i].why + ' Дальше ▶');
sEl.textContent = score;
inp.disabled = true; bGo.disabled = true;
i++;
setTimeout(show, 1900);
}
bGo.addEventListener('click', check);
inp.addEventListener('keydown', e => { if(e.key==='Enter') check(); });
document.getElementById('p33-iv4-restart').addEventListener('click', () => { i = 0; score = 0; inp.disabled=false; bGo.disabled=false; show(); });
show();
})();
wireReadBtn('p33');
}
function build_final5(){
const box = document.getElementById('final5-body');
let html = '';
/* Часть А — Шпаргалка главы (7 mini-карточек) */
const SHEET = [
{ t:'§ 27 · Поле тока', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0891b2" stroke-width="2" style="width:18px;height:18px"><circle cx="12" cy="12" r="3"/><path d="M12 2v3"/><path d="M12 19v3"/><path d="M4 12h3"/><path d="M17 12h3"/></svg>', body:'Создаётся <b>движущимися зарядами</b>. Опыт Эрстеда: стрелка возле проводника отклоняется.' },
{ t:'§ 28 · Индукция $\\vec{B}$', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#7c3aed" stroke-width="2" style="width:18px;height:18px"><path d="M4 12c4-6 12-6 16 0"/><polyline points="18 9 20 12 17 14"/></svg>', body:'$\\vec{B}$ — векторная характеристика поля (Тл). Правило буравчика. Линии замкнуты.' },
{ t:'§ 29 · Сила Ампера', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" style="width:18px;height:18px"><path d="M5 12h14"/><polyline points="13 6 19 12 13 18"/></svg>', body:'$F_A = BIL\\sin\\alpha$. Правило левой руки: 4 пальца — ток, ладонь — $\\vec{B}$, большой — $\\vec{F}$.' },
{ t:'§ 30 · Лоренц', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2" style="width:18px;height:18px"><circle cx="12" cy="12" r="8"/><path d="M12 4v8l5 3"/></svg>', body:'$F_L = qvB\\sin\\alpha$. Окружность: $R = mv/(qB)$. Работа $\\vec{F}_L$ = 0.' },
{ t:'§ 31 · Поток $\\Phi$', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" style="width:18px;height:18px"><rect x="4" y="6" width="16" height="12" rx="1"/><path d="M8 10l8 4"/></svg>', body:'$\\Phi = BS\\cos\\alpha$ [Вб]. ЭМИ — ток в контуре возникает при <b>изменении</b> $\\Phi$.' },
{ t:'§ 32 · Фарадей + Ленц', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0ea5e9" stroke-width="2" style="width:18px;height:18px"><path d="M4 17l4-4 4 2 8-8"/><polyline points="14 7 20 7 20 13"/></svg>', body:'$\\mathcal{E}_i = -N\\,\\Delta\\Phi/\\Delta t$. Ток индукции <b>противодействует</b> изменению потока.' },
{ t:'§ 33 · Самоиндукция', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#8b5cf6" stroke-width="2" style="width:18px;height:18px"><path d="M3 12c2-3 4-3 6 0s4 3 6 0 4-3 6 0"/></svg>', body:'$\\Phi = LI$, $L$ в Гн. $W = LI^2/2$. $\\mathcal{E}_{si} = -L\\,\\Delta I/\\Delta t$. $\\tau = L/R$.' },
];
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 5 — Магнитное поле и ЭМИ</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Ключевые формулы и идеи всех 7 параграфов главы — повтори перед битвой с боссами.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;margin-top:10px">
${SHEET.map(s => `<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
${s.icon}
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.88rem">${s.t}</div>
</div>
<div style="font-size:.92rem;line-height:1.55">${s.body}</div>
</div>`).join('')}
</div>
</div>
</div>`;
/* Часть Б — 7 боссов intro */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 5</span>
<span class="card-num">7</span>
</div>
<div class="card-body">
<p>7 интегрированных задач по §§27–33. За каждого побеждённого босса: <b>+10 XP, +14% к прогрессу</b>. Победишь всех — ачивка <b>«Мастер магнетизма»</b> и <b>+50 XP бонус</b>.</p>
<p style="margin-top:6px;font-size:.92rem;color:var(--muted)">Константы: $e = 1{,}6\\cdot 10^{-19}$ Кл, $m_p = 1{,}67\\cdot 10^{-27}$ кг. Допуск $\\pm 5\\%$.</p>
</div>
</div>`;
html += '<div id="ch5-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--sec-acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch5-final-summary">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>
<div id="ch5-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 7 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch5-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#0891b2,#22d3ee);transition:width .35s"></div>
</div>
<div id="ch5-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #0891b2">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#0e7490;font-size:1.05rem;margin-bottom:6px">Мастер магнетизма</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 5 пройдена! Все 7 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/physics-10-ch6" style="text-decoration:none">Дальше: Глава 6 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p33', null);
box.innerHTML = html;
renderMath(box);
/* Боссы главы 5 */
const BOSSES = [
{
n:1, color:'#dc2626',
title:'Страж Ампера',
tag:'§ 29',
q:'$B = 0{,}5$ Тл, $I = 3$ А, $L = 0{,}2$ м, $\\alpha = 90°$. Найди силу Ампера $F_A$ в <b>Н</b>.',
ans:0.3, tol:0.02,
hint:'$F_A = BIL\\sin\\alpha = 0{,}5\\cdot 3\\cdot 0{,}2\\cdot 1 = 0{,}3$ Н.'
},
{
n:2, color:'#f59e0b',
title:'Жнец Лоренца',
tag:'§ 30',
q:'Электрон ($e = 1{,}6\\cdot 10^{-19}$ Кл) движется со скоростью $v = 10^6$ м/с перпендикулярно $\\vec{B} = 0{,}5$ Тл. Найди $F_L$ в виде $a \\cdot 10^{-14}$ Н. Введи $a$.',
ans:8, tol:0.4,
hint:'$F_L = qvB = 1{,}6\\cdot 10^{-19}\\cdot 10^6\\cdot 0{,}5 = 8\\cdot 10^{-14}$ Н.'
},
{
n:3, color:'#10b981',
title:'Хранитель Потока',
tag:'§ 31',
q:'$B = 0{,}2$ Тл, $S = 0{,}1$ м², $\\alpha = 60°$ (между $\\vec{B}$ и нормалью). Найди $\\Phi$ в <b>мВб</b>.',
ans:10, tol:0.6,
hint:'$\\Phi = BS\\cos\\alpha = 0{,}2\\cdot 0{,}1\\cdot 0{,}5 = 0{,}01$ Вб $= 10$ мВб.'
},
{
n:4, color:'#0ea5e9',
title:'Дракон Фарадея',
tag:'§ 32',
q:'Катушка из $N = 100$ витков. Поток изменился на $\\Delta\\Phi = 0{,}5$ Вб за $\\Delta t = 0{,}05$ с. Найди $|\\mathcal{E}_i|$ в <b>В</b>.',
ans:1000, tol:50,
hint:'$|\\mathcal{E}_i| = N\\,|\\Delta\\Phi|/\\Delta t = 100\\cdot 0{,}5/0{,}05 = 1000$ В.'
},
{
n:5, color:'#8b5cf6',
title:'Капитан Индуктивности',
tag:'§ 33',
q:'$L = 2$ Гн, $I = 3$ А. Найди энергию магнитного поля $W$ в <b>Дж</b>.',
ans:9, tol:0.5,
hint:'$W = LI^2/2 = 2\\cdot 9/2 = 9$ Дж.'
},
{
n:6, color:'#dc2626',
title:'Циклотронный Левиафан',
tag:'§ 30',
q:'Протон ($m_p = 1{,}67\\cdot 10^{-27}$ кг, $q = 1{,}6\\cdot 10^{-19}$ Кл) влетает перпендикулярно в $\\vec{B} = 0{,}5$ Тл со $v = 2\\cdot 10^7$ м/с. Найди радиус окружности $R$ в <b>см</b> (округли).',
ans:42, tol:3,
hint:'$R = m v/(qB) = 1{,}67\\cdot 10^{-27}\\cdot 2\\cdot 10^7/(1{,}6\\cdot 10^{-19}\\cdot 0{,}5) \\approx 0{,}418$ м $\\approx 42$ см.'
},
{
n:7, color:'#22d3ee',
title:'Магистр Магнетизма',
tag:'§ 33 (синтез)',
q:'Катушка с $L = 0{,}4$ Гн, $R = 8$ Ом подключается к источнику. Через какое время ток достигнет $\\approx 63\\%$ от максимума (это $\\tau$)? Ответ в <b>мс</b>.',
ans:50, tol:3,
hint:'$\\tau = L/R = 0{,}4/8 = 0{,}05$ с $= 50$ мс.'
},
];
const cont = document.getElementById('ch5-bosses-container');
const STATE_KEY = 'physics10_ch5_bosses';
const BOSS_STATE = (function(){
try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }catch(e){}
return BOSSES.map(()=>({defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
cont.innerHTML = BOSSES.map((b)=>{
return '<div class="boss-card" id="boss5-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--sec-acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div class="boss-q" id="boss5-'+b.n+'-q" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;line-height:1.5;margin-bottom:10px">'+b.q+'</div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<span style="font-family:\'JetBrains Mono\',monospace;font-size:.92rem">ответ =</span>'
+'<input type="number" id="boss5-'+b.n+'-ans" class="tinp" style="width:130px;text-align:center" step="0.01" placeholder="число">'
+'<button class="btn primary" id="boss5-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss5-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss5-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch5-boss-overall');
const fill = document.getElementById('ch5-boss-overall-fill');
if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено';
if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%';
if(won >= BOSSES.length){
const reward = document.getElementById('ch5-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch5_done')){
achievement('ch5_done','Мастер магнетизма');
addXp(50, 'ch5-bonus');
bumpProgress('final5', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss5-'+b.n+'-card');
const goBtn = document.getElementById('boss5-'+b.n+'-go');
const hintBtn = document.getElementById('boss5-'+b.n+'-hint');
const ansInp = document.getElementById('boss5-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated){
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
card.classList.add('glow');
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
}
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('boss5-'+b.n+'-fb');
const raw = ansInp.value.replace(',', '.');
const val = parseFloat(raw);
if(isNaN(val)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = (typeof b.tol === 'number') ? b.tol : Math.max(0.05 * Math.abs(b.ans), 0.05);
if(Math.abs(val - b.ans) < tol + 0.001){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch5-'+b.n);
bumpProgress('final5', 14);
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
card.classList.add('glow','pulse');
setTimeout(()=>card.classList.remove('pulse'), 900);
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('boss5-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+b.hint;
fb.style.display = 'block';
fb.style.background = 'var(--warn-bg)';
fb.style.color = '#92400e';
fb.style.borderLeftColor = 'var(--warn)';
renderMath(fb);
});
ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
});
refreshOverall();
}
/* ===== 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>