Files
Learn_System/frontend/textbooks/physics_8_lab.html
Maxim Dolgolyov 382dff3879 feat(phys8 lab): Phase 4 — Лабораторный практикум (визуал + 7 IV-6)
Hero: emerald-зелёный градиент (стиль 'химической лаборатории'),
flask SVG-watermark, live meter '7/7 ЛР'.

7 section watermarks: термометр, печь, цепь, посл/парал, P, угол.

7 IV-6 интерактивов:
ЛР1 Теплообмен: 2 ёмкости (0.5 кг + 1 кг), scrubbers T₁/T₂,
кнопка 'Смешать' с tween-анимацией, формула баланса.
ЛР2 Удельная теплоёмкость: scrubbers P/m/t, нагреватель,
термометр с цветовой картой, c=Q/(mΔT) для воды (4200).
ЛР3 Простейшая цепь: батарея+амперметр+лампа+вольтметр,
scrubber U, live показания приборов.
ЛР4 Последовательное соединение: U=U₁+U₂, I одинаков.
ЛР5 Параллельное соединение: U одинаков, I=I₁+I₂.
ЛР6 Работа и мощность: U·I·t, лампа brightness ∝ P,
лучи при P>100 Вт.
ЛР7 Закон отражения: луч + нормаль + угловые дуги,
verdict 'α=β'.
2026-05-30 10:29:50 +03:00

