From 0d9226f6d55aa0dc5feb38fc42b5d7d708b63874 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 10:20:49 +0300 Subject: [PATCH] =?UTF-8?q?feat(phys8=20ch2):=20Phase=202.3=20=E2=80=94=20?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=88=D0=B8=D0=B5=D1=81=D1=8F=20?= =?UTF-8?q?14=20IV-6=20(Ch2=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scrubberWidget() helper в скрипте — генерирует виджет с N scrubbers + 1 readout + live SVG render. §13 Проводники/диэлектрики: 2 стержня, движущиеся электроны в меди, застрявшие в стекле. Анимация через setInterval. §14 Электростатическая индукция: + палочка + проводник, при сближении видны индуцированные −/+ заряды на сторонах. §15 q=ne: сфера тела с радиально размещёнными ±зарядами, расчёт n = q/e. §16 Строение атома: ядро + N электронов на orbitals (2-8-8-2 shells). §18 A=qU: 2 пластины + drag-arrow с подписью работы. §19 ЭДС: батарея с readout ε = A/q. §20 I=q/t: провод с анимированными носителями. §21 Замкнутая цепь: батарея + switch + лампа, кнопка открыть/замкнуть. §23 R=ρl/S: динамический wire с длиной/толщиной по scrubberу, R вычисляется для меди. §24 Последовательное: 2 резистора, общее R=R1+R2, I. §26 P=UI: светящаяся лампа brightness ∝ P, лучи при P>120 Вт. §27 A=UIt: time-bar 0-24 ч, A в кВт·ч. §29 B≈I вокруг провода: концентрические штриховые круги, opacity ∝ |I|. §31 Электромагнит: соленоид (число катушек по N), железный сердечник, полевые линии-параболы с интенсивностью по B=NI. --- backend/scripts/redesign_p8_ch2_3.cjs | 507 ++++++++++++++++++ frontend/textbooks/physics_8_ch2.html | 734 +++++++++++++++++++++++--- 2 files changed, 1157 insertions(+), 84 deletions(-) create mode 100644 backend/scripts/redesign_p8_ch2_3.cjs diff --git a/backend/scripts/redesign_p8_ch2_3.cjs b/backend/scripts/redesign_p8_ch2_3.cjs new file mode 100644 index 0000000..754d529 --- /dev/null +++ b/backend/scripts/redesign_p8_ch2_3.cjs @@ -0,0 +1,507 @@ +// Phase 2.3 — оставшиеся 14 IV-6 для Ch2. +'use strict'; +const fs = require('fs'); +const path = require('path'); + +const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_ch2.html'); +let h = fs.readFileSync(DST, 'utf8'); + +function makeStubText(n) { + return `/* IV6 — flagship интерактив (заглушка Phase 2, наполнение в Phase 2.${n}) */ + h += '
' + +'
IV-6
Новый интерактив §${n}
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 2.${n} — coming soon
' + +'
' + +'
';`; +} + +function replaceStub(pid, n, widgetHtml, initFn) { + const stubLF = makeStubText(n); + const stubCRLF = stubLF.replace(/\n/g, '\r\n'); + let stubText = null; + if (h.includes(stubLF)) stubText = stubLF; + else if (h.includes(stubCRLF)) stubText = stubCRLF; + if (!stubText) { console.warn(`${pid}: stub not found`); return false; } + const eol = stubText === stubCRLF ? '\r\n' : '\n'; + const widget = widgetHtml.trim().replace(/\n/g, eol); + h = h.replace(stubText, widget); + h = h.replace(`wireReadBtn('${pid}');`, `wireReadBtn('${pid}');\n _init${pid.toUpperCase()}_iv6();`); + const fnStart = h.indexOf(`function build_${pid}()`); + const fnEnd = h.indexOf('\n}\n', fnStart); + h = h.slice(0, fnEnd + 3) + '\n' + initFn.trim() + '\n' + h.slice(fnEnd + 3); + console.log(`${pid}: replaced`); + return true; +} + +// Compact builder: scrubber-driven calculator with live SVG. +function scrubberWidget(pid, n, title, helpHtml, inputs, formula, svgRender) { + const html = `/* IV6 — ${title} (Phase 2.3) */ + h += '
' + +'
IV-6
${title}
' + +'
${helpHtml}
' + +'
' + +'
' +${inputs.map(inp => ` +'
${inp.label}${inp.value}${inp.unit}
'`).join('\n')} + +'
${formula.label}${formula.initial}${formula.unit}
' + +'
' + +'
';`; + const inputBindings = inputs.map(inp => + ` state.${inp.id} = ${inp.value};\n const ${inp.id}Inp = document.getElementById('${pid}-iv6-${inp.id}');\n const ${inp.id}Lab = document.getElementById('${pid}-iv6-${inp.id}-val');\n ${inp.id}Inp.oninput = ev => { state.${inp.id} = +ev.target.value; ${inp.id}Lab.textContent = (${inp.id == 'a' ? 'state.'+inp.id : 'state.'+inp.id}).toFixed(${inp.step >= 1 ? 0 : 2}); render(); };` + ).join('\n'); + const init = ` +function _init${pid.toUpperCase()}_iv6(){ + const sb = document.getElementById('${pid}-iv6-sandbox'); + if (!sb || !window.P8Helpers) return; + const svg = P8Helpers.svg.create(560, 200); + svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block'; + sb.appendChild(svg); + const state = {}; + function render(){ + svg.innerHTML = ''; + ${svgRender} + } +${inputBindings} + render(); +} +`; + replaceStub(pid, n, html, init); +} + +// ============================================================ +// §13 — Проводники / диэлектрики +// ============================================================ +scrubberWidget('p13', 13, + 'Проводники vs диэлектрики', + 'Двигай напряжение — в проводнике (медь, $n \\\\sim 10^{29}$/м³) свободные электроны легко движутся, в диэлектрике (стекло) — нет.', + [{ id: 'u', label: 'U', min: 0, max: 100, step: 1, value: 0, unit: 'В' }], + { label: 'Ток', initial: '0', unit: 'А' }, + ` + const U = state.u; + /* Conductor (left) — copper bar */ + svg.appendChild(P8Helpers.svg.el('rect', { x: 50, y: 60, width: 180, height: 60, fill: '#b45309', stroke: '#0f172a', 'stroke-width': 2, rx: 5 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 140, y: 50, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'Проводник (медь)' })); + /* Moving electrons in conductor */ + const numE = 8; + for (let i = 0; i < numE; i++) { + const t = (Date.now() / 100 + i * 20) % 100 / 100; + const x = 60 + t * 160; + svg.appendChild(P8Helpers.svg.el('circle', { cx: x, cy: 80 + (i % 2) * 20, r: 4, fill: '#dc2626', opacity: U > 5 ? 1 : 0.3 })); + } + /* Insulator (right) — glass bar */ + svg.appendChild(P8Helpers.svg.el('rect', { x: 320, y: 60, width: 180, height: 60, fill: '#bae6fd', stroke: '#0f172a', 'stroke-width': 2, rx: 5 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 410, y: 50, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'Диэлектрик (стекло)' })); + /* Stuck electrons */ + for (let i = 0; i < 8; i++) { + svg.appendChild(P8Helpers.svg.el('circle', { cx: 335 + i * 22, cy: 80 + (i % 2) * 20, r: 4, fill: '#475569' })); + } + /* Current ↦ in conductor only */ + const I = U > 1 ? U / 10 : 0; + svg.appendChild(P8Helpers.svg.el('text', { x: 140, y: 145, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'I = '+I.toFixed(2)+' А' })); + svg.appendChild(P8Helpers.svg.el('text', { x: 410, y: 145, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'#94a3b8', 'text-anchor':'middle', text: 'I = 0 А' })); + document.getElementById('p13-iv6-out').textContent = I.toFixed(2); + /* Animate by re-render every 50ms */ + if (!sb._anim) sb._anim = setInterval(() => { if (sb.isConnected) render(); else { clearInterval(sb._anim); } }, 100); + ` +); + +// ============================================================ +// §14 — Электростатическая индукция +// ============================================================ +scrubberWidget('p14', 14, + 'Электростатическая индукция', + 'Двигай заряженную палочку к незаряженному проводнику. Свободные электроны притягиваются к + или отталкиваются от −, на дальней стороне возникает противоположный заряд.', + [{ id: 'd', label: 'Расстояние', min: 50, max: 300, step: 5, value: 200, unit: 'мм' }], + { label: 'Индукция', initial: '0', unit: 'отн.' }, + ` + const d = state.d; + /* Charged rod (left) */ + svg.appendChild(P8Helpers.svg.el('rect', { x: 30, y: 80, width: 60, height: 30, fill: '#fecaca', stroke: '#dc2626', 'stroke-width': 2, rx: 5 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 60, y: 100, 'font-family':"'Unbounded',sans-serif", 'font-size':18, 'font-weight':900, fill: '#dc2626', 'text-anchor':'middle', text: '+++' })); + /* Conductor (right at position 90 + d) */ + const condX = 90 + d; + svg.appendChild(P8Helpers.svg.el('rect', { x: condX, y: 70, width: 140, height: 50, fill: '#fef3c7', stroke: '#0f172a', 'stroke-width': 2, rx: 5 })); + /* Distribution: near side − , far side + (induction) */ + const intensity = Math.max(0, Math.min(1, (300 - d) / 250)); + if (intensity > 0.1) { + svg.appendChild(P8Helpers.svg.el('text', { x: condX + 25, y: 100, 'font-family':"'Unbounded',sans-serif", 'font-size':16, 'font-weight':900, fill: '#2563eb', 'text-anchor':'middle', text: '−−' })); + svg.appendChild(P8Helpers.svg.el('text', { x: condX + 115, y: 100, 'font-family':"'Unbounded',sans-serif", 'font-size':16, 'font-weight':900, fill: '#dc2626', 'text-anchor':'middle', text: '++' })); + } + document.getElementById('p14-iv6-out').textContent = intensity.toFixed(2); + ` +); + +// ============================================================ +// §15 — Элементарный заряд (n = q/e) +// ============================================================ +scrubberWidget('p15', 15, + 'Элементарный заряд: $q = ne$', + 'Любой заряд кратен $e = 1{,}6 \\\\cdot 10^{-19}$ Кл. Двигай заряд тела — посчитаем число избыточных электронов.', + [{ id: 'q', label: 'q', min: -10, max: 10, step: 0.1, value: 1, unit: 'нКл' }], + { label: 'n электронов', initial: '6.25', unit: '×10¹⁰' }, + ` + const q = state.q * 1e-9; + const n = Math.abs(q / 1.6e-19); + const sign = q > 0 ? 1 : -1; + /* Body (sphere) */ + svg.appendChild(P8Helpers.svg.el('circle', { cx: 200, cy: 100, r: 50, fill: sign > 0 ? '#fecaca' : '#bfdbfe', stroke: sign > 0 ? '#dc2626' : '#2563eb', 'stroke-width': 3 })); + /* +/- charges around */ + const numE = Math.min(12, Math.round(n / 1e10) + 1); + for (let i = 0; i < numE; i++) { + const a = i * 2 * Math.PI / numE; + svg.appendChild(P8Helpers.svg.el('text', { x: 200 + 38 * Math.cos(a), y: 105 + 38 * Math.sin(a), 'font-family':"'Inter',sans-serif", 'font-size':14, 'font-weight':900, fill: sign > 0 ? '#dc2626' : '#2563eb', 'text-anchor':'middle', text: sign > 0 ? '+' : '−' })); + } + /* Counter */ + svg.appendChild(P8Helpers.svg.el('text', { x: 380, y: 90, 'font-family':"'Unbounded',sans-serif", 'font-size':14, 'font-weight':800, fill:'#0f172a', 'text-anchor':'middle', text: 'n = q/e' })); + svg.appendChild(P8Helpers.svg.el('text', { x: 380, y: 115, 'font-family':"'JetBrains Mono',monospace", 'font-size':16, 'font-weight':700, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: '≈ '+(n/1e10).toFixed(2)+'·10¹⁰' })); + document.getElementById('p15-iv6-out').textContent = (n / 1e10).toFixed(2); + ` +); + +// ============================================================ +// §16 — Строение атома +// ============================================================ +scrubberWidget('p16', 16, + 'Строение атома', + 'Атом нейтрален: число протонов $Z$ = число электронов. Двигай Z, наблюдай орбиту электронов вокруг ядра.', + [{ id: 'z', label: 'Z (протоны)', min: 1, max: 20, step: 1, value: 6, unit: '' }], + { label: 'Заряд ядра', initial: '+6e', unit: '' }, + ` + const Z = Math.round(state.z); + /* Nucleus */ + svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 100, r: 14, fill: '#dc2626', stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 105, 'font-family':"'Unbounded',sans-serif", 'font-size':12, 'font-weight':900, fill:'#fff', 'text-anchor':'middle', text: '+'+Z })); + /* Orbits — fill shell by shell: 2, 8, 8, 2 */ + const shells = []; + let remaining = Z; + [2, 8, 8, 2].forEach(cap => { if (remaining > 0) { shells.push(Math.min(cap, remaining)); remaining -= cap; }}); + shells.forEach((electrons, shellIdx) => { + const radius = 35 + shellIdx * 20; + svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 100, r: radius, fill: 'none', stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '3 3' })); + for (let i = 0; i < electrons; i++) { + const a = i * 2 * Math.PI / electrons; + svg.appendChild(P8Helpers.svg.el('circle', { cx: 280 + radius * Math.cos(a), cy: 100 + radius * Math.sin(a), r: 4.5, fill: '#2563eb', stroke: '#0f172a', 'stroke-width': 1 })); + } + }); + document.getElementById('p16-iv6-out').textContent = '+'+Z+'e'; + ` +); + +// ============================================================ +// §18 — A = qU +// ============================================================ +scrubberWidget('p18', 18, + 'Работа поля: $A = qU$', + 'Двигай заряд $q$ и напряжение $U$. Работа $A = qU$ обновляется live.', + [ + { id: 'q', label: 'q', min: 0.1, max: 10, step: 0.1, value: 1, unit: 'мкКл' }, + { id: 'u', label: 'U', min: 1, max: 100, step: 1, value: 12, unit: 'В' } + ], + { label: 'A', initial: '12', unit: 'мкДж' }, + ` + const q = state.q, U = state.u; + const A = q * U; + /* Two plates */ + svg.appendChild(P8Helpers.svg.el('line', { x1: 100, y1: 40, x2: 100, y2: 160, stroke: '#dc2626', 'stroke-width': 6 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 100, y: 30, 'font-family':"'Unbounded',sans-serif", 'font-size':14, 'font-weight':900, fill:'#dc2626', 'text-anchor':'middle', text: '+' })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 400, y1: 40, x2: 400, y2: 160, stroke: '#2563eb', 'stroke-width': 6 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 400, y: 30, 'font-family':"'Unbounded',sans-serif", 'font-size':14, 'font-weight':900, fill:'#2563eb', 'text-anchor':'middle', text: '−' })); + /* Charge moving */ + svg.appendChild(P8Helpers.svg.el('circle', { cx: 200, cy: 100, r: 14, fill: '#fecaca', stroke: '#dc2626', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 200, y: 105, 'font-family':"'Inter',sans-serif", 'font-size':14, 'font-weight':900, fill:'#dc2626', 'text-anchor':'middle', text: '+q' })); + /* Arrow direction of work */ + svg.appendChild(P8Helpers.svg.gradientArrow(svg, 220, 100, 380, 100, { colorFrom: '#facc15', colorTo: '#dc2626', width: 3, headSize: 12, glow: true })); + svg.appendChild(P8Helpers.svg.el('text', { x: 300, y: 90, 'font-family':"'JetBrains Mono',monospace", 'font-size':14, 'font-weight':800, fill:'#0f172a', 'text-anchor':'middle', text: 'A = qU = '+A.toFixed(1)+' мкДж' })); + svg.appendChild(P8Helpers.svg.el('text', { x: 250, y: 180, 'font-family':"'Inter',sans-serif", 'font-size':11, fill:'var(--p8-muted,#64748b)', 'text-anchor':'middle', text: 'U = '+U+' В, между пластинами' })); + document.getElementById('p18-iv6-out').textContent = A.toFixed(1); + ` +); + +// ============================================================ +// §19 — Источники тока +// ============================================================ +scrubberWidget('p19', 19, + 'ЭДС источника: $\\\\mathcal{E} = A/q$', + 'Источник тока совершает работу $A$ над зарядом $q$. ЭДС $\\\\mathcal{E} = A/q$.', + [ + { id: 'a', label: 'A', min: 1, max: 100, step: 1, value: 24, unit: 'Дж' }, + { id: 'q', label: 'q', min: 0.5, max: 20, step: 0.5, value: 2, unit: 'Кл' } + ], + { label: 'ЭДС', initial: '12', unit: 'В' }, + ` + const A = state.a, q = state.q; + const E = A / q; + /* Battery shape */ + svg.appendChild(P8Helpers.svg.el('rect', { x: 200, y: 60, width: 160, height: 80, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 3, rx: 8 })); + svg.appendChild(P8Helpers.svg.el('rect', { x: 215, y: 50, width: 30, height: 10, fill: '#475569' })); + svg.appendChild(P8Helpers.svg.el('rect', { x: 320, y: 50, width: 30, height: 10, fill: '#475569' })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 100, 'font-family':"'Unbounded',sans-serif", 'font-size':22, 'font-weight':900, fill:'#0f172a', 'text-anchor':'middle', text: E.toFixed(1)+' В' })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 125, 'font-family':"'Inter',sans-serif", 'font-size':11, fill:'#0f172a', 'text-anchor':'middle', text: 'A = '+A+' Дж, q = '+q+' Кл' })); + document.getElementById('p19-iv6-out').textContent = E.toFixed(1); + ` +); + +// ============================================================ +// §20 — I = q/t +// ============================================================ +scrubberWidget('p20', 20, + 'Сила тока: $I = q/t$', + 'Двигай заряд и время — найдём ток.', + [ + { id: 'q', label: 'q', min: 0.1, max: 100, step: 0.1, value: 6, unit: 'Кл' }, + { id: 't', label: 't', min: 0.1, max: 60, step: 0.1, value: 2, unit: 'с' } + ], + { label: 'I', initial: '3.0', unit: 'А' }, + ` + const q = state.q, t = state.t; + const I = q / t; + /* Wire with flowing charges */ + svg.appendChild(P8Helpers.svg.el('rect', { x: 80, y: 90, width: 400, height: 20, fill: '#cbd5e1', stroke: '#0f172a', 'stroke-width': 2 })); + const numE = Math.min(20, Math.round(q)); + for (let i = 0; i < numE; i++) { + const t0 = (Date.now() / 200 + i / numE) % 1; + const x = 90 + t0 * 380; + svg.appendChild(P8Helpers.svg.el('circle', { cx: x, cy: 100, r: 4, fill: '#dc2626' })); + } + /* Arrow direction */ + svg.appendChild(P8Helpers.svg.gradientArrow(svg, 480, 100, 530, 100, { colorFrom: '#dc2626', colorTo: '#7f1d1d', width: 3, headSize: 12 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 140, 'font-family':"'JetBrains Mono',monospace", 'font-size':14, 'font-weight':800, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'I = '+q+'/'+t+' = '+I.toFixed(2)+' А' })); + document.getElementById('p20-iv6-out').textContent = I.toFixed(2); + if (!sb._anim) sb._anim = setInterval(() => { if (sb.isConnected) render(); else clearInterval(sb._anim); }, 100); + ` +); + +// ============================================================ +// §21 — Электрическая цепь +// ============================================================ +scrubberWidget('p21', 21, + 'Замкнутая электрическая цепь', + 'Состоит из источника, потребителя и соединительных проводов. Переключи выключатель — пойдёт ток.', + [{ id: 's', label: 'Замкнут', min: 0, max: 1, step: 1, value: 1, unit: '' }], + { label: 'Цепь', initial: 'замкнута', unit: '' }, + ` + const closed = state.s > 0.5; + /* Battery */ + svg.appendChild(P8Helpers.em.circuitComponent('battery', 120, 100, 'h', '6 В')); + /* Switch */ + svg.appendChild(P8Helpers.em.circuitComponent('switch', 270, 100, 'h')); + if (closed) { + svg.appendChild(P8Helpers.svg.el('line', { x1: 258, y1: 100, x2: 282, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + } + /* Lamp */ + svg.appendChild(P8Helpers.em.circuitComponent('lamp', 420, 100, 'h')); + if (closed) { + svg.appendChild(P8Helpers.svg.el('circle', { cx: 420, cy: 100, r: 22, fill: '#fef3c7', opacity: 0.5 })); + } + /* Wires */ + svg.appendChild(P8Helpers.svg.el('line', { x1: 150, y1: 100, x2: 240, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 300, y1: 100, x2: 394, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 446, y1: 100, x2: 500, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 500, y1: 100, x2: 500, y2: 160, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 90, y1: 100, x2: 90, y2: 160, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 90, y1: 160, x2: 500, y2: 160, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 185, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill: closed ? '#16a34a' : '#dc2626', 'text-anchor':'middle', text: closed ? '✓ Цепь замкнута — ток идёт' : '✗ Цепь разомкнута' })); + document.getElementById('p21-iv6-out').textContent = closed ? 'замкнута' : 'разомкнута'; + ` +); + +// ============================================================ +// §23 — R = ρl/S +// ============================================================ +scrubberWidget('p23', 23, + 'Сопротивление: $R = \\\\rho l / S$', + 'Длина увеличивает $R$ пропорционально, площадь — обратно пропорционально. Удельное $\\\\rho$ — для меди 1.7·10⁻⁸ Ом·м.', + [ + { id: 'l', label: 'l', min: 0.1, max: 10, step: 0.1, value: 1, unit: 'м' }, + { id: 's', label: 'S', min: 0.5, max: 10, step: 0.1, value: 1, unit: 'мм²' } + ], + { label: 'R (медь)', initial: '0.017', unit: 'Ом' }, + ` + const l = state.l, S = state.s * 1e-6; + const rho = 1.7e-8; + const R = rho * l / S; + /* Wire shape: длина = l*60 max, толщина = sqrt(S)*8 max */ + const wireL = Math.min(440, 50 + l * 40); + const wireH = Math.min(40, 6 + Math.sqrt(state.s) * 8); + svg.appendChild(P8Helpers.svg.el('rect', { x: (560 - wireL) / 2, y: (200 - wireH) / 2, width: wireL, height: wireH, fill: '#b45309', stroke: '#0f172a', 'stroke-width': 2, rx: 4 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: (200 - wireH)/2 - 8, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'l = '+l.toFixed(1)+' м, S = '+state.s.toFixed(1)+' мм²' })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 170, 'font-family':"'Unbounded',sans-serif", 'font-size':14, 'font-weight':800, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'R = '+R.toFixed(4)+' Ом' })); + document.getElementById('p23-iv6-out').textContent = R.toFixed(4); + ` +); + +// ============================================================ +// §24 — Последовательные резисторы +// ============================================================ +scrubberWidget('p24', 24, + 'Последовательное соединение: $R = R_1 + R_2$', + 'Сложи $R_1$ и $R_2$ — получишь общее $R$. Ток через них одинаков, напряжения складываются: $U = U_1 + U_2$.', + [ + { id: 'r1', label: 'R₁', min: 1, max: 100, step: 1, value: 20, unit: 'Ом' }, + { id: 'r2', label: 'R₂', min: 1, max: 100, step: 1, value: 30, unit: 'Ом' } + ], + { label: 'R_общ', initial: '50', unit: 'Ом' }, + ` + const R1 = state.r1, R2 = state.r2; + const R = R1 + R2; + const U = 12; + const I = U / R; + /* Battery */ + svg.appendChild(P8Helpers.em.circuitComponent('battery', 80, 100, 'h', U+' В')); + /* R1 */ + svg.appendChild(P8Helpers.em.circuitComponent('resistor', 240, 100, 'h', R1+' Ом')); + /* R2 */ + svg.appendChild(P8Helpers.em.circuitComponent('resistor', 400, 100, 'h', R2+' Ом')); + /* Wires */ + svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 100, x2: 210, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 270, y1: 100, x2: 370, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 430, y1: 100, x2: 510, y2: 100, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 510, y1: 100, x2: 510, y2: 160, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 100, x2: 50, y2: 160, stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 160, x2: 510, y2: 160, stroke: '#0f172a', 'stroke-width': 2 })); + /* Labels */ + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 180, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'R = R₁+R₂ = '+R+' Ом, I = U/R = '+I.toFixed(3)+' А' })); + document.getElementById('p24-iv6-out').textContent = R; + ` +); + +// ============================================================ +// §26 — P = UI +// ============================================================ +scrubberWidget('p26', 26, + 'Мощность: $P = UI$', + 'Двигай напряжение и ток — мощность.', + [ + { id: 'u', label: 'U', min: 1, max: 220, step: 1, value: 220, unit: 'В' }, + { id: 'i', label: 'I', min: 0.01, max: 10, step: 0.01, value: 0.5, unit: 'А' } + ], + { label: 'P', initial: '110', unit: 'Вт' }, + ` + const U = state.u, I = state.i; + const P = U * I; + /* Lamp brightness */ + const brightness = Math.min(1, P / 200); + svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 100, r: 50, fill: '#fef3c7', opacity: brightness * 0.5 + 0.2 })); + svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 100, r: 30, fill: '#fde047', stroke: '#0f172a', 'stroke-width': 2 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 105, 'font-family':"'Unbounded',sans-serif", 'font-size':16, 'font-weight':900, fill: '#0f172a', 'text-anchor':'middle', text: P.toFixed(0) })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 122, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill: '#0f172a', 'text-anchor':'middle', text: 'Вт' })); + if (brightness > 0.6) { + /* Rays */ + for (let i = 0; i < 8; i++) { + const a = i * Math.PI / 4; + const x1 = 280 + 38 * Math.cos(a), y1 = 100 + 38 * Math.sin(a); + const x2 = 280 + 58 * Math.cos(a), y2 = 100 + 58 * Math.sin(a); + svg.appendChild(P8Helpers.svg.el('line', { x1, y1, x2, y2, stroke: '#facc15', 'stroke-width': 3 })); + } + } + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 180, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'P = U·I = '+U+'·'+I.toFixed(2)+' = '+P.toFixed(1)+' Вт' })); + document.getElementById('p26-iv6-out').textContent = P.toFixed(1); + ` +); + +// ============================================================ +// §27 — A = UIt (электроэнергия) +// ============================================================ +scrubberWidget('p27', 27, + 'Электроэнергия: $A = UIt$', + 'За время $t$ потребитель потратит $A = UIt$ или $A = Pt$.', + [ + { id: 'p', label: 'P', min: 1, max: 3000, step: 1, value: 100, unit: 'Вт' }, + { id: 't', label: 't', min: 0.1, max: 24, step: 0.1, value: 5, unit: 'ч' } + ], + { label: 'A', initial: '0.5', unit: 'кВт·ч' }, + ` + const P = state.p, t = state.t; + const A = P * t / 1000; /* kWh */ + /* Time bar */ + const barW = (t / 24) * 460; + svg.appendChild(P8Helpers.svg.el('rect', { x: 50, y: 80, width: 460, height: 40, fill: '#e5e7eb', stroke: '#0f172a' })); + svg.appendChild(P8Helpers.svg.el('rect', { x: 50, y: 80, width: barW, height: 40, fill: 'var(--el-mid,#06b6d4)', opacity: 0.7 })); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 105, 'font-family':"'JetBrains Mono',monospace", 'font-size':14, 'font-weight':800, fill:'#fff', 'text-anchor':'middle', text: t.toFixed(1)+' ч из 24' })); + /* A display */ + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 160, 'font-family':"'Unbounded',sans-serif", 'font-size':18, 'font-weight':900, fill:'#0f172a', 'text-anchor':'middle', text: 'A = '+A.toFixed(3)+' кВт·ч' })); + document.getElementById('p27-iv6-out').textContent = A.toFixed(3); + ` +); + +// ============================================================ +// §29 — Магнитное поле тока (видим B-grid) +// ============================================================ +scrubberWidget('p29', 29, + 'Магнитное поле тока: $B \\\\propto I$', + 'Чем больше ток — тем сильнее поле вокруг проводника. Густота линий ∝ $|I|$.', + [{ id: 'i', label: 'I', min: -10, max: 10, step: 0.1, value: 3, unit: 'А' }], + { label: 'B (отн.)', initial: '3', unit: '' }, + ` + const I = state.i; + /* Wire (vertical center) */ + svg.appendChild(P8Helpers.svg.el('line', { x1: 280, y1: 20, x2: 280, y2: 180, stroke: '#0f172a', 'stroke-width': 5 })); + /* Current direction */ + if (Math.abs(I) > 0.05) { + const dir = I > 0 ? 1 : -1; + svg.appendChild(P8Helpers.svg.el('polygon', { points: '280,'+(dir>0?20:180)+' 274,'+(dir>0?30:170)+' 286,'+(dir>0?30:170), fill: '#dc2626' })); + } + /* Field circles around wire */ + const intensity = Math.abs(I) / 10; + [25, 45, 65, 90, 115].forEach((r, k) => { + const opacity = intensity * (1 - k * 0.15); + if (opacity > 0.05) { + svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 100, r, fill: 'none', stroke: '#7c3aed', 'stroke-width': 1.5, opacity, 'stroke-dasharray': '5 3' })); + } + }); + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 195, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'I = '+I.toFixed(1)+' А, B ∝ |I|' })); + document.getElementById('p29-iv6-out').textContent = Math.abs(I).toFixed(1); + ` +); + +// ============================================================ +// §31 — Электромагнит (B ∝ NI) +// ============================================================ +scrubberWidget('p31', 31, + 'Электромагнит: $B \\\\propto NI$', + 'Соленоид с $N$ витками и током $I$ — поле растёт пропорционально и тому, и другому. Сердечник из железа усиливает в $\\\\mu \\\\sim 1000$ раз.', + [ + { id: 'n', label: 'Витки N', min: 10, max: 1000, step: 10, value: 100, unit: '' }, + { id: 'i', label: 'I', min: 0, max: 5, step: 0.1, value: 1, unit: 'А' } + ], + { label: 'B (отн.)', initial: '100', unit: '' }, + ` + const N = state.n, I = state.i; + const B = N * I; + /* Solenoid coils */ + const coils = Math.min(20, Math.round(N / 50) + 4); + const coilW = 16; + for (let k = 0; k < coils; k++) { + const x = 130 + k * coilW; + svg.appendChild(P8Helpers.svg.el('ellipse', { cx: x, cy: 100, rx: 6, ry: 32, fill: 'none', stroke: '#b45309', 'stroke-width': 2 })); + } + /* Iron core */ + svg.appendChild(P8Helpers.svg.el('rect', { x: 120, y: 90, width: coils * coilW + 20, height: 20, fill: '#64748b', stroke: '#0f172a', 'stroke-width': 1 })); + /* Field lines */ + const intensity = Math.min(1, B / 2000); + if (intensity > 0.05) { + [40, 70, 100].forEach((dy, k) => { + const op = intensity * (1 - k * 0.25); + if (op < 0.05) return; + svg.appendChild(P8Helpers.svg.el('path', { d: 'M 50 '+(100-dy)+' Q 280 '+(100-dy*1.5)+', 510 '+(100-dy), fill: 'none', stroke: '#7c3aed', 'stroke-width': 1.5, opacity: op })); + svg.appendChild(P8Helpers.svg.el('path', { d: 'M 50 '+(100+dy)+' Q 280 '+(100+dy*1.5)+', 510 '+(100+dy), fill: 'none', stroke: '#7c3aed', 'stroke-width': 1.5, opacity: op })); + }); + } + svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 180, 'font-family':"'JetBrains Mono',monospace", 'font-size':12, 'font-weight':800, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'B ∝ N·I = '+B+' (отн.)' })); + document.getElementById('p31-iv6-out').textContent = B; + ` +); + +fs.writeFileSync(DST, h); +console.log('ch2 final size:', h.length); + +const scripts = [...h.matchAll(/