From d2ce0d70b28440b8b6fdb2e0fc345a7c26b382c2 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 09:50:53 +0300 Subject: [PATCH] =?UTF-8?q?feat(phys9=20ch4):=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=206=20=D0=B2=D0=B8=D0=B4=D0=B6?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B2=20Wave=20D=20=E2=80=94=20=D0=93=D0=BB?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=204=20=C2=AB=D0=98=D0=BC=D0=BF=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=D1=81,=20=D1=8D=D0=BD=D0=B5=D1=80=D0=B3=D0=B8=D1=8F,=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B5=D0=B1=D0=B0=D0=BD=D0=B8=D1=8F=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новый модуль frontend/js/phys9_ch4_widgets.js — экспортирует window.PHYS9_CH4_WIDGETS = { p31..p36: fn }. Виджеты: - §31 CALC: импульс p=mv с slider'ами m, v и бытовой аналогией (футбольный мяч / автомобиль / грузовик и т.д.) - §32 CALC: ЗСИ — упругий и неупругий удар двух тел. m₁, v₁, m₂, v₂ → v₁', v₂', проверка сохранения импульса - §33 DnD: 9 ситуаций → знак работы (A>0/A<0/A=0) - §34 CALC: Ek+Ep с slider'ами m, v, h - §35 CALC: ЗСМЭ — найти v в любой точке горки по высоте старта (h_старт=5, h_тек=0 → v=9.9 м/с) - §36 CALC: период маятника (математ./пружинный) — переключатель, формула обновляется автоматически Подключено в physics_9_ch4.html через тот же hook ensureBuilt. Co-Authored-By: Claude Opus 4.7 --- frontend/js/phys9_ch4_widgets.js | 258 ++++++++++++++++++++++++++ frontend/textbooks/physics_9_ch4.html | 4 +- 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 frontend/js/phys9_ch4_widgets.js diff --git a/frontend/js/phys9_ch4_widgets.js b/frontend/js/phys9_ch4_widgets.js new file mode 100644 index 0000000..c791c4c --- /dev/null +++ b/frontend/js/phys9_ch4_widgets.js @@ -0,0 +1,258 @@ +// phys9_ch4_widgets.js — виджеты для Физики 9, Глава 4 (§31-§36): импульс, энергия, колебания. +(function(){ +'use strict'; +const C = () => window.PHYS9_COLORS || {}; +const PI = Math.PI; + +function dndPool(secId, items, cats){ + let p='
'; + items.forEach(it=>{ p += '
'+it.html+'
'; }); + p += '
'; + cats.forEach(c=>{ p += '
'+c.label+'
'; }); + return p + '
'; +} +function wireDnd(scopeId, items){ + const scope = document.querySelector('#'+scopeId); if(!scope) return; + scope.querySelectorAll('.dnd-chip').forEach(chip=>{ + chip.addEventListener('dragstart', e=>{ e.dataTransfer.setData('text/plain', chip.dataset.id); chip.style.opacity='0.5'; }); + chip.addEventListener('dragend', e=>{ chip.style.opacity='1'; }); + }); + scope.querySelectorAll('.drop-box').forEach(box=>{ + box.addEventListener('dragover', e=>{ e.preventDefault(); box.style.borderColor=C().force||'#10b981'; }); + box.addEventListener('dragleave', e=>{ box.style.borderColor=''; }); + box.addEventListener('drop', e=>{ + e.preventDefault(); box.style.borderColor=''; + const id = e.dataTransfer.getData('text/plain'); + const chip = scope.querySelector('.dnd-chip[data-id="'+id+'"]'); + if(chip) box.querySelector('.drop-items').appendChild(chip); + }); + }); + scope.querySelector('.dnd-check').addEventListener('click', ()=>{ + let wrong=0; const total = items.length; + scope.querySelectorAll('.drop-box').forEach(box=>{ + const cat = box.dataset.cat; + box.querySelectorAll('.dnd-chip').forEach(chip=>{ if(chip.dataset.cat !== cat) wrong++; }); + }); + let placed=0; scope.querySelectorAll('.drop-box .dnd-chip').forEach(()=>placed++); + const fb = scope.querySelector('.dnd-fb'); + if(placed < total){ fb.className='feedback fail'; fb.innerHTML='Распредели все — осталось '+(total-placed)+'.'; return; } + if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='✓ Идеально!'; } + else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+'.'; } + }); +} +function wgWrapper(secId, badge, title, help, body){ + return '
'+badge+'
'+title+'
'+help+'
'+body+'
'; +} +function appendTo(secId, html){ + const box = document.getElementById(secId+'-body'); + if(!box) return false; + if(box.querySelector('.wg-phys9-extra-'+secId)) return false; + const div = document.createElement('div'); div.className = 'wg-phys9-extra-'+secId; div.innerHTML = html; + box.appendChild(div); + try { if(window.renderMathInElement) window.renderMathInElement(box); } catch(e){} + return true; +} + +/* ====== §31 — Импульс p = mv ====== */ +function add_p31(){ + const body = '
' + +'' + +'' + +'
' + +'
' + +'$p = mv$ = 20 кг·м/с' + +'Аналог: футбольный мяч после удара' + +'
'; + if(appendTo('p31', wgWrapper('p31-extra', 'CALC', 'Импульс тела', '$\\vec p = m \\vec v$ — векторная величина. Направление = направление скорости.', body))){ + const upd = ()=>{ + const m = +document.getElementById('p31w-m-r').value; + const v = +document.getElementById('p31w-v-r').value; + document.getElementById('p31w-m').textContent = m; + document.getElementById('p31w-v').textContent = v; + const p = m*v; + document.getElementById('p31w-p').textContent = p.toFixed(1); + let cmp = ''; + const ap = Math.abs(p); + if(ap < 0.5) cmp = 'медленный пешеход с ребёнком'; + else if(ap < 5) cmp = 'идущий человек'; + else if(ap < 30) cmp = 'футбольный мяч'; + else if(ap < 300) cmp = 'велосипедист'; + else if(ap < 5000) cmp = 'автомобиль'; + else cmp = 'грузовик / поезд'; + document.getElementById('p31w-cmp').textContent = cmp; + }; + document.getElementById('p31w-m-r').addEventListener('input', upd); + document.getElementById('p31w-v-r').addEventListener('input', upd); + upd(); + } +} + +/* ====== §32 — ЗСИ: упругий и неупругий удар ====== */ +function add_p32(){ + const body = '
' + +'' + +'' + +'' + +'' + +'' + +'
' + +'
' + +'$p_{до} = m_1 v_1 + m_2 v_2$ = 4 кг·м/с' + +'$v_1\'$ = 0.8 м/с, $v_2\'$ = 0.8 м/с' + +'$p_{после}$ = 4 кг·м/с (сохраняется ✓)' + +'
'; + if(appendTo('p32', wgWrapper('p32-extra', 'CALC', 'Закон сохранения импульса', 'В замкнутой системе $\\Sigma p =$ const. При неупругом ударе тела движутся одной скоростью.', body))){ + const upd = ()=>{ + const m1 = +document.getElementById('p32w-m1-r').value; + const v1 = +document.getElementById('p32w-v1-r').value; + const m2 = +document.getElementById('p32w-m2-r').value; + const v2 = +document.getElementById('p32w-v2-r').value; + const tp = document.getElementById('p32w-type').value; + document.getElementById('p32w-m1').textContent = m1; + document.getElementById('p32w-v1').textContent = v1; + document.getElementById('p32w-m2').textContent = m2; + document.getElementById('p32w-v2').textContent = v2; + const pdo = m1*v1 + m2*v2; + let v1p, v2p; + if(tp === 'abs'){ v1p = pdo/(m1+m2); v2p = v1p; } + else { v1p = ((m1-m2)*v1 + 2*m2*v2)/(m1+m2); v2p = ((m2-m1)*v2 + 2*m1*v1)/(m1+m2); } + document.getElementById('p32w-pdo').textContent = pdo.toFixed(2); + document.getElementById('p32w-v1p').textContent = v1p.toFixed(2); + document.getElementById('p32w-v2p').textContent = v2p.toFixed(2); + document.getElementById('p32w-ppos').textContent = (m1*v1p + m2*v2p).toFixed(2); + }; + ['p32w-m1-r','p32w-v1-r','p32w-m2-r','p32w-v2-r','p32w-type'].forEach(id=>document.getElementById(id).addEventListener('input', upd)); + document.getElementById('p32w-type').addEventListener('change', upd); + upd(); + } +} + +/* ====== §33 — Работа: знак ====== */ +function add_p33(){ + const items = [ + {id:'i1', cat:'pos', html:'тянем сани горизонтально по снегу'}, + {id:'i2', cat:'pos', html:'поднимаем груз вверх (рукой)'}, + {id:'i3', cat:'pos', html:'двигатель толкает машину'}, + {id:'i4', cat:'neg', html:'сила трения тормозит брусок'}, + {id:'i5', cat:'neg', html:'сила тяжести при подъёме вверх'}, + {id:'i6', cat:'neg', html:'тормоза автомобиля'}, + {id:'i7', cat:'zer', html:'несём чемодан по горизонтали (вес ⊥ путь)'}, + {id:'i8', cat:'zer', html:'тело неподвижно (s=0)'}, + {id:'i9', cat:'zer', html:'сила перпендикулярна скорости (по окружности)'} + ]; + const body = dndPool('p33ex', items, [ + {cat:'pos', label:'$A > 0$'}, + {cat:'zer', label:'$A = 0$'}, + {cat:'neg', label:'$A < 0$'} + ]) + '
'; + if(appendTo('p33', wgWrapper('p33-extra', 'DnD', 'Знак работы $A = Fs\\cos\\alpha$', 'Сила вдоль движения → $A > 0$; против → $A < 0$; перпендикулярно → $A = 0$.', body))){ + wireDnd('p33-extra', items); + } +} + +/* ====== §34 — Энергия Ek, Ep ====== */ +function add_p34(){ + const body = '
' + +'' + +'' + +'' + +'
' + +'
' + +'$E_k = mv^2/2$ = 100 Дж' + +'$E_p = mgh$ = 98 Дж' + +'$E = E_k + E_p$ = 198 Дж' + +'
'; + if(appendTo('p34', wgWrapper('p34-extra', 'CALC', 'Кинетическая и потенциальная энергия', 'Ek зависит от $v$, Ep — от $h$. Их сумма — полная механическая энергия.', body))){ + const upd = ()=>{ + const m = +document.getElementById('p34w-m-r').value; + const v = +document.getElementById('p34w-v-r').value; + const h = +document.getElementById('p34w-h-r').value; + document.getElementById('p34w-m').textContent = m; + document.getElementById('p34w-v').textContent = v; + document.getElementById('p34w-h').textContent = h.toFixed(1); + const Ek = m*v*v/2; + const Ep = m*9.8*h; + document.getElementById('p34w-Ek').textContent = Ek.toFixed(0); + document.getElementById('p34w-Ep').textContent = Ep.toFixed(0); + document.getElementById('p34w-E').textContent = (Ek+Ep).toFixed(0); + }; + ['p34w-m-r','p34w-v-r','p34w-h-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd)); + upd(); + } +} + +/* ====== §35 — ЗСМЭ: v внизу горки ====== */ +function add_p35(){ + const body = '
' + +'' + +'' + +'' + +'
' + +'
' + +'$E_0 = mgh_0$ = 49 Дж' + +'$E_{p}^{тек} = mgh$ = 0 Дж' + +'$E_k = E_0 - E_p$ = 49 Дж' + +'$v = \\sqrt{2E_k/m}$ = 9.9 м/с' + +'
'; + if(appendTo('p35', wgWrapper('p35-extra', 'CALC', 'Закон сохранения энергии', 'В отсутствие трения: $E_p^{старт} = E_p + E_k$. Найди $v$ в любой точке.', body))){ + const upd = ()=>{ + const m = +document.getElementById('p35w-m-r').value; + const h0 = +document.getElementById('p35w-h0-r').value; + const h = +document.getElementById('p35w-h-r').value; + document.getElementById('p35w-m').textContent = m; + document.getElementById('p35w-h0').textContent = h0.toFixed(1); + document.getElementById('p35w-h').textContent = h.toFixed(1); + const E0 = m*9.8*h0; + const Ep = m*9.8*Math.min(h, h0); + const Ek = Math.max(0, E0 - Ep); + const v = Math.sqrt(2*Ek/m); + document.getElementById('p35w-E0').textContent = E0.toFixed(1); + document.getElementById('p35w-Ep').textContent = Ep.toFixed(1); + document.getElementById('p35w-Ek').textContent = Ek.toFixed(1); + document.getElementById('p35w-v').textContent = v.toFixed(2); + }; + ['p35w-m-r','p35w-h0-r','p35w-h-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd)); + upd(); + } +} + +/* ====== §36 — Период маятника ====== */ +function add_p36(){ + const body = '
' + +'' + +'' + +'' + +'
' + +'
' + +'Формула: $T = 2\\pi\\sqrt{l/g}$' + +'$T$ = 2.01 с' + +'$\\nu = 1/T$ = 0.50 Гц' + +'
'; + if(appendTo('p36', wgWrapper('p36-extra', 'CALC', 'Период колебаний', 'Математический: $T = 2\\pi\\sqrt{l/g}$. Пружинный: $T = 2\\pi\\sqrt{m/k}$.', body))){ + const upd = ()=>{ + const tp = document.getElementById('p36w-t').value; + const x = +document.getElementById('p36w-x-r').value; + const y = +document.getElementById('p36w-y-r').value; + document.getElementById('p36w-x').textContent = x.toFixed(2); + document.getElementById('p36w-y').textContent = y.toFixed(1); + let T; + if(tp === 'math'){ + T = 2*PI*Math.sqrt(x/y); + document.getElementById('p36w-formula').textContent = 'T = 2π√(l/g)'; + } else { + T = 2*PI*Math.sqrt(x/y); + document.getElementById('p36w-formula').textContent = 'T = 2π√(m/k)'; + } + document.getElementById('p36w-T').textContent = T.toFixed(2); + document.getElementById('p36w-nu').textContent = (1/T).toFixed(2); + }; + document.getElementById('p36w-t').addEventListener('change', upd); + document.getElementById('p36w-x-r').addEventListener('input', upd); + document.getElementById('p36w-y-r').addEventListener('input', upd); + upd(); + } +} + +window.PHYS9_CH4_WIDGETS = { p31:add_p31, p32:add_p32, p33:add_p33, p34:add_p34, p35:add_p35, p36:add_p36 }; + +})(); diff --git a/frontend/textbooks/physics_9_ch4.html b/frontend/textbooks/physics_9_ch4.html index c39e114..ffff833 100644 --- a/frontend/textbooks/physics_9_ch4.html +++ b/frontend/textbooks/physics_9_ch4.html @@ -17,6 +17,7 @@ +