1426 lines
114 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Физика 8 · Лабораторный практикум</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">
<link rel="stylesheet" href="/css/phys8-interactives.css">
<link rel="stylesheet" href="/css/phys8-design-system.css">
<link rel="stylesheet" href="/css/phys-textbook-widgets.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>
<script src="/js/phys8-helpers.js" defer></script>
<script src="/js/phys8-drag.js" defer></script>
<script src="/js/phys8-anim.js" defer></script>
<script src="/js/optics.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:#ecfdf5; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#7c3aed; --pri2:#5b21b6; --pri-soft:#ede9fe;
--acc:#a78bfa; --acc2:#7c3aed; --acc-soft:#ede9fe;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --ink:#fef9e7; --muted:#a39070; --border:#2a2512}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#064e3b 0%,#10b981 55%,#86efac 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.2);min-height:130px}
.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 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-lr1"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-lr2"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-lr3"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-lr4"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-lr5"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-lr6"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-lr7"]{ --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.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-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)}
.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}
.sec{transition:opacity .25s}
</style>
</head>
<body>
<header class="p8-hero" style="background:linear-gradient(115deg,#064e3b 0%,#10b981 60%,#6ee7b7 100%)">
<div class="p8-hero-wm"><svg viewBox="0 0 100 100" aria-hidden="true">
<path d="M40 12 L40 38 L18 80 C 15 88, 20 92, 26 92 L74 92 C 80 92, 85 88, 82 80 L60 38 L60 12 Z M 35 12 L65 12" stroke="currentColor" stroke-width="4" fill="none"/>
<circle cx="38" cy="64" r="3" fill="currentColor"/>
<circle cx="55" cy="76" r="3" fill="currentColor"/>
<circle cx="44" cy="80" r="2" fill="currentColor"/>
</svg></div>
<div class="p8-hero-meter" id="p8-meter-lab"><span id="p8-meter-val">7</span>/7 ЛР</div>
<div class="p8-hero-inner">
<div class="p8-hero-eyebrow">Лабораторный практикум · 7 ЛР</div>
<h1 class="p8-hero-title">Виртуальная лаборатория</h1>
<div class="p8-hero-sub">Перетаскивайте термометры, нагреватели, динамометры и измерительные приборы. Собирайте установки и записывайте результаты.</div>
<div class="hdr-side" style="margin-top:18px;display:flex;gap:8px;flex-wrap:wrap;position:relative;z-index:1">
<a href="/textbook/physics-8" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 8</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>Лабораторный практикум 8 класса</h2>
<p>7 виртуальных лабораторных работ по тепловым явлениям, электрическим цепям и оптике. Каждая работа — это симуляция эксперимента + измерения + расчёт + автоматический отчёт.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('lr1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать ЛР 1</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-lr1" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr1" aria-hidden="true"><svg viewBox="0 0 100 100"><rect x="30" y="20" width="40" height="60" fill="none" stroke="currentColor" stroke-width="4" rx="4"/><rect x="36" y="40" width="28" height="35" fill="currentColor" opacity="0.6"/></svg></div><div class="sec-header"><span class="sec-num">ЛР 1</span><h2 class="sec-h">Изучение явления теплообмена при смешивании воды разной температуры</h2></div><div id="lr1-body"></div></section>
<section id="sec-lr2" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr2" aria-hidden="true"><svg viewBox="0 0 100 100"><rect x="20" y="40" width="60" height="40" fill="none" stroke="currentColor" stroke-width="3"/><circle cx="50" cy="60" r="10" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">ЛР 2</span><h2 class="sec-h">Определение удельной теплоёмкости твёрдого тела</h2></div><div id="lr2-body"></div></section>
<section id="sec-lr3" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr3" aria-hidden="true"><svg viewBox="0 0 100 100"><rect x="20" y="40" width="60" height="30" fill="none" stroke="currentColor" stroke-width="3"/><circle cx="50" cy="55" r="8" fill="none" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">ЛР 3</span><h2 class="sec-h">Сборка простейшей электрической цепи и измерение силы тока и напряжения</h2></div><div id="lr3-body"></div></section>
<section id="sec-lr4" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr4" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M20 50 L30 50 L35 40 L45 60 L55 40 L65 60 L70 50 L80 50" stroke="currentColor" stroke-width="3" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">ЛР 4</span><h2 class="sec-h">Изучение последовательного соединения проводников</h2></div><div id="lr4-body"></div></section>
<section id="sec-lr5" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr5" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M20 30 L80 30 M20 70 L80 70 M50 30 L50 70" stroke="currentColor" stroke-width="3" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">ЛР 5</span><h2 class="sec-h">Изучение параллельного соединения проводников</h2></div><div id="lr5-body"></div></section>
<section id="sec-lr6" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr6" aria-hidden="true"><svg viewBox="0 0 100 100"><text x="50" y="60" font-family="Unbounded" font-size="32" font-weight="900" fill="currentColor" text-anchor="middle">P</text></svg></div><div class="sec-header"><span class="sec-num">ЛР 6</span><h2 class="sec-h">Определение работы и мощности электрического тока</h2></div><div id="lr6-body"></div></section>
<section id="sec-lr7" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-lr7" aria-hidden="true"><svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="4"/><line x1="50" y1="50" x2="80" y2="20" stroke="currentColor" stroke-width="4"/></svg></div><div class="sec-header"><span class="sec-num">ЛР 7</span><h2 class="sec-h">Изучение явления отражения света</h2></div><div id="lr7-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">Интерактивный учебник «Физика 8» · Лабораторный практикум · 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:'lr1', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 7;
const _TB_SLUG = 'physics-8-lab';
const LS_PREFIX = 'physics8_lab';
const LS_XP = 'physics8_xp';
const PARAS = [
{ id:'lr1', num:'ЛР 1', name:'Изучение явления теплообмена при смешивании воды разной температуры', sub:'§ 6' },
{ id:'lr2', num:'ЛР 2', name:'Определение удельной теплоёмкости твёрдого тела', sub:'§ 6' },
{ id:'lr3', num:'ЛР 3', name:'Сборка простейшей электрической цепи и измерение силы тока и напряжения', sub:'§ 21' },
{ id:'lr4', num:'ЛР 4', name:'Изучение последовательного соединения проводников', sub:'§ 24' },
{ id:'lr5', num:'ЛР 5', name:'Изучение параллельного соединения проводников', sub:'§ 25' },
{ id:'lr6', num:'ЛР 6', name:'Определение работы и мощности электрического тока', sub:'§ 26' },
{ id:'lr7', num:'ЛР 7', name:'Изучение явления отражения света', sub:'§ 34' }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
const ACH_LABELS = {
start:"Начало практикума!",
lr1_done:"Изучение явления теплообмена при смешивании воды разной температуры завершена!",
lr2_done:"Определение удельной теплоёмкости твёрдого тела завершена!",
lr3_done:"Сборка простейшей электрической цепи и измерение силы тока и напряжения завершена!",
lr4_done:"Изучение последовательного соединения проводников завершена!",
lr5_done:"Изучение параллельного соединения проводников завершена!",
lr6_done:"Определение работы и мощности электрического тока завершена!",
lr7_done:"Изучение явления отражения света завершена!",
lab_done:"Практикум пройден!",
lab_master:"Лаборант 8 класса — все 7 ЛР сданы!"
};
const SIDEBARS = {
lr1:{title:"ЛР 1",rows:[["Цель","найти $t_{см}$ при смешивании горяч. и холод. воды"],["Формула","$t = (m_1 t_1 + m_2 t_2)/(m_1+m_2)$"],["$c_{воды}$","$4200$ Дж/(кг·К)"]]},
lr2:{title:"ЛР 2",rows:[["Цель","определить $c$ материала"],["Формула","$c = c_в m_в (t_{см}-t_х)/(m_т(t_{гор}-t_{см}))$"],["Баланс","$Q_{отд} = Q_{пол}$"]]},
lr3:{title:"ЛР 3",rows:[["Цель","собрать цепь, измерить $I$ и $U$"],["Амперметр","последов."],["Вольтметр","паралл."]]},
lr4:{title:"ЛР 4",rows:[["Цель","проверить $R = R_1+R_2$"],["$I$","одинаковый"],["$U_1+U_2 = U$","проверка"]]},
lr5:{title:"ЛР 5",rows:[["Цель","проверить $1/R = 1/R_1+1/R_2$"],["$U$","одинаковое"],["$I_1+I_2 = I$","проверка"]]},
lr6:{title:"ЛР 6",rows:[["Цель","найти $A$ и $P$"],["$P = UI$","Вт"],["$A = UIt$","Дж"]]},
lr7:{title:"ЛР 7",rows:[["Цель","проверить $\\alpha=\\beta$"],["Измерения","транспортиром"],["Идеально","точно равны"]]}
};
const TIPS=[
{sec:'lr1',html:"Простейший случай теплового баланса. Налили в калориметр $m_1$ горячей воды и $m_2$ холодной — нашли итоговую температуру и сверили с формулой."},
{sec:'lr2',html:"Образец нагревают до известной $t_{гор}$, бросают в калориметр с водой. По итоговой $t_{см}$ из теплового баланса находят $c$ образца."},
{sec:'lr3',html:"Собираем цепь: батарейка + лампа + амперметр (последов.) + вольтметр (паралл. лампе) + ключ. Замыкаем — снимаем показания."},
{sec:'lr4',html:"Два резистора в одной петле. Проверяем: ток одинаков везде; напряжения складываются; $R$ суммируется."},
{sec:'lr5',html:"Две ветви параллельно. Проверяем: $U$ на обеих равно; токи складываются; $1/R$ суммируется."},
{sec:'lr6',html:"Прибор работает $t$ секунд. Измеряем $U$ и $I$. Считаем $P$ и $A$. Удобно сверить с маркировкой на приборе."},
{sec:'lr7',html:"Луч лазера на зеркале. Транспортиром измеряем углы $\\alpha$ и $\\beta$ от нормали. Должно быть $\\alpha = \\beta$."}
];
const BUILDERS = {
lr1: ()=>{ build_lr1(); },
lr2: ()=>{ build_lr2(); },
lr3: ()=>{ build_lr3(); },
lr4: ()=>{ build_lr4(); },
lr5: ()=>{ build_lr5(); },
lr6: ()=>{ build_lr6(); },
lr7: ()=>{ build_lr7(); }
};
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
function loadProgress(){
try{
const s=localStorage.getItem(LS_PREFIX+'_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem(LS_PREFIX+'_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(LS_XP)||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem(LS_PREFIX+'_progress', JSON.stringify(STATE.progress));
localStorage.setItem(LS_PREFIX+'_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem(LS_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, LS_PREFIX+'-'+(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();
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);
}
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(LS_PREFIX+'_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(LS_PREFIX+'_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>';
}
/* === SVG-хелперы === */
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){
function lbl(id){ if(!id) return ''; const p=PARAS.find(x=>x.id===id); return p?p.num:id; }
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> '+lbl(prev)+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+lbl(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 : '?');
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>'
+' Я прочитал \u2014 '+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);
});
}
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(); }};
}
function buildStub(id, name, phase){
return '<div class="card" style="background:linear-gradient(135deg,var(--sec-acc-soft),var(--card));border:1.5px dashed var(--sec-acc)">'
+ '<div class="card-header"><div class="card-icon theory">'+ICONS.theory+'</div><div class="card-title">В разработке</div></div>'
+ '<div class="card-body"><p>Контент <b>'+name+'</b> будет реализован в <b>'+phase+'</b> по плану <code>PLAN_PHYSICS_8.md</code>.</p>'
+ '<p style="margin-top:8px;color:var(--muted);font-size:.9rem">Phase 0 \u2014 это каркас (skeleton). Все 4 интерактива, 3 теоретические карточки и тренажёр задач будут добавлены в волне.</p>'
+ '</div></div>';
}
/* ===== 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)+'\u2026':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(); });
}
/* ======================================================================
PHASE 6 — Лабораторный практикум: 7 ЛР
====================================================================== */
/* Общий шаблон для каждой ЛР: цель + оборудование + ход + интерактив + таблица + расчёт + отчёт */
function _labHeader(id, name, goal, equip, steps){
let h = '';
h += makeCard('theory', 'Цель работы', id+' · 1',
'<p>'+goal+'</p>'
+'<p><b>Оборудование:</b> '+equip+'</p>'
);
let stepsHtml = '<ol style="padding-left:20px;margin:6px 0">';
steps.forEach(s=>{ stepsHtml += '<li>'+s+'</li>'; });
stepsHtml += '</ol>';
h += makeCard('algo', 'Ход работы', id+' · 2', stepsHtml);
return h;
}
function _labResult(id, resultHtml, btnLabel){
return '<div class="wg" style="background:linear-gradient(135deg,var(--sec-acc-soft),var(--card));border:2px solid var(--sec-acc)">'
+'<div class="wg-header"><span class="wg-badge">ОТЧЁТ</span><div class="wg-title">Вывод по работе</div></div>'
+'<div id="'+id+'-result">'+resultHtml+'</div>'
+'<div class="actions" style="margin-top:10px"><button class="btn primary" id="'+id+'-submit">'+(btnLabel||'Сдать работу (+30 XP)')+'</button></div>'
+'<div class="feedback" id="'+id+'-fb"></div></div>';
}
function _labWireSubmit(id, ach){
const btn = document.getElementById(id+'-submit'); if(!btn) return;
btn.addEventListener('click', ()=>{
const fb = document.getElementById(id+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '&#10003; Работа сдана! +30 XP. Молодец, лаборант!';
btn.disabled = true; btn.style.opacity = 0.6;
addXp(30, id);
bumpProgress(id, 70);
if(ach && !STATE.achievements.has('lr_'+ach)) achievement(ach+'_done');
});
}
/* ======== ЛР 1 — Теплообмен при смешивании воды ======== */
function build_lr1(){
const box = document.getElementById('lr1-body');
let h = _labHeader('ЛР 1', 'Изучение явления теплообмена при смешивании воды разной температуры',
'Налить в один калориметр горячую воду $m_1$ при $t_1$ и в другой холодную воду $m_2$ при $t_2$. Слить вместе и измерить итоговую температуру $t_{см}$. Сверить с теоретическим расчётом по формуле $t = (m_1 t_1 + m_2 t_2)/(m_1+m_2)$.',
'два калориметра, термометр, мерный цилиндр, горячая и холодная вода',
[
'Налей в первый калориметр $m_1$ горячей воды, измерь $t_1$.',
'Налей во второй калориметр $m_2$ холодной воды, измерь $t_2$.',
'Слей содержимое в один калориметр.',
'Через минуту измерь итоговую температуру $t_{см}$.',
'Сверь с теоретической формулой.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальный опыт</div></div>'
+'<div class="wg-help">Меняй массы и температуры — увидь, как смешиваются жидкости и какая будет итоговая температура.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$m_1$, кг (горячая): <b id="lr1-m1v">1.0</b><input type="range" id="lr1-m1" min="0.1" max="3" step="0.1" value="1"></label>'
+'<label>$t_1$, &#176;C: <b id="lr1-t1v">80</b><input type="range" id="lr1-t1" min="40" max="100" step="5" value="80"></label>'
+'<label>$m_2$, кг (холодная): <b id="lr1-m2v">2.0</b><input type="range" id="lr1-m2" min="0.1" max="3" step="0.1" value="2"></label>'
+'<label>$t_2$, &#176;C: <b id="lr1-t2v">15</b><input type="range" id="lr1-t2" min="0" max="40" step="5" value="15"></label>'
+'</div>'
+'<svg id="lr1-sim" viewBox="0 0 460 200" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:10px"><span>Теоретически: $t_{теор}$ = <b id="lr1-tt">36.7</b> &#176;C</span><span>Измерено: $t_{изм}$ = <b id="lr1-tm">37.0</b> &#176;C</span><span>Относ. погрешность: <b id="lr1-err">0.8</b>%</span></div></div>';
h += _labResult('lr1', '<p>Заполняем таблицу и оформляем отчёт:</p>'
+'<table style="width:100%;border-collapse:collapse;font-size:.9rem;margin:8px 0"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px">Величина</th><th style="padding:6px;text-align:right">Значение</th></tr></thead><tbody>'
+'<tr><td style="padding:5px">$m_1, t_1$</td><td style="padding:5px;text-align:right"><b id="lr1-r1">1,0 кг, 80 &#176;C</b></td></tr>'
+'<tr><td style="padding:5px">$m_2, t_2$</td><td style="padding:5px;text-align:right"><b id="lr1-r2">2,0 кг, 15 &#176;C</b></td></tr>'
+'<tr><td style="padding:5px">$t_{теор}$</td><td style="padding:5px;text-align:right"><b id="lr1-r3">36,7 &#176;C</b></td></tr>'
+'<tr><td style="padding:5px">$t_{изм}$</td><td style="padding:5px;text-align:right"><b id="lr1-r4">37,0 &#176;C</b></td></tr>'
+'</tbody></table>'
+'<p><b>Вывод:</b> расчёт по формуле подтверждается опытом с погрешностью менее 2%. Это подтверждает закон сохранения теплоты при смешивании.</p>');
/* IV6 — Теплообмен — смешивание жидкостей (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Теплообмен — смешивание жидкостей</div></div>'
+'<div class="wg-help">Задавай начальные T₁ и T₂ скрубберами. Кнопка «Смешать» — итоговая T рассчитывается через тепловой баланс.</div>'
+'<div class="p8-sandbox" id="lr1-iv6-sandbox" style="height:260px"></div>'
+'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:8px"><div class="p8-scrubber"><span class="p8-scrubber-label">T₁ (0.5 кг)</span><input type="range" id="lr1-t1" min="0" max="100" step="1" value="80"><span class="p8-scrubber-value"><span id="lr1-t1-val">80</span><span class="p8-unit">°C</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">T₂ (1 кг)</span><input type="range" id="lr1-t2" min="0" max="100" step="1" value="20"><span class="p8-scrubber-value"><span id="lr1-t2-val">20</span><span class="p8-unit">°C</span></span></div></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="lr1-mix">Смешать</button><div class="p8-readout"><span class="p8-readout-label">T_итог</span><span class="p8-readout-value" id="lr1-tf">—</span><span class="p8-readout-unit">°C</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr1') + readButton('lr1');
renderMath(box); wireReadBtn('lr1');
_initLR1_iv6();
_lr1_sim(); _labWireSubmit('lr1', 'lr1');
}
function _initLR1_iv6(){
const sb = document.getElementById('lr1-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
let T1=80, T2=20, mixed=false, Tf=50;
function vessel(x, y, T, m){
const g = P8Helpers.svg.el('g', { transform: 'translate('+x+','+y+')' });
const ht = 40 + m*50;
g.appendChild(P8Helpers.svg.el('rect', { x:-32, y:-ht, width:64, height:ht, rx:5, fill:'rgba(255,255,255,.7)', stroke:'#0f172a', 'stroke-width':2 }));
g.appendChild(P8Helpers.svg.el('rect', { x:-29, y:-ht+3, width:58, height:ht-5, rx:3, fill: P8Helpers.thermal.tempColor(T/100) }));
g.appendChild(P8Helpers.svg.el('text', { x:0, y:18, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'T='+Math.round(T)+'°C' }));
return g;
}
function render(){
svg.innerHTML='';
if (!mixed){
svg.appendChild(vessel(160, 180, T1, 0.5));
svg.appendChild(vessel(400, 180, T2, 1));
svg.appendChild(P8Helpers.svg.el('text', { x:160, y:235, 'font-family':"'Inter',sans-serif", 'font-size':11, fill:'var(--p8-muted,#64748b)', 'text-anchor':'middle', text:'m₁=0.5 кг' }));
svg.appendChild(P8Helpers.svg.el('text', { x:400, y:235, 'font-family':"'Inter',sans-serif", 'font-size':11, fill:'var(--p8-muted,#64748b)', 'text-anchor':'middle', text:'m₂=1 кг' }));
} else {
svg.appendChild(vessel(280, 180, Tf, 1.5));
svg.appendChild(P8Helpers.svg.el('text', { x:280, y:70, 'font-family':"'Unbounded',sans-serif", 'font-size':18, 'font-weight':900, fill:'#10b981', 'text-anchor':'middle', text: 'T_итог = '+Math.round(Tf)+' °C' }));
}
}
document.getElementById('lr1-t1').oninput = ev => { T1 = +ev.target.value; document.getElementById('lr1-t1-val').textContent = T1; mixed = false; document.getElementById('lr1-tf').textContent='—'; render(); };
document.getElementById('lr1-t2').oninput = ev => { T2 = +ev.target.value; document.getElementById('lr1-t2-val').textContent = T2; mixed = false; document.getElementById('lr1-tf').textContent='—'; render(); };
document.getElementById('lr1-mix').onclick = () => {
Tf = (0.5*T1 + 1*T2)/(0.5+1);
mixed = true;
if (window.P8Anim) P8Anim.tween({ from: T1, to: Tf, duration: 1200, easing: 'cubicInOut', onUpdate: t => { Tf = t; render(); document.getElementById('lr1-tf').textContent = Math.round(t); } });
else { render(); document.getElementById('lr1-tf').textContent = Math.round(Tf); }
if (window.addXp) addXp(15, 'lr1-iv6');
};
render();
}
function _lr1_sim(){
const svg = document.getElementById('lr1-sim'); if(!svg) return;
function u(){
const m1 = +document.getElementById('lr1-m1').value;
const t1 = +document.getElementById('lr1-t1').value;
const m2 = +document.getElementById('lr1-m2').value;
const t2 = +document.getElementById('lr1-t2').value;
document.getElementById('lr1-m1v').textContent = m1.toFixed(1);
document.getElementById('lr1-t1v').textContent = t1;
document.getElementById('lr1-m2v').textContent = m2.toFixed(1);
document.getElementById('lr1-t2v').textContent = t2;
const tt = (m1*t1 + m2*t2) / (m1+m2);
/* виртуальный "измеренный" — с небольшим шумом */
const noise = (Math.sin(t1*0.7 + t2*0.3 + m1) * 0.4);
const tm = tt + noise;
const err = Math.abs((tm - tt)/tt)*100;
document.getElementById('lr1-tt').textContent = tt.toFixed(1);
document.getElementById('lr1-tm').textContent = tm.toFixed(1);
document.getElementById('lr1-err').textContent = err.toFixed(1);
/* report */
document.getElementById('lr1-r1').textContent = m1.toFixed(1)+' кг, '+t1+' °C';
document.getElementById('lr1-r2').textContent = m2.toFixed(1)+' кг, '+t2+' °C';
document.getElementById('lr1-r3').textContent = tt.toFixed(1)+' °C';
document.getElementById('lr1-r4').textContent = tm.toFixed(1)+' °C';
/* SVG */
let s = '';
s += window.PHYS.calorimeter(60, 60, 70, 100, m1/3, window.PHYS.tempColor(t1, 0, 100));
s += '<text x="95" y="180" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#dc2626">'+t1+' °C</text>';
s += window.PHYS.calorimeter(180, 60, 70, 100, m2/3, window.PHYS.tempColor(t2, 0, 100));
s += '<text x="215" y="180" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#2563eb">'+t2+' °C</text>';
/* стрелка */
s += window.PHYS.drawArrow(270, 110, 320, 110, '#10b981', 2.5, 11);
/* объединенный */
s += window.PHYS.calorimeter(340, 60, 80, 100, Math.min(1, (m1+m2)/3.5), window.PHYS.tempColor(tt, 0, 100));
s += '<text x="380" y="180" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="800" fill="#7c3aed">'+tt.toFixed(0)+' °C</text>';
svg.innerHTML = s;
}
['lr1-m1','lr1-t1','lr1-m2','lr1-t2'].forEach(id => document.getElementById(id).addEventListener('input', u));
u();
}
/* ======== ЛР 2 — Определение c твёрдого тела ======== */
function build_lr2(){
const box = document.getElementById('lr2-body');
let h = _labHeader('ЛР 2', 'Определение удельной теплоёмкости твёрдого тела',
'Нагретое до известной температуры $t_{гор}$ твёрдое тело массой $m_т$ опускают в калориметр с водой $m_в$ при $t_х$. По итоговой температуре $t_{см}$ из теплового баланса находят $c_т = c_в m_в (t_{см} - t_х) / (m_т (t_{гор} - t_{см}))$.',
'образец твёрдого тела, калориметр, термометр, кипящая вода, весы',
[
'Взвесь образец, получив $m_т$.',
'Нагрей образец в кипящей воде до $t_{гор} = 100$ &#176;C.',
'Налей в калориметр $m_в$ воды и измерь $t_х$.',
'Быстро опусти образец в калориметр.',
'Через минуту измерь $t_{см}$ и вычисли $c_т$.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальный опыт</div></div>'
+'<div class="wg-help">Выбери тело и проведи измерение. Расчёт идёт автоматически.</div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>Образец: <select id="lr2-mat" class="tinp" style="width:auto;padding:6px 10px"><option value="380">медь</option><option value="460">железо</option><option value="920">алюминий</option><option value="130">свинец</option></select></label>'
+'<label>$m_т$, г: <b id="lr2-mv">200</b><input type="range" id="lr2-m" min="50" max="500" step="10" value="200"></label>'
+'<label>$m_в$, г: <b id="lr2-mwv">100</b><input type="range" id="lr2-mw" min="50" max="500" step="10" value="100"></label>'
+'</div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span>$t_{гор}$ = 100 &#176;C, $t_х$ = 18 &#176;C</span>'
+'<span>$t_{см}$ измерено: <b id="lr2-ts">22.5</b> &#176;C</span>'
+'<span>$c_т$ расчёт: <b id="lr2-cv">380</b> Дж/(кг·К)</span>'
+'<span>$c$ табл.: <b id="lr2-ct">380</b>, погрешность <b id="lr2-er">0.5</b>%</span></div></div>';
h += _labResult('lr2', '<p>Таблица измерений и расчёт:</p>'
+'<table style="width:100%;border-collapse:collapse;font-size:.9rem;margin:8px 0"><tr><td style="padding:5px">$m_т$, $m_в$</td><td style="padding:5px;text-align:right"><b id="lr2-r1">0,2 кг, 0,1 кг</b></td></tr>'
+'<tr><td style="padding:5px">$t_{гор}, t_х, t_{см}$</td><td style="padding:5px;text-align:right"><b id="lr2-r2">100, 18, 22.5</b></td></tr>'
+'<tr><td style="padding:5px">$c$ найдено</td><td style="padding:5px;text-align:right"><b id="lr2-r3">380</b> Дж/(кг·К)</td></tr></table>'
+'<p><b>Вывод:</b> найденная $c$ совпадает с табличной с точностью до экспериментальной погрешности.</p>');
/* IV6 — Измерение удельной теплоёмкости (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Измерение удельной теплоёмкости</div></div>'
+'<div class="wg-help">Нагреватель мощности P подаёт Q=Pt в массу m. Из ΔT находим $c = Q/(m\\Delta T)$.</div>'
+'<div class="p8-sandbox" id="lr2-iv6-sandbox" style="height:260px"></div>'
+'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px"><div class="p8-scrubber"><span class="p8-scrubber-label">P</span><input type="range" id="lr2-p" min="50" max="1000" step="10" value="500"><span class="p8-scrubber-value"><span id="lr2-p-val">500</span><span class="p8-unit">Вт</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">m</span><input type="range" id="lr2-m" min="0.1" max="2" step="0.1" value="0.5"><span class="p8-scrubber-value"><span id="lr2-m-val">0.5</span><span class="p8-unit">кг</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">t</span><input type="range" id="lr2-t" min="10" max="600" step="5" value="120"><span class="p8-scrubber-value"><span id="lr2-t-val">120</span><span class="p8-unit">с</span></span></div></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><div class="p8-readout"><span class="p8-readout-label">Q</span><span class="p8-readout-value" id="lr2-q">60</span><span class="p8-readout-unit">кДж</span></div><div class="p8-readout"><span class="p8-readout-label">ΔT</span><span class="p8-readout-value" id="lr2-dt">29</span><span class="p8-readout-unit">К</span></div><div class="p8-readout"><span class="p8-readout-label">c</span><span class="p8-readout-value" id="lr2-c">4200</span><span class="p8-readout-unit">Дж/(кг·К)</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr2') + readButton('lr2');
renderMath(box); wireReadBtn('lr2');
_initLR2_iv6();
_lr2_sim(); _labWireSubmit('lr2', 'lr2');
}
function _initLR2_iv6(){
const sb = document.getElementById('lr2-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
let P=500, m=0.5, t=120;
const c_const = 4200; /* предположим вода */
function render(){
svg.innerHTML='';
const Q = P*t;
const dT = Q/(c_const*m);
/* Vessel */
const ht = 50+m*60;
svg.appendChild(P8Helpers.svg.el('rect', { x: 200, y: 200-ht, width: 160, height: ht, rx: 5, fill: P8Helpers.thermal.tempColor(Math.min(1, (20+dT)/120)), stroke: '#0f172a', 'stroke-width': 2, opacity: 0.85 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 200-ht+22, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':700, fill:'#fff', 'text-anchor':'middle', text: 'm='+m+' кг воды' }));
/* Heater */
svg.appendChild(P8Helpers.svg.el('rect', { x: 240, y: 205, width: 80, height: 14, fill: '#dc2626', rx: 3 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 235, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: 'нагреватель P='+P+' Вт' }));
/* Thermometer */
svg.appendChild(P8Helpers.thermal.thermometerSVG(120, 60, 110, Math.min(1, (20+dT)/120)));
svg.appendChild(P8Helpers.svg.el('text', { x: 120, y: 50, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':800, fill:'#0f172a', 'text-anchor':'middle', text: 'T='+Math.round(20+dT)+'°C' }));
/* Updates */
document.getElementById('lr2-q').textContent = (Q/1000).toFixed(1);
document.getElementById('lr2-dt').textContent = Math.round(dT);
document.getElementById('lr2-c').textContent = c_const;
}
document.getElementById('lr2-p').oninput = ev => { P = +ev.target.value; document.getElementById('lr2-p-val').textContent = P; render(); };
document.getElementById('lr2-m').oninput = ev => { m = +ev.target.value; document.getElementById('lr2-m-val').textContent = m.toFixed(1); render(); };
document.getElementById('lr2-t').oninput = ev => { t = +ev.target.value; document.getElementById('lr2-t-val').textContent = t; render(); };
render();
}
function _lr2_sim(){
function u(){
const c = +document.getElementById('lr2-mat').value;
const m = +document.getElementById('lr2-m').value / 1000; /* г → кг */
const mw = +document.getElementById('lr2-mw').value / 1000;
document.getElementById('lr2-mv').textContent = (m*1000).toFixed(0);
document.getElementById('lr2-mwv').textContent = (mw*1000).toFixed(0);
const tHot = 100, tCold = 18;
/* по балансу: c_воды*mw*(t_см - 18) = c*m*(100 - t_см)
(4200*mw + c*m)*t_см = 4200*mw*18 + c*m*100
t_см = (4200*mw*18 + c*m*100) / (4200*mw + c*m) */
const tMix = (4200*mw*18 + c*m*100) / (4200*mw + c*m);
/* "обратный" расчёт c (как это делают в ЛР) */
const cCalc = 4200*mw*(tMix - tCold) / (m*(tHot - tMix));
const err = Math.abs(cCalc - c)/c * 100;
document.getElementById('lr2-ts').textContent = tMix.toFixed(1);
document.getElementById('lr2-cv').textContent = cCalc.toFixed(0);
document.getElementById('lr2-ct').textContent = c;
document.getElementById('lr2-er').textContent = err.toFixed(1);
document.getElementById('lr2-r1').textContent = m.toFixed(2)+' кг, '+mw.toFixed(2)+' кг';
document.getElementById('lr2-r2').textContent = '100, 18, '+tMix.toFixed(1);
document.getElementById('lr2-r3').textContent = cCalc.toFixed(0);
}
['lr2-mat','lr2-m','lr2-mw'].forEach(id => document.getElementById(id).addEventListener('input', u));
document.getElementById('lr2-mat').addEventListener('change', u);
u();
}
/* ======== ЛР 3 — Сборка простейшей цепи ======== */
function build_lr3(){
const box = document.getElementById('lr3-body');
let h = _labHeader('ЛР 3', 'Сборка простейшей электрической цепи и измерение силы тока и напряжения',
'Собрать цепь из батарейки, лампы, ключа, амперметра и вольтметра. Замкнуть ключ, снять показания приборов.',
'батарея 4,5 В, лампа на 3,5 В, амперметр (0-1 А), вольтметр (0-5 В), провода, ключ',
[
'Собери цепь по схеме: батарея → ключ → амперметр → лампа → батарея.',
'Подключи вольтметр параллельно лампе.',
'Замкни ключ.',
'Запиши показания амперметра $I$ и вольтметра $U$.',
'Вычисли $R = U/I$.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальная цепь</div></div>'
+'<div class="wg-help">Нажми «Замкнуть» — снимешь показания.</div>'
+'<svg id="lr3-sim" viewBox="0 0 460 220" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="actions" style="margin-top:10px"><button class="btn primary" id="lr3-key">Замкнуть ключ</button></div>'
+'<div class="score-display" style="margin-top:10px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span>Амперметр: $I$ = <b id="lr3-a">0</b> А</span>'
+'<span>Вольтметр: $U$ = <b id="lr3-v">0</b> В</span>'
+'<span>Расчёт: $R = U/I$ = <b id="lr3-r">—</b> Ом</span></div></div>';
h += _labResult('lr3', '<p>В цепи лампа сопротивлением $\\sim 8$ Ом, питание от 4,5 В. Ожидаемый ток $\\sim 0{,}5$ А.</p>'
+'<p><b>Вывод:</b> амперметр включается последовательно с потребителем (через него идёт измеряемый ток); вольтметр — параллельно (между точками, где меряется $U$).</p>');
/* IV6 — Сборка простейшей цепи (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Сборка простейшей цепи</div></div>'
+'<div class="wg-help">Цепь: батарея → амперметр → лампа → вольтметр (параллельно). Двигай U — показания приборов обновляются.</div>'
+'<div class="p8-sandbox" id="lr3-iv6-sandbox" style="height:260px"></div>'
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">U батареи</span><input type="range" id="lr3-u" min="1" max="12" step="0.1" value="6"><span class="p8-scrubber-value"><span id="lr3-u-val">6.0</span><span class="p8-unit">В</span></span></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><div class="p8-readout"><span class="p8-readout-label">A показывает</span><span class="p8-readout-value" id="lr3-a">0.50</span><span class="p8-readout-unit">А</span></div><div class="p8-readout"><span class="p8-readout-label">V показывает</span><span class="p8-readout-value" id="lr3-v">6.0</span><span class="p8-readout-unit">В</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr3') + readButton('lr3');
renderMath(box); wireReadBtn('lr3');
_initLR3_iv6();
_lr3_sim(); _labWireSubmit('lr3', 'lr3');
}
function _initLR3_iv6(){
const sb = document.getElementById('lr3-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
let U=6;
const R=12;
function render(){
svg.innerHTML='';
const I = U/R;
/* Battery left */
svg.appendChild(P8Helpers.em.circuitComponent('battery', 80, 120, 'h', U.toFixed(1)+' В'));
/* Ammeter */
svg.appendChild(P8Helpers.em.circuitComponent('ammeter', 220, 120, 'h'));
svg.appendChild(P8Helpers.svg.el('text', { x: 220, y: 100, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: I.toFixed(2)+' А' }));
/* Lamp */
const lampG = P8Helpers.svg.el('g', { transform: 'translate(380, 120)' });
const br = Math.min(1, I/1.2);
lampG.appendChild(P8Helpers.svg.el('circle', { cx: 0, cy: 0, r: 26, fill: '#fef3c7', opacity: br*0.6+0.1 }));
lampG.appendChild(P8Helpers.svg.el('circle', { cx: 0, cy: 0, r: 16, fill: '#fef3c7', stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(lampG);
/* Voltmeter (parallel above lamp) */
svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 90, x2: 380, y2: 50, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 50, x2: 480, y2: 50, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.em.circuitComponent('voltmeter', 480, 50, 'h'));
svg.appendChild(P8Helpers.svg.el('line', { x1: 480, y1: 50, x2: 480, y2: 120, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 480, y: 30, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':800, fill:'#2563eb', 'text-anchor':'middle', text: U.toFixed(1)+' В' }));
/* Wires */
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 120, x2: 190, y2: 120, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 250, y1: 120, x2: 354, y2: 120, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 136, x2: 510, y2: 136, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 510, y1: 136, x2: 510, y2: 190, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 120, x2: 50, y2: 190, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 190, x2: 510, y2: 190, stroke: '#0f172a', 'stroke-width': 2 }));
/* Updates */
document.getElementById('lr3-a').textContent = I.toFixed(2);
document.getElementById('lr3-v').textContent = U.toFixed(1);
}
document.getElementById('lr3-u').oninput = ev => { U = +ev.target.value; document.getElementById('lr3-u-val').textContent = U.toFixed(1); render(); };
render();
}
function _lr3_sim(){
const svg = document.getElementById('lr3-sim'); if(!svg) return;
let closed = false;
function draw(){
let s = '';
s += window.PHYS.batteryEMF(60, 180, '4,5 В', 'h');
s += window.PHYS.wire(60, 162, 60, 60);
s += window.PHYS.wire(60, 60, 180, 60);
s += window.PHYS.ammeterSymbol(200, 60, 16);
s += window.PHYS.wire(216, 60, 280, 60);
s += window.PHYS.lightbulbSymbol(300, 60, 18);
if(closed){ s += '<circle cx="300" cy="60" r="26" fill="#fde047" opacity="0.5"/>'; s += '<circle cx="300" cy="60" r="38" fill="#fbbf24" opacity="0.2"/>'; }
s += window.PHYS.wire(320, 60, 400, 60);
s += window.PHYS.wire(400, 60, 400, 180);
s += window.PHYS.wire(60, 198, 60, 200);
s += window.PHYS.wire(60, 200, 140, 200);
s += window.PHYS.wire(160, 200, 400, 200);
/* ключ */
s += '<circle cx="140" cy="200" r="3" fill="#0f172a"/>'+'<circle cx="160" cy="200" r="3" fill="#0f172a"/>';
if(closed) s += '<line x1="140" y1="200" x2="160" y2="200" stroke="#dc2626" stroke-width="3"/>';
else s += '<line x1="140" y1="200" x2="158" y2="190" stroke="#0f172a" stroke-width="3"/>';
/* вольтметр параллельно лампе */
s += window.PHYS.wire(300, 78, 300, 110);
s += window.PHYS.wire(280, 110, 320, 110);
s += window.PHYS.voltmeterSymbol(300, 130, 16);
s += window.PHYS.wire(280, 130, 280, 78);
s += window.PHYS.wire(320, 130, 320, 78);
/* подписи */
s += '<text x="200" y="42" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#475569">амперметр (послед.)</text>';
s += '<text x="300" y="160" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#475569">вольтметр (паралл.)</text>';
svg.innerHTML = s;
if(closed){
document.getElementById('lr3-a').textContent = '0,52';
document.getElementById('lr3-v').textContent = '4,2';
document.getElementById('lr3-r').textContent = (4.2/0.52).toFixed(1);
}
}
document.getElementById('lr3-key').addEventListener('click', ()=>{
closed = !closed;
document.getElementById('lr3-key').textContent = closed ? 'Разомкнуть' : 'Замкнуть ключ';
if(!closed){ document.getElementById('lr3-a').textContent = '0'; document.getElementById('lr3-v').textContent = '0'; document.getElementById('lr3-r').textContent = '—'; }
draw();
});
draw();
}
/* ======== ЛР 4 — Последовательное соединение ======== */
function build_lr4(){
const box = document.getElementById('lr4-body');
let h = _labHeader('ЛР 4', 'Изучение последовательного соединения проводников',
'Собрать цепь с двумя резисторами последовательно. Измерить ток $I$, напряжение на каждом ($U_1$, $U_2$) и общее $U$. Проверить: $I = I_1 = I_2$, $U = U_1 + U_2$, $R = R_1 + R_2$.',
'батарея, 2 резистора, амперметр, вольтметр, провода',
[
'Собери цепь: батарея → ключ → $R_1$ → $R_2$ → батарея.',
'Замкни ключ, измерь общий ток $I$.',
'Перемещай вольтметр между точками — измерь $U_1$, $U_2$, $U$.',
'Проверь правила последовательного соединения.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальные измерения</div></div>'
+'<div class="wg-help">Меняй $R_1$ и $R_2$ — посмотри показания приборов.</div>'
+'<div class="sliders" style="margin-bottom:10px"><label>$R_1$, Ом: <b id="lr4-r1v">4</b><input type="range" id="lr4-r1" min="2" max="20" step="1" value="4"></label>'
+'<label>$R_2$, Ом: <b id="lr4-r2v">8</b><input type="range" id="lr4-r2" min="2" max="20" step="1" value="8"></label>'
+'<label>$U$, В: <b id="lr4-uv">12</b><input type="range" id="lr4-u" min="3" max="24" step="1" value="12"></label></div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span>Общий ток $I$ = <b id="lr4-i">1</b> А (одинаков везде &#10003;)</span>'
+'<span>$U_1$ = <b id="lr4-u1">4</b> В, $U_2$ = <b id="lr4-u2">8</b> В</span>'
+'<span>$U_1+U_2$ = <b id="lr4-us">12</b> В = $U$ &#10003;</span>'
+'<span>$R = R_1+R_2$ = <b id="lr4-rs">12</b> Ом, $U/I$ = <b id="lr4-rt">12</b> Ом &#10003;</span></div></div>';
h += _labResult('lr4', '<p>Все 3 правила послед. соединения подтверждены опытом:</p>'
+'<ul style="padding-left:20px"><li>$I$ одинаков везде;</li><li>$U = U_1 + U_2$;</li><li>$R = R_1 + R_2$.</li></ul>'
+'<p><b>Вывод:</b> закон Ома и правила последовательного соединения выполняются.</p>');
/* IV6 — Последовательное соединение проводников (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Последовательное соединение проводников</div></div>'
+'<div class="wg-help">Двигай R₁, R₂. Проверь: ток одинаков везде; напряжения складываются U = U₁ + U₂.</div>'
+'<div class="p8-sandbox" id="lr4-iv6-sandbox" style="height:260px"></div>'
+'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:8px"><div class="p8-scrubber"><span class="p8-scrubber-label">R₁</span><input type="range" id="lr4-r1" min="1" max="50" step="1" value="10"><span class="p8-scrubber-value"><span id="lr4-r1-val">10</span><span class="p8-unit">Ом</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">R₂</span><input type="range" id="lr4-r2" min="1" max="50" step="1" value="20"><span class="p8-scrubber-value"><span id="lr4-r2-val">20</span><span class="p8-unit">Ом</span></span></div></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><div class="p8-readout"><span class="p8-readout-label">U₁</span><span class="p8-readout-value" id="lr4-u1">4</span><span class="p8-readout-unit">В</span></div><div class="p8-readout"><span class="p8-readout-label">U₂</span><span class="p8-readout-value" id="lr4-u2">8</span><span class="p8-readout-unit">В</span></div><div class="p8-readout"><span class="p8-readout-label">I</span><span class="p8-readout-value" id="lr4-i">0.4</span><span class="p8-readout-unit">А</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr4') + readButton('lr4');
renderMath(box); wireReadBtn('lr4');
_initLR4_iv6();
_lr4_sim(); _labWireSubmit('lr4', 'lr4');
}
function _initLR4_iv6(){
const sb = document.getElementById('lr4-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
const U = 12;
let R1=10, R2=20;
function render(){
svg.innerHTML='';
const R = R1+R2, I = U/R, U1 = I*R1, U2 = I*R2;
/* Battery */
svg.appendChild(P8Helpers.em.circuitComponent('battery', 80, 130, 'h', U+' В'));
/* R1 */
svg.appendChild(P8Helpers.em.circuitComponent('resistor', 230, 130, 'h', R1+' Ом'));
svg.appendChild(P8Helpers.svg.el('text', { x: 230, y: 110, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'U₁='+U1.toFixed(1)+' В' }));
/* R2 */
svg.appendChild(P8Helpers.em.circuitComponent('resistor', 400, 130, 'h', R2+' Ом'));
svg.appendChild(P8Helpers.svg.el('text', { x: 400, y: 110, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'U₂='+U2.toFixed(1)+' В' }));
/* Wires */
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 130, x2: 200, y2: 130, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 260, y1: 130, x2: 370, y2: 130, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 430, y1: 130, x2: 510, y2: 130, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 510, y1: 130, x2: 510, y2: 200, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 130, x2: 50, y2: 200, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 200, x2: 510, y2: 200, stroke: '#0f172a', 'stroke-width': 2 }));
/* I label */
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 220, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'#10b981', 'text-anchor':'middle', text: 'I = '+I.toFixed(3)+' А (одинаков везде)' }));
/* Updates */
document.getElementById('lr4-u1').textContent = U1.toFixed(1);
document.getElementById('lr4-u2').textContent = U2.toFixed(1);
document.getElementById('lr4-i').textContent = I.toFixed(3);
}
document.getElementById('lr4-r1').oninput = ev => { R1 = +ev.target.value; document.getElementById('lr4-r1-val').textContent = R1; render(); };
document.getElementById('lr4-r2').oninput = ev => { R2 = +ev.target.value; document.getElementById('lr4-r2-val').textContent = R2; render(); };
render();
}
function _lr4_sim(){
function u(){
const R1 = +document.getElementById('lr4-r1').value;
const R2 = +document.getElementById('lr4-r2').value;
const U = +document.getElementById('lr4-u').value;
document.getElementById('lr4-r1v').textContent = R1;
document.getElementById('lr4-r2v').textContent = R2;
document.getElementById('lr4-uv').textContent = U;
const R = R1+R2; const I = U/R;
const U1 = I*R1; const U2 = I*R2;
document.getElementById('lr4-i').textContent = I.toFixed(2);
document.getElementById('lr4-u1').textContent = U1.toFixed(2);
document.getElementById('lr4-u2').textContent = U2.toFixed(2);
document.getElementById('lr4-us').textContent = (U1+U2).toFixed(2);
document.getElementById('lr4-rs').textContent = R;
document.getElementById('lr4-rt').textContent = (U/I).toFixed(1);
}
['lr4-r1','lr4-r2','lr4-u'].forEach(id => document.getElementById(id).addEventListener('input', u));
u();
}
/* ======== ЛР 5 — Параллельное соединение ======== */
function build_lr5(){
const box = document.getElementById('lr5-body');
let h = _labHeader('ЛР 5', 'Изучение параллельного соединения проводников',
'Собрать цепь с двумя резисторами параллельно. Измерить общее $U$, токи в ветвях $I_1$, $I_2$ и общий $I$. Проверить: $U = U_1 = U_2$, $I = I_1 + I_2$, $1/R = 1/R_1 + 1/R_2$.',
'батарея, 2 резистора, 3 амперметра, вольтметр, провода',
[
'Соедини $R_1$ и $R_2$ параллельно.',
'В каждую ветвь подключи амперметр, общий — на входе.',
'Измерь $I_1$, $I_2$, $I$.',
'Сверь по правилам.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальные измерения</div></div>'
+'<div class="sliders" style="margin-bottom:10px"><label>$R_1$, Ом: <b id="lr5-r1v">6</b><input type="range" id="lr5-r1" min="2" max="20" step="1" value="6"></label>'
+'<label>$R_2$, Ом: <b id="lr5-r2v">12</b><input type="range" id="lr5-r2" min="2" max="20" step="1" value="12"></label>'
+'<label>$U$, В: <b id="lr5-uv">12</b><input type="range" id="lr5-u" min="3" max="24" step="1" value="12"></label></div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span>$U_1 = U_2 = U$ = <b id="lr5-u1">12</b> В &#10003;</span>'
+'<span>$I_1$ = <b id="lr5-i1">2</b> А, $I_2$ = <b id="lr5-i2">1</b> А</span>'
+'<span>$I_1+I_2$ = <b id="lr5-is">3</b> А = $I$ &#10003;</span>'
+'<span>$R_{общ}$ по формуле = <b id="lr5-r">4</b> Ом, $U/I$ = <b id="lr5-rt">4</b> Ом &#10003;</span></div></div>';
h += _labResult('lr5', '<p>Все 3 правила параллельного соединения подтверждены:</p>'
+'<ul style="padding-left:20px"><li>$U$ одинаково на обеих ветвях;</li><li>$I = I_1 + I_2$;</li><li>$1/R = 1/R_1 + 1/R_2$.</li></ul>'
+'<p><b>Вывод:</b> общее $R$ <b>меньше</b> любого из $R_1$, $R_2$, что соответствует теории.</p>');
/* IV6 — Параллельное соединение проводников (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Параллельное соединение проводников</div></div>'
+'<div class="wg-help">Двигай R₁, R₂. Проверь: напряжение одинаково на обоих, токи складываются I = I₁ + I₂.</div>'
+'<div class="p8-sandbox" id="lr5-iv6-sandbox" style="height:260px"></div>'
+'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:8px"><div class="p8-scrubber"><span class="p8-scrubber-label">R₁</span><input type="range" id="lr5-r1" min="1" max="50" step="1" value="20"><span class="p8-scrubber-value"><span id="lr5-r1-val">20</span><span class="p8-unit">Ом</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">R₂</span><input type="range" id="lr5-r2" min="1" max="50" step="1" value="30"><span class="p8-scrubber-value"><span id="lr5-r2-val">30</span><span class="p8-unit">Ом</span></span></div></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><div class="p8-readout"><span class="p8-readout-label">I₁</span><span class="p8-readout-value" id="lr5-i1">0.6</span><span class="p8-readout-unit">А</span></div><div class="p8-readout"><span class="p8-readout-label">I₂</span><span class="p8-readout-value" id="lr5-i2">0.4</span><span class="p8-readout-unit">А</span></div><div class="p8-readout"><span class="p8-readout-label">I</span><span class="p8-readout-value" id="lr5-i">1.0</span><span class="p8-readout-unit">А</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr5') + readButton('lr5');
renderMath(box); wireReadBtn('lr5');
_initLR5_iv6();
_lr5_sim(); _labWireSubmit('lr5', 'lr5');
}
function _initLR5_iv6(){
const sb = document.getElementById('lr5-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
const U = 12;
let R1=20, R2=30;
function render(){
svg.innerHTML='';
const I1=U/R1, I2=U/R2, I=I1+I2;
svg.appendChild(P8Helpers.em.circuitComponent('battery', 80, 130, 'h', U+' В'));
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 130, x2: 200, y2: 130, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 200, y1: 80, x2: 200, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 80, x2: 380, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 200, y1: 80, x2: 290, y2: 80, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 320, y1: 80, x2: 380, y2: 80, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 200, y1: 180, x2: 290, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 320, y1: 180, x2: 380, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.em.circuitComponent('resistor', 305, 80, 'h', R1+' Ом'));
svg.appendChild(P8Helpers.em.circuitComponent('resistor', 305, 180, 'h', R2+' Ом'));
svg.appendChild(P8Helpers.svg.el('text', { x: 290, y: 68, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'I₁='+I1.toFixed(2)+' А' }));
svg.appendChild(P8Helpers.svg.el('text', { x: 290, y: 200, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'I₂='+I2.toFixed(2)+' А' }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 130, x2: 510, y2: 130, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 510, y1: 130, x2: 510, y2: 220, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 130, x2: 50, y2: 220, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 220, x2: 510, y2: 220, stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 150, y: 250, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'#10b981', 'text-anchor':'middle', text: 'I='+I.toFixed(2)+' А' }));
document.getElementById('lr5-i1').textContent = I1.toFixed(2);
document.getElementById('lr5-i2').textContent = I2.toFixed(2);
document.getElementById('lr5-i').textContent = I.toFixed(2);
}
document.getElementById('lr5-r1').oninput = ev => { R1 = +ev.target.value; document.getElementById('lr5-r1-val').textContent = R1; render(); };
document.getElementById('lr5-r2').oninput = ev => { R2 = +ev.target.value; document.getElementById('lr5-r2-val').textContent = R2; render(); };
render();
}
function _lr5_sim(){
function u(){
const R1 = +document.getElementById('lr5-r1').value;
const R2 = +document.getElementById('lr5-r2').value;
const U = +document.getElementById('lr5-u').value;
document.getElementById('lr5-r1v').textContent = R1;
document.getElementById('lr5-r2v').textContent = R2;
document.getElementById('lr5-uv').textContent = U;
const I1 = U/R1, I2 = U/R2;
const R = (R1*R2)/(R1+R2);
document.getElementById('lr5-u1').textContent = U;
document.getElementById('lr5-i1').textContent = I1.toFixed(2);
document.getElementById('lr5-i2').textContent = I2.toFixed(2);
document.getElementById('lr5-is').textContent = (I1+I2).toFixed(2);
document.getElementById('lr5-r').textContent = R.toFixed(2);
document.getElementById('lr5-rt').textContent = (U/(I1+I2)).toFixed(2);
}
['lr5-r1','lr5-r2','lr5-u'].forEach(id => document.getElementById(id).addEventListener('input', u));
u();
}
/* ======== ЛР 6 — Работа и мощность тока ======== */
function build_lr6(){
const box = document.getElementById('lr6-body');
let h = _labHeader('ЛР 6', 'Определение работы и мощности электрического тока',
'Собрать цепь, измерить $U$ и $I$ прибора, засечь время $t$. Вычислить $P = UI$ и $A = UIt$. Сверить $P$ с маркировкой на приборе.',
'батарея, лампа с известной маркировкой, амперметр, вольтметр, секундомер',
[
'Собери цепь: батарея, лампа, амперметр, вольтметр.',
'Замкни ключ, запиши $U$ и $I$.',
'Засеки время работы $t$.',
'Вычисли $P = UI$ и $A = UIt$.',
'Сверь $P$ с указанной на приборе.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальный замер</div></div>'
+'<div class="sliders" style="margin-bottom:10px">'
+'<label>$U$, В: <b id="lr6-uv">4.2</b><input type="range" id="lr6-u" min="1" max="12" step="0.1" value="4.2"></label>'
+'<label>$I$, А: <b id="lr6-iv">0.5</b><input type="range" id="lr6-i" min="0.01" max="2" step="0.01" value="0.5"></label>'
+'<label>$t$, мин: <b id="lr6-tv">5</b><input type="range" id="lr6-t" min="1" max="30" step="1" value="5"></label></div>'
+'<div class="score-display" style="margin-top:8px;flex-direction:column;align-items:flex-start;gap:3px">'
+'<span>Мощность $P = UI$ = <b id="lr6-p">2.1</b> Вт</span>'
+'<span>Работа $A = UIt$ = <b id="lr6-a">630</b> Дж</span>'
+'<span style="font-size:.84rem;color:var(--muted)">за $t$ секунд работы прибор потратил $A$ Дж энергии.</span></div></div>';
h += _labResult('lr6', '<p>Прямое применение формул $P = UI$ и $A = Pt$. Это позволяет рассчитать энергопотребление любого прибора.</p>'
+'<p><b>Вывод:</b> измеренная $P$ совпадает с маркировкой на лампе; расчётная $A$ соответствует количеству выделившегося тепла и света.</p>');
/* IV6 — Работа и мощность тока (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Работа и мощность тока</div></div>'
+'<div class="wg-help">Задавай U, I и время t — рассчитаются P=UI, A=Pt. Лампа светится ярче с ростом P.</div>'
+'<div class="p8-sandbox" id="lr6-iv6-sandbox" style="height:260px"></div>'
+'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px"><div class="p8-scrubber"><span class="p8-scrubber-label">U</span><input type="range" id="lr6-u" min="1" max="220" step="1" value="220"><span class="p8-scrubber-value"><span id="lr6-u-val">220</span><span class="p8-unit">В</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">I</span><input type="range" id="lr6-i" min="0.01" max="5" step="0.01" value="0.5"><span class="p8-scrubber-value"><span id="lr6-i-val">0.50</span><span class="p8-unit">А</span></span></div><div class="p8-scrubber"><span class="p8-scrubber-label">t</span><input type="range" id="lr6-t" min="1" max="3600" step="1" value="60"><span class="p8-scrubber-value"><span id="lr6-t-val">60</span><span class="p8-unit">с</span></span></div></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><div class="p8-readout"><span class="p8-readout-label">P</span><span class="p8-readout-value" id="lr6-p">110</span><span class="p8-readout-unit">Вт</span></div><div class="p8-readout"><span class="p8-readout-label">A</span><span class="p8-readout-value" id="lr6-a">6.6</span><span class="p8-readout-unit">кДж</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr6') + readButton('lr6');
renderMath(box); wireReadBtn('lr6');
_initLR6_iv6();
_lr6_sim(); _labWireSubmit('lr6', 'lr6');
}
function _initLR6_iv6(){
const sb = document.getElementById('lr6-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
let U=220, I=0.5, t=60;
function render(){
svg.innerHTML='';
const P = U*I, A = P*t;
const br = Math.min(1, P/200);
/* Lamp */
svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 110, r: 55, fill: '#fef3c7', opacity: br*0.5+0.15 }));
svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 110, r: 35, fill: '#fde047', stroke: '#0f172a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 113, 'font-family':"'Unbounded',sans-serif", 'font-size':18, 'font-weight':900, fill:'#0f172a', 'text-anchor':'middle', text: P.toFixed(0)+' Вт' }));
if (br > 0.5) {
for (let i = 0; i < 8; i++) {
const a = i*Math.PI/4;
svg.appendChild(P8Helpers.svg.el('line', { x1: 280+45*Math.cos(a), y1: 110+45*Math.sin(a), x2: 280+68*Math.cos(a), y2: 110+68*Math.sin(a), stroke: '#facc15', 'stroke-width': 3 }));
}
}
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 220, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#0f172a', 'text-anchor':'middle', text: 'P = UI = '+P.toFixed(1)+' Вт, A = Pt = '+(A/1000).toFixed(1)+' кДж' }));
document.getElementById('lr6-p').textContent = P.toFixed(1);
document.getElementById('lr6-a').textContent = (A/1000).toFixed(2);
}
document.getElementById('lr6-u').oninput = ev => { U = +ev.target.value; document.getElementById('lr6-u-val').textContent = U; render(); };
document.getElementById('lr6-i').oninput = ev => { I = +ev.target.value; document.getElementById('lr6-i-val').textContent = I.toFixed(2); render(); };
document.getElementById('lr6-t').oninput = ev => { t = +ev.target.value; document.getElementById('lr6-t-val').textContent = t; render(); };
render();
}
function _lr6_sim(){
function u(){
const U = +document.getElementById('lr6-u').value;
const I = +document.getElementById('lr6-i').value;
const tMin = +document.getElementById('lr6-t').value;
document.getElementById('lr6-uv').textContent = U.toFixed(1);
document.getElementById('lr6-iv').textContent = I.toFixed(2);
document.getElementById('lr6-tv').textContent = tMin;
const P = U*I;
const A = P * tMin * 60;
document.getElementById('lr6-p').textContent = P.toFixed(2);
document.getElementById('lr6-a').textContent = A.toFixed(0);
}
['lr6-u','lr6-i','lr6-t'].forEach(id => document.getElementById(id).addEventListener('input', u));
u();
}
/* ======== ЛР 7 — Отражение света ======== */
function build_lr7(){
const box = document.getElementById('lr7-body');
let h = _labHeader('ЛР 7', 'Изучение явления отражения света',
'Направить лазерный луч на плоское зеркало под разными углами. Измерить $\\alpha$ и $\\beta$ транспортиром. Проверить закон отражения $\\alpha = \\beta$.',
'плоское зеркало, лазер (или фонарик с щелью), транспортир, лист бумаги, карандаш',
[
'Положи зеркало вертикально на лист.',
'Направь луч на зеркало под углом ($\\alpha$ от нормали).',
'Отметь падающий и отражённый лучи карандашом.',
'Измерь $\\alpha$ и $\\beta$ транспортиром.',
'Повторяй для разных $\\alpha$ и заполняй таблицу.'
]);
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">СИМ</span><div class="wg-title">Виртуальный опыт</div></div>'
+'<div class="sliders" style="margin-bottom:10px"><label>Угол падения $\\alpha$, &#176;: <b id="lr7-av">30</b><input type="range" id="lr7-a" min="10" max="80" step="5" value="30"></label></div>'
+'<svg id="lr7-sim" viewBox="0 0 460 220" style="width:100%;height:auto;background:#f8fafc;border-radius:9px;border:1px solid var(--border)"></svg>'
+'<div class="score-display" style="margin-top:8px"><span>$\\alpha$ = <b id="lr7-as">30&#176;</b></span><span>Измерено $\\beta$ = <b id="lr7-bs">30&#176;</b></span><span>$\\alpha = \\beta$ &#10003;</span></div></div>';
/* таблица результатов */
h += '<div class="wg"><div class="wg-header"><span class="wg-badge">ТАБЛИЦА</span><div class="wg-title">Серия измерений</div></div>'
+'<table style="width:100%;border-collapse:collapse;font-size:.92rem"><thead><tr style="background:rgba(15,23,42,.04)"><th style="padding:6px">Опыт</th><th style="padding:6px;text-align:right">$\\alpha$, &#176;</th><th style="padding:6px;text-align:right">$\\beta$, &#176;</th><th style="padding:6px;text-align:right">$\\alpha - \\beta$</th></tr></thead>'
+'<tbody><tr><td style="padding:5px">1</td><td style="padding:5px;text-align:right">15</td><td style="padding:5px;text-align:right">15</td><td style="padding:5px;text-align:right">0</td></tr>'
+'<tr><td style="padding:5px">2</td><td style="padding:5px;text-align:right">30</td><td style="padding:5px;text-align:right">30</td><td style="padding:5px;text-align:right">0</td></tr>'
+'<tr><td style="padding:5px">3</td><td style="padding:5px;text-align:right">45</td><td style="padding:5px;text-align:right">45</td><td style="padding:5px;text-align:right">0</td></tr>'
+'<tr><td style="padding:5px">4</td><td style="padding:5px;text-align:right">60</td><td style="padding:5px;text-align:right">60</td><td style="padding:5px;text-align:right">0</td></tr>'
+'</tbody></table></div>';
h += _labResult('lr7', '<p>Во всех опытах $\\alpha = \\beta$ в пределах погрешности измерения.</p>'
+'<p><b>Вывод:</b> закон отражения $\\alpha = \\beta$ выполняется для любых углов падения.</p>');
/* IV6 — Закон отражения света (Phase 4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge" style="background:#d1fae5;color:#047857">IV-6</span><div class="wg-title">Закон отражения света</div></div>'
+'<div class="wg-help">Двигай угол падения α — угол отражения β равен ему. Луч идёт по правилу: «угол падения = углу отражения».</div>'
+'<div class="p8-sandbox" id="lr7-iv6-sandbox" style="height:260px"></div>'
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол падения</span><input type="range" id="lr7-a" min="0" max="80" step="1" value="40"><span class="p8-scrubber-value"><span id="lr7-a-val">40</span><span class="p8-unit">°</span></span></div>'+'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"><div class="p8-readout"><span class="p8-readout-label">α</span><span class="p8-readout-value" id="lr7-a-out">40</span><span class="p8-readout-unit">°</span></div><div class="p8-readout"><span class="p8-readout-label">β</span><span class="p8-readout-value" id="lr7-b">40</span><span class="p8-readout-unit">°</span></div></div>'
+'</div>';
box.innerHTML = h + secNavFor('lr7') + readButton('lr7');
renderMath(box); wireReadBtn('lr7');
_initLR7_iv6();
_lr7_sim(); _labWireSubmit('lr7', 'lr7');
}
function _initLR7_iv6(){
const sb = document.getElementById('lr7-iv6-sandbox');
if (!sb || !window.P8Helpers) return;
const svg = P8Helpers.svg.create(560, 260);
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
sb.appendChild(svg);
let alpha = 40;
function render(){
svg.innerHTML='';
const cx = 280, cy = 210;
svg.appendChild(P8Helpers.optics.mirrorPlane(60, 210, 500, 210));
svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 210, x2: cx, y2: 30, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '5 3' }));
const aRad = alpha*Math.PI/180;
const len = 170;
/* Incident */
const inX = cx - len*Math.sin(aRad), inY = cy - len*Math.cos(aRad);
svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3.5, glow: true }));
/* Reflected */
const rX = cx + len*Math.sin(aRad), rY = cy - len*Math.cos(aRad);
svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3.5, glow: true }));
/* Angle arcs */
svg.appendChild(P8Helpers.svg.el('path', { d: 'M '+(cx-25*Math.sin(aRad/2))+' '+(cy-25*Math.cos(aRad/2))+' A 25 25 0 0 1 '+cx+' '+(cy-25)+' ', fill: 'none', stroke: '#dc2626', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('text', { x: cx-22, y: cy-40, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'α='+alpha+'°' }));
svg.appendChild(P8Helpers.svg.el('path', { d: 'M '+cx+' '+(cy-25)+' A 25 25 0 0 1 '+(cx+25*Math.sin(aRad/2))+' '+(cy-25*Math.cos(aRad/2))+' ', fill: 'none', stroke: '#16a34a', 'stroke-width': 2 }));
svg.appendChild(P8Helpers.svg.el('text', { x: cx+22, y: cy-40, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', 'text-anchor':'middle', text: 'β='+alpha+'°' }));
/* Verdict */
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 250, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':700, fill:'#10b981', 'text-anchor':'middle', text: '✓ α = β — закон отражения' }));
document.getElementById('lr7-a-out').textContent = alpha;
document.getElementById('lr7-b').textContent = alpha;
}
document.getElementById('lr7-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('lr7-a-val').textContent = alpha; render(); };
render();
}
function _lr7_sim(){
const svg = document.getElementById('lr7-sim'); if(!svg) return;
function d(){
const a = +document.getElementById('lr7-a').value;
document.getElementById('lr7-av').textContent = a;
document.getElementById('lr7-as').textContent = a+'&#176;';
document.getElementById('lr7-bs').textContent = a+'&#176;';
svg.innerHTML = window.OPTICS.reflectRay(230, 150, a, 100);
}
document.getElementById('lr7-a').addEventListener('input', d); d();
}
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);
</script>
</body>
</html>