Files
Learn_System/frontend/textbooks/physics_10_ch4.html
T
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

1724 lines
114 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Физика 10 · Глава 4 · «Постоянный ток»</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:#db2777; --pri2:#be185d; --pri-soft:#fce7f3;
--acc:#f472b6; --acc2:#db2777; --acc-soft:#fce7f3;
--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,#831843 0%,#db2777 55%,#f472b6 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 4';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:'I';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-p25"]{ --sec-acc:#db2777; --sec-acc-d:#be185d; --sec-acc-soft:#fce7f3; }
.sec[id="sec-p26"]{ --sec-acc:#db2777; --sec-acc-d:#be185d; --sec-acc-soft:#fce7f3; }
.sec[id="sec-final4"]{ --sec-acc:#db2777; --sec-acc-d:#be185d; --sec-acc-soft:#fce7f3; }
.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 · Глава 4</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('p25')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 25</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-p25" class="sec" data-watermark="&epsi;"><div class="sec-header"><span class="sec-num">§ 25</span><h2 class="sec-h">ЭДС источника тока</h2></div><div id="p25-body"></div></section>
<section id="sec-p26" class="sec" data-watermark="Ом"><div class="sec-header"><span class="sec-num">§ 26</span><h2 class="sec-h">Закон Ома для полной цепи</h2></div><div id="p26-body"></div></section>
<section id="sec-final4" class="sec" data-watermark="&#9733;"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#db2777,#f472b6)"></span><h2 class="sec-h">Финал главы</h2></div><div id="final4-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» · Глава 4 · «Постоянный ток» · 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:'p25', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 3;
const _TB_SLUG = 'physics-10-ch4';
const PARAS = [
{ id:'p25', num:'\u00a7 25', name:"ЭДС источника тока", sub:'$\\mathcal{E}$' },
{ id:'p26', num:'\u00a7 26', name:"Закон Ома для полной цепи", sub:'$I = \\mathcal{E}/(R+r)$' },
{ id:'final4', num:'\u2605', name:'Финал главы', sub:'Итоги \u00b7 боссы главы 4', 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:"Начало главы 4!",
p25_done:"ЭДС источника тока освоен!",
p26_done:"Закон Ома для полной цепи освоен!",
ch4_done:"Глава 4 пройдена!"
};
function loadProgress(){
try{
const s=localStorage.getItem('physics10_ch4_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics10_ch4_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_ch4_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics10_ch4_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-ch4-'+(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 = { p25:()=>build_p25(), p26:()=>build_p26(), final4:()=>build_final4() };
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 = {
p25:{title:"Шпаргалка §25",rows:[["$\\mathcal{E}$","ЭДС — В"],["Сторон. силы","внутри источника"],["$\\mathcal{E} = A_{стор}/q$",""]]},
p26:{title:"Шпаргалка §26",rows:[["$I = \\mathcal{E}/(R+r)$","полная цепь"],["КЗ","$I_{кз} = \\mathcal{E}/r$"],["КПД","$\\eta = R/(R+r)$"],["$U = \\mathcal{E} - Ir$","напр. на полюсах"]]},
final4:{title:"Финал главы 4",rows:[["§§2526","теория главы 4"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p25',html:"ЭДС — работа сторонних сил по перемещению единичного заряда: $\\mathcal{E} = A_{стор}/q$. Измер. в В."},
{sec:'p26',html:"Закон Ома для полной цепи: $I = \\dfrac{\\mathcal{E}}{R + r}$. КПД источника: $\\eta = R/(R + r)$."},
{sec:'final4',html:"Финал главы 4 — интегрированные задачи по §§25–26. В разработке (Phase 4+)."}
];
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_ch4_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_ch4_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 = {p25:'\xA725',p26:'\xA726',final4:'Финал'};
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_p25(){
const box = document.getElementById('p25-body');
let html = '';
/* THEORY 1 — Электрический ток и условия его существования */
html += makeCard('theory', "Электрический ток и условия его существования", "§25", `
<p><b>Электрический ток</b> — упорядоченное движение электрических зарядов (свободных носителей: электронов в металлах, ионов в электролитах и т. д.).</p>
<p><b>Сила тока</b> $I$ — заряд, проходящий через сечение проводника за единицу времени:</p>
<p style="text-align:center;margin:10px 0">$$I = \\dfrac{q}{t}$$</p>
<p>Единица: <b>Ампер</b> (А) $= 1$ Кл / 1 с.</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>Свободные заряженные частицы в проводнике.</li>
<li>Электрическое поле в проводнике (поддерживается источником тока).</li>
<li>Замкнутая цепь.</li>
</ol>
<p>Если поле не поддерживать — заряды перераспределятся и ток прекратится за доли секунды.</p>
`);
/* THEORY 2 — Сторонние силы. ЭДС */
html += makeCard('rule', "Сторонние силы. ЭДС", "§25", `
<p><b>Сторонние силы</b> — силы неэлектрической природы, которые в источнике тока производят работу по разделению зарядов и поддерживают разность потенциалов на полюсах. Виды сторонних сил:</p>
<ul style="margin:6px 0 6px 18px">
<li><b>Химические</b> — в гальванических элементах и аккумуляторах.</li>
<li><b>Механические</b> — в генераторах (вращение ротора).</li>
<li><b>Световые</b> — в фотоэлементах (солнечных батареях).</li>
<li><b>Тепловые</b> — в термоэлементах.</li>
</ul>
<p style="margin-top:10px"><b>ЭДС источника</b> $\\mathcal{E}$ — отношение работы сторонних сил $A_{ст}$ по перемещению заряда $q$ внутри источника к самому заряду:</p>
<p style="text-align:center;margin:10px 0">$$\\mathcal{E} = \\dfrac{A_{ст}}{q}$$</p>
<p>Единица: <b>Вольт</b> (В) — та же, что для напряжения.</p>
<p>ЭДС — характеристика источника, не зависит от заряда: при $q = 1$ Кл работа сторонних сил равна $\\mathcal{E}$ Дж.</p>
`);
/* THEORY 3 — Различие ЭДС и напряжения */
html += makeCard('example', "Различие ЭДС и напряжения на полюсах", "§25", `
<p><b>Разомкнутая цепь</b> (ток не течёт): напряжение на клеммах равно ЭДС:</p>
<p style="text-align:center;margin:8px 0">$$U_{кл} = \\mathcal{E}$$</p>
<p><b>Замкнутая цепь</b> (ток $I$ течёт): напряжение на клеммах меньше ЭДС:</p>
<p style="text-align:center;margin:8px 0">$$U_{кл} = \\mathcal{E} - I r$$</p>
<p>где $r$ — <b>внутреннее сопротивление</b> источника.</p>
<p style="margin-top:10px">Внутреннее сопротивление возникает из-за движения зарядов внутри самого источника (электролит в батарейке, обмотка в генераторе).</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Пример.</b> Новая батарейка АА: $\\mathcal{E} \\approx 1{,}5$ В, $r \\approx 0{,}2$ Ом. Старая батарейка — $r$ может вырасти до нескольких ом, и напряжение на клеммах при том же токе падает заметно.</p>
`);
/* INTERACTIVE 1 — Источник тока: разомкнутая vs замкнутая цепь */
html += `<div class="wg" id="p25-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Источник тока: разомкнутая vs замкнутая цепь</div></div>
<div class="wg-help">Меняй $\\mathcal{E}$, $r$, $R$ и переключатель цепи. Смотри, как меняется ток и показания вольтметра на клеммах.</div>
<div class="sliders">
<label>$\\mathcal{E}$: <b id="p25-iv1-EL">4.5</b> В <input type="range" id="p25-iv1-E" min="1" max="12" value="4.5" step="0.5"></label>
<label>$r$: <b id="p25-iv1-rL">0.5</b> Ом <input type="range" id="p25-iv1-r" min="0.1" max="5" value="0.5" step="0.1"></label>
<label>$R$: <b id="p25-iv1-RL">10</b> Ом <input type="range" id="p25-iv1-R" min="1" max="100" value="10" step="1"></label>
</div>
<div style="display:flex;gap:8px;justify-content:center;margin:8px 0">
<button class="btn primary" id="p25-iv1-cl">Замкнуто</button>
<button class="btn" id="p25-iv1-op">Разомкнуто</button>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p25-iv1-svg" viewBox="0 0 480 260" width="100%" style="height:auto"></svg>
</div>
<div id="p25-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор ЭДС */
html += `<div class="wg" id="p25-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор ЭДС</div></div>
<div class="wg-help">$\\mathcal{E} = A_{ст}/q$. Выбери, что искать.</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:10px;flex-wrap:wrap">
<button class="btn primary" id="p25-iv2-mE">Найти $\\mathcal{E}$</button>
<button class="btn" id="p25-iv2-mA">Найти $A_{ст}$</button>
<button class="btn" id="p25-iv2-mQ">Найти $q$</button>
</div>
<div id="p25-iv2-inputs" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;align-items:end;margin-bottom:10px"></div>
<div style="display:flex;justify-content:center;margin-bottom:10px">
<button class="btn primary" id="p25-iv2-go">Вычислить</button>
</div>
<div id="p25-iv2-out" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 3 — Какие силы? */
html += `<div class="wg" id="p25-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="p25-iv3-i">1</b> / 6</span><span>Очки: <b id="p25-iv3-s">0</b> / 6</span></div>
<div id="p25-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="p25-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p25-iv3-fb"></div>
<div class="actions"><button class="btn" id="p25-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр ЭДС */
html += `<div class="wg" id="p25-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр ЭДС</div></div>
<div class="wg-help">5 задач. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p25-iv4-i">1</b> / 5</span><span>Очки: <b id="p25-iv4-s">0</b> / 5</span></div>
<div id="p25-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p25-iv4-ans" class="tinp" style="width:140px;text-align:center" step="any">
<button class="btn primary" id="p25-iv4-go">Проверить</button>
<button class="btn" id="p25-iv4-start">Заново</button>
</div>
<div class="feedback" id="p25-iv4-fb"></div>
</div>`;
html += secNav(null, 'p26');
html += readButton('p25');
box.innerHTML = html;
renderMath(box);
/* IV1 — Разомкнутая vs замкнутая цепь */
(function(){
const svg = document.getElementById('p25-iv1-svg');
const ES = document.getElementById('p25-iv1-E');
const rS = document.getElementById('p25-iv1-r');
const RS = document.getElementById('p25-iv1-R');
const EL = document.getElementById('p25-iv1-EL');
const rL = document.getElementById('p25-iv1-rL');
const RL = document.getElementById('p25-iv1-RL');
const bCl = document.getElementById('p25-iv1-cl');
const bOp = document.getElementById('p25-iv1-op');
const out = document.getElementById('p25-iv1-out');
const seen = new Set();
let _done = false;
let closed = true;
function setClosed(v){
closed = v;
if(v){ bCl.classList.add('primary'); bOp.classList.remove('primary'); }
else { bOp.classList.add('primary'); bCl.classList.remove('primary'); }
render();
}
function render(){
const E = +ES.value, r = +rS.value, R = +RS.value;
EL.textContent = E.toFixed(1);
rL.textContent = r.toFixed(1);
RL.textContent = R.toFixed(0);
const I = closed ? E / (R + r) : 0;
const Ucl = closed ? (E - I*r) : E;
// Раскладка: батарея слева, резистор справа, провода сверху/снизу, вольтметр сверху над клеммами
const W = 480, H = 260;
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 battX = 110, battY = 150;
const resX = 360, resY = 150;
const wireTopY = 80;
const wireBotY = 220;
// Источник с внутренним сопротивлением: батарея + маленький резистор r
g += PHYS.batteryEMF(battX, battY, E.toFixed(1), 'v');
// r — маленький резистор справа от батареи
g += '<rect x="'+(battX+14)+'" y="'+(battY-6)+'" width="22" height="12" fill="white" stroke="#0f172a" stroke-width="1.4"/>';
g += '<text x="'+(battX+25)+'" y="'+(battY+24)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">r='+r.toFixed(1)+'</text>';
// Внешняя нагрузка
g += PHYS.resistor(resX, resY, R.toFixed(0), 'v');
// Провода: верх — батарея(+) → резистор; низ — резистор → батарея(−)
// Верх: от точки над батареей до точки над резистором
const topL_x = battX, topR_x = resX;
const botL_x = battX+25, botR_x = resX;
// Вертикальный провод от батареи вверх к шине
g += PHYS.wire(topL_x, battY-18, topL_x, wireTopY);
g += PHYS.wire(topL_x, wireTopY, topR_x, wireTopY);
g += PHYS.wire(topR_x, wireTopY, topR_x, resY-20);
// Нижний провод (с разрывом если разомкнуто)
g += PHYS.wire(botL_x, battY+6, botL_x, wireBotY);
if(closed){
g += PHYS.wire(botL_x, wireBotY, botR_x, wireBotY);
} else {
// Разрыв в середине
const midX = (botL_x + botR_x)/2;
g += PHYS.wire(botL_x, wireBotY, midX-18, wireBotY);
g += PHYS.wire(midX+18, wireBotY, botR_x, wireBotY);
// ключ-разрыв
g += '<line x1="'+(midX-16)+'" y1="'+wireBotY+'" x2="'+(midX+10)+'" y2="'+(wireBotY-18)+'" stroke="#0f172a" stroke-width="1.8" stroke-linecap="round"/>';
g += '<circle cx="'+(midX-16)+'" cy="'+wireBotY+'" r="3" fill="#0f172a"/>';
g += '<circle cx="'+(midX+18)+'" cy="'+wireBotY+'" r="3" fill="#0f172a"/>';
g += '<text x="'+midX+'" y="'+(wireBotY+18)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#dc2626" font-weight="700">разомкнуто</text>';
}
g += PHYS.wire(botR_x, wireBotY, botR_x, resY+20);
// Вольтметр между клеммами источника (поперёк) — слева от батареи
const volX = 50, volY = 150;
g += PHYS.voltmeterSymbol(volX, volY, 16);
// провода от вольтметра к клеммам батареи
g += PHYS.wire(volX, volY-16, volX, wireTopY);
g += PHYS.wire(volX, wireTopY, topL_x, wireTopY);
g += PHYS.wire(volX, volY+16, volX, wireBotY);
g += PHYS.wire(volX, wireBotY, botL_x, wireBotY);
// показание вольтметра
g += '<text x="'+volX+'" y="'+(volY+38)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#2563eb">'+Ucl.toFixed(2)+' В</text>';
// Стрелки тока (только если замкнуто и I>0)
if(closed && I > 0.001){
// Стрелка по верхнему проводу — слева направо
g += PHYS.drawArrow(180, wireTopY, 240, wireTopY, '#dc2626', 2.4, 9);
g += '<text x="210" y="'+(wireTopY-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#dc2626">I = '+I.toFixed(2)+' А</text>';
} else if(!closed) {
g += '<text x="240" y="'+(wireTopY-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#64748b">I = 0</text>';
}
svg.innerHTML = g;
// Текстовый вывод
let txt = '';
if(closed){
txt += '<b>$I = \\dfrac{\\mathcal{E}}{R+r} = \\dfrac{'+E.toFixed(1)+'}{'+R.toFixed(0)+'+'+r.toFixed(1)+'} = '+I.toFixed(3)+'$ А</b>';
txt += '<br/><b>$U_{кл} = \\mathcal{E} - Ir = '+E.toFixed(1)+' - '+I.toFixed(3)+'\\cdot '+r.toFixed(1)+' = '+Ucl.toFixed(2)+'$ В</b>';
} else {
txt += '<b>Разомкнуто: $I = 0$, $U_{кл} = \\mathcal{E} = '+E.toFixed(1)+'$ В</b>';
}
out.innerHTML = txt;
renderMath(out);
seen.add((closed?'C':'O')+':'+E.toFixed(1)+':'+r.toFixed(1)+':'+R.toFixed(0));
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p25-iv1'); bumpProgress('p25', 15); }
}
bCl.addEventListener('click', () => setClosed(true));
bOp.addEventListener('click', () => setClosed(false));
ES.addEventListener('input', render);
rS.addEventListener('input', render);
RS.addEventListener('input', render);
setClosed(true);
})();
/* IV2 — Калькулятор ЭДС */
(function(){
let mode = 'E';
const bE = document.getElementById('p25-iv2-mE');
const bA = document.getElementById('p25-iv2-mA');
const bQ = document.getElementById('p25-iv2-mQ');
const inputs = document.getElementById('p25-iv2-inputs');
const out = document.getElementById('p25-iv2-out');
const seen = new Set();
let _done = false;
function setMode(m){
mode = m;
[bE,bA,bQ].forEach(x => x.classList.remove('primary'));
({E:bE, A:bA, Q:bQ}[m]).classList.add('primary');
if(m === 'E'){
inputs.innerHTML = '<label>$A_{ст}$ (Дж): <input type="number" id="p25-iv2-A" class="tinp" value="12" step="0.5" min="0" style="width:100%"></label>'
+ '<label>$q$ (Кл): <input type="number" id="p25-iv2-q" class="tinp" value="3" step="0.1" min="0.01" style="width:100%"></label>';
} else if(m === 'A'){
inputs.innerHTML = '<label>$\\mathcal{E}$ (В): <input type="number" id="p25-iv2-E" class="tinp" value="4.5" step="0.1" min="0" style="width:100%"></label>'
+ '<label>$q$ (Кл): <input type="number" id="p25-iv2-q" class="tinp" value="5" step="0.1" min="0.01" style="width:100%"></label>';
} else {
inputs.innerHTML = '<label>$A_{ст}$ (Дж): <input type="number" id="p25-iv2-A" class="tinp" value="20" step="0.5" min="0" style="width:100%"></label>'
+ '<label>$\\mathcal{E}$ (В): <input type="number" id="p25-iv2-E" class="tinp" value="4" step="0.1" min="0.01" style="width:100%"></label>';
}
renderMath(inputs);
out.innerHTML = '';
}
function calc(){
let html = '';
if(mode === 'E'){
const A = parseFloat(document.getElementById('p25-iv2-A').value);
const q = parseFloat(document.getElementById('p25-iv2-q').value);
if(!isFinite(A) || !isFinite(q) || q <= 0){ out.innerHTML = '<b>Введи корректные значения.</b>'; return; }
const E = A / q;
html += '<b>$\\mathcal{E} = \\dfrac{A_{ст}}{q} = \\dfrac{'+A+'}{'+q+'} = '+E.toFixed(3)+'$ В</b>';
} else if(mode === 'A'){
const E = parseFloat(document.getElementById('p25-iv2-E').value);
const q = parseFloat(document.getElementById('p25-iv2-q').value);
if(!isFinite(E) || !isFinite(q)){ out.innerHTML = '<b>Введи корректные значения.</b>'; return; }
const A = E * q;
html += '<b>$A_{ст} = \\mathcal{E}\\cdot q = '+E+'\\cdot '+q+' = '+A.toFixed(3)+'$ Дж</b>';
} else {
const A = parseFloat(document.getElementById('p25-iv2-A').value);
const E = parseFloat(document.getElementById('p25-iv2-E').value);
if(!isFinite(A) || !isFinite(E) || E <= 0){ out.innerHTML = '<b>Введи корректные значения.</b>'; return; }
const q = A / E;
html += '<b>$q = \\dfrac{A_{ст}}{\\mathcal{E}} = \\dfrac{'+A+'}{'+E+'} = '+q.toFixed(3)+'$ Кл</b>';
}
out.innerHTML = html;
renderMath(out);
seen.add(mode+':'+Date.now());
if(!_done && seen.size >= 3){ _done = true; addXp(10, 'p25-iv2'); bumpProgress('p25', 15); }
}
bE.addEventListener('click', () => setMode('E'));
bA.addEventListener('click', () => setMode('A'));
bQ.addEventListener('click', () => setMode('Q'));
document.getElementById('p25-iv2-go').addEventListener('click', calc);
setMode('E');
})();
/* IV3 — Какие сторонние силы? */
(function(){
const OPTS = ['Химические','Механические','Световые','Тепловые'];
const Q = [
{ q:'В гальваническом элементе (батарейке).', ans:0, why:'Реакция между электродами и электролитом разделяет заряды.' },
{ q:'В генераторе электростанции.', ans:1, why:'Вращение ротора в магнитном поле разделяет заряды.' },
{ q:'В фотоэлементе (солнечной батарее).', ans:2, why:'Световой поток выбивает электроны и создаёт ЭДС.' },
{ q:'В аккумуляторе автомобиля.', ans:0, why:'Это тоже химический источник тока.' },
{ q:'В термоэлементе (термопаре).', ans:3, why:'Разность температур контактов разных металлов создаёт ЭДС.' },
{ q:'В велосипедной динамо-машине.', ans:1, why:'Вращение магнита от колеса — механическая сила.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p25-iv3-q');
const oEl = document.getElementById('p25-iv3-opts');
const fb = document.getElementById('p25-iv3-fb');
const iEl = document.getElementById('p25-iv3-i');
const sEl = document.getElementById('p25-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p25-iv3'); bumpProgress('p25', 25); }
else if(score >= 4){ addXp(8, 'p25-iv3'); bumpProgress('p25', 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('p25-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр ЭДС */
(function(){
const Q = [
{ q:'Сторонние силы совершили работу $A = 10$ Дж при заряде $q = 5$ Кл. ЭДС в В?', ans:2, tol:0.1, hint:'$\\mathcal{E} = A/q = 10/5 = 2$ В.' },
{ q:'Аккумулятор $\\mathcal{E} = 12$ В, $r = 0{,}5$ Ом, нагрузка $R = 5{,}5$ Ом. Сила тока в А?', ans:2, tol:0.1, hint:'$I = \\mathcal{E}/(R+r) = 12/6 = 2$ А.' },
{ q:'Тот же аккумулятор. Напряжение на клеммах в В?', ans:11, tol:0.6, hint:'$U_{кл} = \\mathcal{E} - Ir = 12 - 2\\cdot 0{,}5 = 11$ В.' },
{ q:'За $t = 5$ с через сечение прошёл заряд $q = 2$ Кл. Сила тока в А?', ans:0.4, tol:0.05, hint:'$I = q/t = 2/5 = 0{,}4$ А.' },
{ q:'Идеальная батарейка $\\mathcal{E} = 1{,}5$ В, $r = 0$, $R = 3$ Ом. Сила тока в А?', ans:0.5, tol:0.05, hint:'$I = \\mathcal{E}/R = 1{,}5/3 = 0{,}5$ А.' }
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p25-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p25-iv4'); bumpProgress('p25', 25); }
else if(score >= 3){ addXp(8, 'p25-iv4'); bumpProgress('p25', 15); }
return;
}
document.getElementById('p25-iv4-i').textContent = (i+1);
document.getElementById('p25-iv4-s').textContent = score;
document.getElementById('p25-iv4-q').innerHTML = Q[i].q;
document.getElementById('p25-iv4-ans').value = '';
renderMath(document.getElementById('p25-iv4-q'));
document.getElementById('p25-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p25-iv4-fb');
const raw = document.getElementById('p25-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p25-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p25-iv4-go').addEventListener('click', go);
document.getElementById('p25-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p25-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p25');
}
function build_p26(){
const box = document.getElementById('p26-body');
let html = '';
/* THEORY 1 — Закон Ома для полной цепи */
html += makeCard('theory', "Закон Ома для полной цепи", "§26", `
<p><b>Закон Ома для полной электрической цепи:</b></p>
<p style="text-align:center;margin:10px 0">$$I = \\dfrac{\\mathcal{E}}{R + r}$$</p>
<ul style="margin:6px 0 6px 18px">
<li>$\\mathcal{E}$ — ЭДС источника (В)</li>
<li>$R$ — сопротивление <b>внешней</b> цепи (нагрузки) — Ом</li>
<li>$r$ — <b>внутреннее</b> сопротивление источника — Ом</li>
<li>$I$ — сила тока в цепи (А)</li>
</ul>
<p style="margin-top:10px">Эту формулу легко понять физически: ЭДС «толкает» ток через две последовательно соединённых «препятствия» — внешнее $R$ и внутреннее $r$.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"><b>Закон Ома для участка цепи</b> (повторение из 8 класса): $I = U/R$. Это частный случай — для участка без источника.</p>
`);
/* THEORY 2 — Напряжение и КПД источника */
html += makeCard('rule', "Напряжение на полюсах и КПД источника", "§26", `
<p><b>Напряжение на внешней цепи</b> (= на клеммах источника, когда есть ток):</p>
<p style="text-align:center;margin:10px 0">$$U = IR = \\mathcal{E} - I r$$</p>
<p>Падение напряжения внутри источника: $\\mathcal{E} - U = Ir$.</p>
<p style="margin-top:10px"><b>КПД источника тока</b> — отношение полезной мощности (на нагрузке) к полной мощности источника:</p>
<p style="text-align:center;margin:10px 0">$$\\eta = \\dfrac{U}{\\mathcal{E}} = \\dfrac{R}{R+r}$$</p>
<p>Чем меньше $r$ (или больше $R$), тем выше КПД.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--warn-bg,#fef3c7);border-left:4px solid var(--warn,#f59e0b);border-radius:9px"><b>Максимальная мощность во внешней цепи</b> достигается при $R = r$. При этом $\\eta = 50\\%$, а $P_{max} = \\dfrac{\\mathcal{E}^2}{4r}$.</p>
`);
/* THEORY 3 — Короткое замыкание */
html += makeCard('example', "Короткое замыкание", "§26", `
<p><b>Короткое замыкание (КЗ)</b> — соединение клемм источника проводником с очень малым сопротивлением (когда $R \\ll r$ или $R \\approx 0$).</p>
<p>Ток короткого замыкания:</p>
<p style="text-align:center;margin:10px 0">$$I_{кз} = \\dfrac{\\mathcal{E}}{r}$$</p>
<p>Это <b>максимально возможный ток</b> от данного источника. Если $r$ мало — $I_{кз}$ огромен. Последствия:</p>
<ul style="margin:6px 0 6px 18px">
<li>Перегрев проводов.</li>
<li>Расплавление изоляции.</li>
<li>Пожар.</li>
<li>Взрыв батареи (особенно у аккумуляторов).</li>
</ul>
<p style="margin-top:10px"><b>Защита от КЗ:</b></p>
<ul style="margin:6px 0 6px 18px">
<li><b>Предохранители</b> — плавкие или электронные.</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> Автомобильный аккумулятор $\\mathcal{E} = 12$ В, $r = 0{,}005$ Ом. $I_{кз} = 12/0{,}005 = 2400$ А — расплавит любой тонкий провод за секунды.</p>
`);
/* INTERACTIVE 1 — Конструктор электрической цепи */
html += `<div class="wg" id="p26-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конструктор электрической цепи</div></div>
<div class="wg-help">Двигай $\\mathcal{E}$, $r$, $R$. Смотри ток, напряжение на клеммах, мощность и КПД в реальном времени.</div>
<div class="sliders">
<label>$\\mathcal{E}$: <b id="p26-iv1-EL">6</b> В <input type="range" id="p26-iv1-E" min="1" max="12" value="6" step="0.5"></label>
<label>$r$: <b id="p26-iv1-rL">0.5</b> Ом <input type="range" id="p26-iv1-r" min="0.1" max="5" value="0.5" step="0.1"></label>
<label>$R$: <b id="p26-iv1-RL">5</b> Ом <input type="range" id="p26-iv1-R" min="0.1" max="100" value="5" step="0.5"></label>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px">
<svg id="p26-iv1-svg" viewBox="0 0 480 320" width="100%" style="height:auto"></svg>
</div>
<div id="p26-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.75;text-align:center"></div>
<div id="p26-iv1-warn" style="margin-top:8px;padding:8px 12px;background:#fef2f2;border-left:3px solid #dc2626;color:#7f1d1d;border-radius:8px;font-size:.88rem;display:none">⚠ Сопротивление почти ноль — это близко к режиму короткого замыкания!</div>
</div>`;
/* INTERACTIVE 2 — Универсальный калькулятор Ома */
html += `<div class="wg" id="p26-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Универсальный калькулятор закона Ома</div></div>
<div class="wg-help">$I = \\mathcal{E}/(R+r)$. Выбери, что искать.</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:10px;flex-wrap:wrap">
<button class="btn primary" id="p26-iv2-mI">Найти $I$</button>
<button class="btn" id="p26-iv2-mE">Найти $\\mathcal{E}$</button>
<button class="btn" id="p26-iv2-mR">Найти $R$</button>
<button class="btn" id="p26-iv2-mr">Найти $r$</button>
</div>
<div id="p26-iv2-inputs" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;align-items:end;margin-bottom:10px"></div>
<div style="display:flex;justify-content:center;margin-bottom:10px">
<button class="btn primary" id="p26-iv2-go">Вычислить</button>
</div>
<div id="p26-iv2-out" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.75;text-align:center"></div>
</div>`;
/* INTERACTIVE 3 — Что произойдёт с током? */
html += `<div class="wg" id="p26-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Что произойдёт с силой тока?</div></div>
<div class="wg-help">Опираясь на $I = \\mathcal{E}/(R+r)$, выбери, как изменится ток.</div>
<div class="score-display"><span>Задача <b id="p26-iv3-i">1</b> / 6</span><span>Очки: <b id="p26-iv3-s">0</b> / 6</span></div>
<div id="p26-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="p26-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px"></div>
<div class="feedback" id="p26-iv3-fb"></div>
<div class="actions"><button class="btn" id="p26-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр закона Ома */
html += `<div class="wg" id="p26-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр закона Ома</div></div>
<div class="wg-help">6 задач. Допуск $\\pm 5\\%$.</div>
<div class="score-display"><span>Задача <b id="p26-iv4-i">1</b> / 6</span><span>Очки: <b id="p26-iv4-s">0</b> / 6</span></div>
<div id="p26-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p26-iv4-ans" class="tinp" style="width:140px;text-align:center" step="any">
<button class="btn primary" id="p26-iv4-go">Проверить</button>
<button class="btn" id="p26-iv4-start">Заново</button>
</div>
<div class="feedback" id="p26-iv4-fb"></div>
</div>`;
html += secNav('p25', 'final4');
html += readButton('p26');
box.innerHTML = html;
renderMath(box);
/* IV1 — Конструктор цепи */
(function(){
const svg = document.getElementById('p26-iv1-svg');
const ES = document.getElementById('p26-iv1-E');
const rS = document.getElementById('p26-iv1-r');
const RS = document.getElementById('p26-iv1-R');
const EL = document.getElementById('p26-iv1-EL');
const rL = document.getElementById('p26-iv1-rL');
const RL = document.getElementById('p26-iv1-RL');
const out = document.getElementById('p26-iv1-out');
const warn = document.getElementById('p26-iv1-warn');
const seen = new Set();
let _done = false;
function render(){
const E = +ES.value, r = +rS.value, R = +RS.value;
EL.textContent = E.toFixed(1);
rL.textContent = r.toFixed(1);
RL.textContent = R.toFixed(1);
const I = E / (R + r);
const Ucl = E - I*r;
const Pvn = I*I * R;
const eta = R / (R + r) * 100;
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">Полная электрическая цепь</text>';
// Прямоугольная цепь
const battX = 90, battY = 180;
const amX = 240, amY = 80; // амперметр сверху по центру
const resX = 390, resY = 180;
const volX = 60, volY = 240; // вольтметр снизу-слева
// Источник
g += PHYS.batteryEMF(battX, battY, E.toFixed(1), 'v');
// Внутреннее сопротивление r — рядом
g += '<rect x="'+(battX+14)+'" y="'+(battY-6)+'" width="22" height="12" fill="white" stroke="#0f172a" stroke-width="1.4"/>';
g += '<text x="'+(battX+25)+'" y="'+(battY+24)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">r='+r.toFixed(1)+'</text>';
// Внешняя нагрузка
g += PHYS.resistor(resX, resY, R.toFixed(1), 'v');
// Амперметр сверху
g += PHYS.ammeterSymbol(amX, amY, 16);
// Провода
// (+) батареи вверх → амперметр → к резистору
g += PHYS.wire(battX, battY-18, battX, amY);
g += PHYS.wire(battX, amY, amX-16, amY);
g += PHYS.wire(amX+16, amY, resX, amY);
g += PHYS.wire(resX, amY, resX, resY-20);
// (−) батареи вниз → к резистору снизу
g += PHYS.wire(battX+25, battY+6, battX+25, 270);
g += PHYS.wire(battX+25, 270, resX, 270);
g += PHYS.wire(resX, 270, resX, resY+20);
// Вольтметр между клеммами источника (по бокам, слева)
g += PHYS.voltmeterSymbol(volX, volY, 16);
g += PHYS.wire(volX, volY-16, volX, amY);
g += PHYS.wire(volX, amY, battX, amY);
g += PHYS.wire(volX, volY+16, volX, 270);
g += PHYS.wire(volX, 270, battX+25, 270);
g += '<text x="'+volX+'" y="'+(volY+38)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#2563eb">'+Ucl.toFixed(2)+' В</text>';
// Стрелка тока и подпись возле амперметра
g += PHYS.drawArrow(amX-50, amY-22, amX-18, amY-22, '#dc2626', 2.2, 9);
g += '<text x="'+(amX-30)+'" y="'+(amY-28)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#dc2626">I = '+I.toFixed(2)+' А</text>';
svg.innerHTML = g;
let txt = '';
txt += '<b>$I = \\dfrac{\\mathcal{E}}{R+r} = \\dfrac{'+E.toFixed(1)+'}{'+R.toFixed(1)+'+'+r.toFixed(1)+'} = '+I.toFixed(3)+'$ А</b>';
txt += '<br/><b>$U_{кл} = '+Ucl.toFixed(2)+'$ В &nbsp; $P_{вн} = I^2R = '+Pvn.toFixed(2)+'$ Вт &nbsp; $\\eta = '+eta.toFixed(1)+'\\%$</b>';
out.innerHTML = txt;
renderMath(out);
warn.style.display = (R < 0.5) ? 'block' : 'none';
seen.add(E.toFixed(1)+':'+r.toFixed(1)+':'+R.toFixed(1));
if(!_done && seen.size >= 4){ _done = true; addXp(10, 'p26-iv1'); bumpProgress('p26', 15); }
}
ES.addEventListener('input', render);
rS.addEventListener('input', render);
RS.addEventListener('input', render);
render();
})();
/* IV2 — Универсальный калькулятор */
(function(){
let mode = 'I';
const bI = document.getElementById('p26-iv2-mI');
const bE = document.getElementById('p26-iv2-mE');
const bR = document.getElementById('p26-iv2-mR');
const br = document.getElementById('p26-iv2-mr');
const inputs = document.getElementById('p26-iv2-inputs');
const out = document.getElementById('p26-iv2-out');
const seen = new Set();
let _done = false;
function setMode(m){
mode = m;
[bI,bE,bR,br].forEach(x => x.classList.remove('primary'));
({I:bI, E:bE, R:bR, r:br}[m]).classList.add('primary');
if(m === 'I'){
inputs.innerHTML = '<label>$\\mathcal{E}$ (В): <input type="number" id="p26-iv2-E" class="tinp" value="12" step="0.5" style="width:100%"></label>'
+ '<label>$R$ (Ом): <input type="number" id="p26-iv2-R" class="tinp" value="5" step="0.5" style="width:100%"></label>'
+ '<label>$r$ (Ом): <input type="number" id="p26-iv2-r" class="tinp" value="1" step="0.1" style="width:100%"></label>';
} else if(m === 'E'){
inputs.innerHTML = '<label>$I$ (А): <input type="number" id="p26-iv2-I" class="tinp" value="2" step="0.1" style="width:100%"></label>'
+ '<label>$R$ (Ом): <input type="number" id="p26-iv2-R" class="tinp" value="5" step="0.5" style="width:100%"></label>'
+ '<label>$r$ (Ом): <input type="number" id="p26-iv2-r" class="tinp" value="1" step="0.1" style="width:100%"></label>';
} else if(m === 'R'){
inputs.innerHTML = '<label>$\\mathcal{E}$ (В): <input type="number" id="p26-iv2-E" class="tinp" value="12" step="0.5" style="width:100%"></label>'
+ '<label>$I$ (А): <input type="number" id="p26-iv2-I" class="tinp" value="2" step="0.1" style="width:100%"></label>'
+ '<label>$r$ (Ом): <input type="number" id="p26-iv2-r" class="tinp" value="1" step="0.1" style="width:100%"></label>';
} else {
inputs.innerHTML = '<label>$\\mathcal{E}$ (В): <input type="number" id="p26-iv2-E" class="tinp" value="12" step="0.5" style="width:100%"></label>'
+ '<label>$I$ (А): <input type="number" id="p26-iv2-I" class="tinp" value="2" step="0.1" style="width:100%"></label>'
+ '<label>$R$ (Ом): <input type="number" id="p26-iv2-R" class="tinp" value="5" step="0.5" style="width:100%"></label>';
}
renderMath(inputs);
out.innerHTML = '';
}
function val(id){ return parseFloat(document.getElementById(id).value); }
function calc(){
let html = '';
if(mode === 'I'){
const E = val('p26-iv2-E'), R = val('p26-iv2-R'), r = val('p26-iv2-r');
if(!isFinite(E)||!isFinite(R)||!isFinite(r)||(R+r)<=0){ out.innerHTML='<b>Введи корректные значения.</b>'; return; }
const I = E/(R+r);
html += '<b>$I = \\dfrac{\\mathcal{E}}{R+r} = \\dfrac{'+E+'}{'+R+'+'+r+'} = '+I.toFixed(3)+'$ А</b>';
} else if(mode === 'E'){
const I = val('p26-iv2-I'), R = val('p26-iv2-R'), r = val('p26-iv2-r');
if(!isFinite(I)||!isFinite(R)||!isFinite(r)){ out.innerHTML='<b>Введи корректные значения.</b>'; return; }
const E = I*(R+r);
html += '<b>$\\mathcal{E} = I(R+r) = '+I+'\\cdot ('+R+'+'+r+') = '+E.toFixed(3)+'$ В</b>';
} else if(mode === 'R'){
const E = val('p26-iv2-E'), I = val('p26-iv2-I'), r = val('p26-iv2-r');
if(!isFinite(E)||!isFinite(I)||!isFinite(r)||I<=0){ out.innerHTML='<b>Введи корректные значения.</b>'; return; }
const R = E/I - r;
html += '<b>$R = \\dfrac{\\mathcal{E}}{I} - r = \\dfrac{'+E+'}{'+I+'} - '+r+' = '+R.toFixed(3)+'$ Ом</b>';
} else {
const E = val('p26-iv2-E'), I = val('p26-iv2-I'), R = val('p26-iv2-R');
if(!isFinite(E)||!isFinite(I)||!isFinite(R)||I<=0){ out.innerHTML='<b>Введи корректные значения.</b>'; return; }
const r = E/I - R;
html += '<b>$r = \\dfrac{\\mathcal{E}}{I} - R = \\dfrac{'+E+'}{'+I+'} - '+R+' = '+r.toFixed(3)+'$ Ом</b>';
}
out.innerHTML = html;
renderMath(out);
seen.add(mode+':'+Date.now());
if(!_done && seen.size >= 3){ _done = true; addXp(10, 'p26-iv2'); bumpProgress('p26', 15); }
}
bI.addEventListener('click', () => setMode('I'));
bE.addEventListener('click', () => setMode('E'));
bR.addEventListener('click', () => setMode('R'));
br.addEventListener('click', () => setMode('r'));
document.getElementById('p26-iv2-go').addEventListener('click', calc);
setMode('I');
})();
/* IV3 — Что произойдёт с током? */
(function(){
const OPTS = ['Увеличится','Уменьшится','Не изменится'];
const Q = [
{ q:'Увеличить $R$ (при постоянных $\\mathcal{E}$ и $r$).', ans:1, why:'$I = \\mathcal{E}/(R+r)$ — рост знаменателя уменьшает ток.' },
{ q:'Уменьшить $R$ (при постоянных $\\mathcal{E}$ и $r$).', ans:0, why:'Знаменатель уменьшился — ток вырос.' },
{ q:'Увеличить $\\mathcal{E}$ (при постоянных $R$ и $r$).', ans:0, why:'Числитель вырос — ток увеличился.' },
{ q:'Уменьшить $\\mathcal{E}$ (при постоянных $R$ и $r$).', ans:1, why:'Числитель уменьшился — ток упал.' },
{ q:'Разомкнуть цепь.', ans:1, why:'Цепь разорвана — ток падает до нуля.' },
{ q:'Замкнуть цепь накоротко ($R \\to 0$).', ans:0, why:'$I \\to \\mathcal{E}/r$ — это максимально возможный ток.' }
];
let i = 0, score = 0;
const qEl = document.getElementById('p26-iv3-q');
const oEl = document.getElementById('p26-iv3-opts');
const fb = document.getElementById('p26-iv3-fb');
const iEl = document.getElementById('p26-iv3-i');
const sEl = document.getElementById('p26-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p26-iv3'); bumpProgress('p26', 25); }
else if(score >= 4){ addXp(8, 'p26-iv3'); bumpProgress('p26', 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('p26-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — Тренажёр закона Ома */
(function(){
const Q = [
{ q:'$\\mathcal{E} = 12$ В, $r = 1$ Ом, $R = 5$ Ом. Сила тока $I$ в А?', ans:2, tol:0.1, hint:'$I = \\mathcal{E}/(R+r) = 12/6 = 2$ А.' },
{ q:'Те же данные: $\\mathcal{E} = 12$ В, $r = 1$ Ом, $R = 5$ Ом. Напряжение на клеммах $U_{кл}$ в В?', ans:10, tol:0.5, hint:'$U_{кл} = IR = 2\\cdot 5 = 10$ В.' },
{ q:'$\\mathcal{E} = 10$ В, $r = 0{,}5$ Ом. Ток короткого замыкания в А?', ans:20, tol:1, hint:'$I_{кз} = \\mathcal{E}/r = 10/0{,}5 = 20$ А.' },
{ q:'$\\mathcal{E} = 6$ В, $r = 0{,}5$ Ом, $R = 2{,}5$ Ом. КПД источника в %?', ans:83, tol:1, hint:'$\\eta = R/(R+r) = 2{,}5/3 \\approx 0{,}833 \\approx 83\\%$.' },
{ q:'Батарея $\\mathcal{E} = 4{,}5$ В, $I = 0{,}9$ А, $r = 0{,}5$ Ом. Внешнее сопротивление $R$ в Ом?', ans:4.5, tol:0.3, hint:'$R = \\mathcal{E}/I - r = 5 - 0{,}5 = 4{,}5$ Ом.' },
{ q:'При условии $R = r$ — каков КПД источника в %?', ans:50, tol:1, hint:'$\\eta = R/(R+r) = R/(2R) = 0{,}5 = 50\\%$.' }
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p26-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p26-iv4'); bumpProgress('p26', 25); }
else if(score >= 4){ addXp(8, 'p26-iv4'); bumpProgress('p26', 15); }
return;
}
document.getElementById('p26-iv4-i').textContent = (i+1);
document.getElementById('p26-iv4-s').textContent = score;
document.getElementById('p26-iv4-q').innerHTML = Q[i].q;
document.getElementById('p26-iv4-ans').value = '';
renderMath(document.getElementById('p26-iv4-q'));
document.getElementById('p26-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p26-iv4-fb');
const raw = document.getElementById('p26-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol + 0.001){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
document.getElementById('p26-iv4-s').textContent = score;
i++;
setTimeout(show, 1800);
}
document.getElementById('p26-iv4-go').addEventListener('click', go);
document.getElementById('p26-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p26-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p26');
}
function build_final4(){
const box = document.getElementById('final4-body');
let html = '';
/* Часть А — Шпаргалка главы (2 mini-карточки) */
const SHEET = [
{ t:'§ 25 · ЭДС источника', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#0ea5e9" stroke-width="2" style="width:18px;height:18px"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>', body:'$\\mathcal{E} = A_{ст}/q$. Сторонние силы: химические, механические, световые, тепловые. $I = q/t$ (А).' },
{ t:'§ 26 · Закон Ома для полной цепи', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" style="width:18px;height:18px"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>', body:'$I = \\mathcal{E}/(R+r)$. $U_{кл} = \\mathcal{E} - Ir$. $\\eta = R/(R+r)$. КЗ: $I_{кз} = \\mathcal{E}/r$.' },
];
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 4 — Постоянный электрический ток</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Ключевые формулы и идеи двух параграфов главы — повтори перед битвой с боссами.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,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>`;
/* Часть Б — 5 боссов intro */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 4</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач по §§25–26. За каждого побеждённого босса: <b>+10 XP, +18% к прогрессу</b>. Победишь всех — ачивка <b>«Мастер тока»</b> и <b>+50 XP бонус</b>.</p>
<p style="margin-top:6px;font-size:.92rem;color:var(--muted)">Допуск $\\pm 5\\%$.</p>
</div>
</div>`;
html += '<div id="ch4-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="ch4-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="ch4-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch4-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#d97706,#fbbf24);transition:width .35s"></div>
</div>
<div id="ch4-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #f59e0b">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#92400e;font-size:1.05rem;margin-bottom:6px">Мастер тока</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 4 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/physics-10-ch5" style="text-decoration:none">Дальше: Глава 5 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p26', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#0ea5e9',
title:'Страж Силы Тока',
tag:'§ 25',
q:'Через сечение проводника прошёл заряд $q = 30$ Кл за время $t = 60$ с. Сила тока $I$ в А?',
ans:0.5, tol:0.05,
hint:'$I = q/t = 30/60 = 0{,}5$ А.'
},
{
n:2, color:'#10b981',
title:'Хранитель ЭДС',
tag:'§ 25',
q:'Сторонние силы совершили работу $A_{ст} = 18$ Дж при перемещении заряда $q = 6$ Кл. Найди ЭДС в В.',
ans:3, tol:0.2,
hint:'$\\mathcal{E} = A_{ст}/q = 18/6 = 3$ В.'
},
{
n:3, color:'#f59e0b',
title:'Повелитель Закона Ома',
tag:'§ 26',
q:'Источник $\\mathcal{E} = 24$ В, $r = 1$ Ом, нагрузка $R = 5$ Ом. Сила тока в А?',
ans:4, tol:0.2,
hint:'$I = \\mathcal{E}/(R+r) = 24/6 = 4$ А.'
},
{
n:4, color:'#7c3aed',
title:'Властелин КПД',
tag:'§ 26',
q:'$\\mathcal{E} = 10$ В, $r = 0{,}2$ Ом, $R = 4{,}8$ Ом. Найди КПД источника в %.',
ans:96, tol:1,
hint:'$\\eta = R/(R+r) = 4{,}8/5 = 0{,}96 = 96\\%$.'
},
{
n:5, color:'#dc2626',
title:'Магистр Постоянного Тока',
tag:'§§ 2526',
q:'Аккумулятор $\\mathcal{E} = 6$ В, $r = 0{,}5$ Ом. При замыкании на лампочку напряжение на клеммах упало до $U_{кл} = 5$ В. Найди сопротивление лампочки в Ом.',
ans:2.5, tol:0.15,
hint:'$Ir = \\mathcal{E} - U_{кл} = 1$ В $\\Rightarrow I = 1/0{,}5 = 2$ А. $R = U_{кл}/I = 5/2 = 2{,}5$ Ом.'
},
];
const cont = document.getElementById('ch4-bosses-container');
const STATE_KEY = 'physics10_ch4_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="boss4-'+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="boss4-'+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="boss4-'+b.n+'-ans" class="tinp" style="width:130px;text-align:center" step="0.01" placeholder="число">'
+'<button class="btn primary" id="boss4-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss4-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss4-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch4-boss-overall');
const fill = document.getElementById('ch4-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('ch4-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch4_done')){
achievement('ch4_done','Мастер тока');
addXp(50, 'ch4-bonus');
bumpProgress('final4', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss4-'+b.n+'-card');
const goBtn = document.getElementById('boss4-'+b.n+'-go');
const hintBtn = document.getElementById('boss4-'+b.n+'-hint');
const ansInp = document.getElementById('boss4-'+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('boss4-'+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-ch4-'+b.n);
bumpProgress('final4', 18);
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('boss4-'+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>