Files
Learn_System/frontend/js/phys9_ch4_widgets.js
T
Maxim Dolgolyov d2ce0d70b2 feat(phys9 ch4): добавлены 6 виджетов Wave D — Глава 4 «Импульс, энергия, колебания»
Новый модуль 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 <noreply@anthropic.com>
2026-05-30 09:50:53 +03:00

259 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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='<div class="dnd-pool" id="'+secId+'-pool" style="display:flex;flex-wrap:wrap;gap:6px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;margin-bottom:10px">';
items.forEach(it=>{ p += '<div class="dnd-chip" draggable="true" data-id="'+it.id+'" data-cat="'+it.cat+'" style="padding:6px 11px;border:1.5px solid var(--border);border-radius:9px;background:var(--card);cursor:grab;font-size:.92rem">'+it.html+'</div>'; });
p += '</div><div style="display:grid;grid-template-columns:repeat('+cats.length+',1fr);gap:10px">';
cats.forEach(c=>{ p += '<div class="drop-box" data-cat="'+c.cat+'" style="border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:80px"><h5 style="font-size:.78rem;font-weight:800;margin-bottom:6px;color:var(--sec-acc-d,var(--pri-d))">'+c.label+'</h5><div class="drop-items"></div></div>'; });
return p + '</div>';
}
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='&#10003; Идеально!'; }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Ошибок: '+wrong+'.'; }
});
}
function wgWrapper(secId, badge, title, help, body){
return '<div class="wg" id="'+secId+'"><div class="wg-header"><span class="wg-badge">'+badge+'</span><div class="wg-title">'+title+'</div></div><div class="wg-help">'+help+'</div>'+body+'</div>';
}
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 = '<div class="sliders">'
+'<label>$m$, кг: <b id="p31w-m">2</b><input type="range" id="p31w-m-r" min="0.5" max="100" step="0.5" value="2"></label>'
+'<label>$v$, м/с: <b id="p31w-v">10</b><input type="range" id="p31w-v-r" min="-30" max="30" step="0.5" value="10"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$p = mv$ = <b id="p31w-p">20</b> кг·м/с</span>'
+'<span style="font-size:.85rem;color:var(--muted)">Аналог: <b id="p31w-cmp">футбольный мяч после удара</b></span>'
+'</div>';
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 = '<div class="sliders">'
+'<label>$m_1$, кг: <b id="p32w-m1">2</b><input type="range" id="p32w-m1-r" min="0.5" max="10" step="0.5" value="2"></label>'
+'<label>$v_1$, м/с: <b id="p32w-v1">5</b><input type="range" id="p32w-v1-r" min="-10" max="10" step="0.5" value="5"></label>'
+'<label>$m_2$, кг: <b id="p32w-m2">3</b><input type="range" id="p32w-m2-r" min="0.5" max="10" step="0.5" value="3"></label>'
+'<label>$v_2$, м/с: <b id="p32w-v2">-2</b><input type="range" id="p32w-v2-r" min="-10" max="10" step="0.5" value="-2"></label>'
+'<label>Тип: <select id="p32w-type" class="tinp" style="width:auto;padding:6px 10px"><option value="abs">неупругий (слиплись)</option><option value="elast">упругий</option></select></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$p_{до} = m_1 v_1 + m_2 v_2$ = <b id="p32w-pdo">4</b> кг·м/с</span>'
+'<span>$v_1\'$ = <b id="p32w-v1p">0.8</b> м/с, $v_2\'$ = <b id="p32w-v2p">0.8</b> м/с</span>'
+'<span>$p_{после}$ = <b id="p32w-ppos">4</b> кг·м/с (сохраняется ✓)</span>'
+'</div>';
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$'}
]) + '<div class="actions"><button class="btn primary dnd-check">Проверить</button></div><div class="feedback dnd-fb"></div>';
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 = '<div class="sliders">'
+'<label>$m$, кг: <b id="p34w-m">2</b><input type="range" id="p34w-m-r" min="0.5" max="20" step="0.5" value="2"></label>'
+'<label>$v$, м/с: <b id="p34w-v">10</b><input type="range" id="p34w-v-r" min="0" max="30" step="0.5" value="10"></label>'
+'<label>$h$, м: <b id="p34w-h">5</b><input type="range" id="p34w-h-r" min="0" max="50" step="0.5" value="5"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$E_k = mv^2/2$ = <b id="p34w-Ek">100</b> Дж</span>'
+'<span>$E_p = mgh$ = <b id="p34w-Ep">98</b> Дж</span>'
+'<span>$E = E_k + E_p$ = <b id="p34w-E">198</b> Дж</span>'
+'</div>';
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 = '<div class="sliders">'
+'<label>$m$, кг: <b id="p35w-m">1</b><input type="range" id="p35w-m-r" min="0.5" max="10" step="0.5" value="1"></label>'
+'<label>$h_{старт}$, м: <b id="p35w-h0">5</b><input type="range" id="p35w-h0-r" min="0.5" max="50" step="0.5" value="5"></label>'
+'<label>$h_{в точке}$, м: <b id="p35w-h">0</b><input type="range" id="p35w-h-r" min="0" max="50" step="0.5" value="0"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$E_0 = mgh_0$ = <b id="p35w-E0">49</b> Дж</span>'
+'<span>$E_{p}^{тек} = mgh$ = <b id="p35w-Ep">0</b> Дж</span>'
+'<span>$E_k = E_0 - E_p$ = <b id="p35w-Ek">49</b> Дж</span>'
+'<span>$v = \\sqrt{2E_k/m}$ = <b id="p35w-v">9.9</b> м/с</span>'
+'</div>';
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 = '<div class="sliders">'
+'<label>Тип: <select id="p36w-t" class="tinp" style="width:auto;padding:6px 10px"><option value="math">математический</option><option value="spr">пружинный</option></select></label>'
+'<label>Длина $l$ (мат.) / $m$ (пруж.): <b id="p36w-x">1</b><input type="range" id="p36w-x-r" min="0.1" max="5" step="0.1" value="1"></label>'
+'<label>$g$ (мат.) / $k$ (пруж.): <b id="p36w-y">9.8</b><input type="range" id="p36w-y-r" min="1" max="100" step="1" value="10"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>Формула: <b id="p36w-formula">$T = 2\\pi\\sqrt{l/g}$</b></span>'
+'<span>$T$ = <b id="p36w-T">2.01</b> с</span>'
+'<span>$\\nu = 1/T$ = <b id="p36w-nu">0.50</b> Гц</span>'
+'</div>';
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 };
})();