Files
Learn_System/frontend/js/phys9_ch5_widgets.js
Maxim Dolgolyov 5b075cde86 feat(phys9 finals): прогресс-бары и ачивки финалов Wave F + G
Новый модуль frontend/js/phys9_finals.js:

1. РАСШИРЯЕТ window.checkNum чтобы поддерживать сигнатуру
   (id, answer, unit, tol) — раньше legacy checkNum принимал только
   sec для POOLS, из-за чего кнопки «Проверить» в финалах не работали.

2. ПРОГРЕСС-БАР под заголовком каждого finalN:
   - Подсчитывает количество <input id="fin1-q1"...> в финале
   - При правильном ответе обновляет % решённых
   - +8 XP за каждую решённую задачу

3. АЧИВКИ:
   - При 100% решённых задач финала — +50 XP + бэйдж
     «★ МАСТЕР ГЛАВЫ» (физика9_chN_master)
   - При всех 5 финалах — +150 XP + ачивка «МАГИСТР ФИЗИКИ 9»
     (Wave G — финал курса)

Подключение во все 5 ch + хук на ensureBuilt вызывает
PHYS9_FINALS_INIT(id) для id вида final1..final5.

(linter добавил { delimiters, throwOnError:false } в renderMathInElement
вызовы во всех 5 widget-модулях — сохранено).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:55:44 +03:00

430 lines
25 KiB
JavaScript
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.
// phys9_ch5_widgets.js — виджеты для Физики 9, Глава 5 (ЛР 1-12).
(function(){
'use strict';
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, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){}
return true;
}
function wireSubmit(id){
const btn = document.getElementById(id+'-sub'); 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;
try { if(window.addXp) window.addXp(30, id); } catch(e){}
});
}
/* === ЛР 1. Определение средней скорости === */
function add_lr1(){
const body = '<div class="sliders">'
+'<label>$s_1$ м: <b id="lr1w-s1">1.0</b><input type="range" id="lr1w-s1-r" min="0.2" max="3" step="0.1" value="1"></label>'
+'<label>$t_1$ с: <b id="lr1w-t1">2.0</b><input type="range" id="lr1w-t1-r" min="0.5" max="5" step="0.1" value="2"></label>'
+'<label>$s_2$ м: <b id="lr1w-s2">2.0</b><input type="range" id="lr1w-s2-r" min="0.2" max="5" step="0.1" value="2"></label>'
+'<label>$t_2$ с: <b id="lr1w-t2">3.0</b><input type="range" id="lr1w-t2-r" min="0.5" max="6" step="0.1" value="3"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Полный путь $s$ = <b id="lr1w-s">3.0</b> м, время $t$ = <b id="lr1w-t">5.0</b> с</span>'
+'<span>$\\langle v\\rangle = s/t$ = <b id="lr1w-vv">0.60</b> м/с</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr1-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr1-fb"></div>';
if(appendTo('lr1', wgWrapper('lr1-extra', 'ЛР 1', 'Определение средней скорости', 'Засеки время на 2 участках, рассчитай среднюю скорость.', body))){
const upd = ()=>{
const s1 = +document.getElementById('lr1w-s1-r').value;
const t1 = +document.getElementById('lr1w-t1-r').value;
const s2 = +document.getElementById('lr1w-s2-r').value;
const t2 = +document.getElementById('lr1w-t2-r').value;
document.getElementById('lr1w-s1').textContent = s1.toFixed(2);
document.getElementById('lr1w-t1').textContent = t1.toFixed(2);
document.getElementById('lr1w-s2').textContent = s2.toFixed(2);
document.getElementById('lr1w-t2').textContent = t2.toFixed(2);
const s = s1+s2, t = t1+t2;
document.getElementById('lr1w-s').textContent = s.toFixed(2);
document.getElementById('lr1w-t').textContent = t.toFixed(2);
document.getElementById('lr1w-vv').textContent = (s/t).toFixed(2);
};
['lr1w-s1-r','lr1w-t1-r','lr1w-s2-r','lr1w-t2-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
upd();
wireSubmit('lr1');
}
}
/* === ЛР 2. Изучение равноускоренного движения === */
function add_lr2(){
const body = '<div class="sliders">'
+'<label>Длина 1: $s_1$ м: <b id="lr2w-s1">0.5</b><input type="range" id="lr2w-s1-r" min="0.1" max="2" step="0.05" value="0.5"></label>'
+'<label>$t_1$ с: <b id="lr2w-t1">1.0</b><input type="range" id="lr2w-t1-r" min="0.2" max="3" step="0.05" value="1"></label>'
+'<label>Длина 2: $s_2$ м: <b id="lr2w-s2">2.0</b><input type="range" id="lr2w-s2-r" min="0.5" max="5" step="0.1" value="2"></label>'
+'<label>$t_2$ с: <b id="lr2w-t2">2.0</b><input type="range" id="lr2w-t2-r" min="0.5" max="5" step="0.05" value="2"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$a = 2 s_1 / t_1^2$ = <b id="lr2w-a1">1.0</b> м/с² (по 1-му)</span>'
+'<span>$a = 2 s_2 / t_2^2$ = <b id="lr2w-a2">1.0</b> м/с² (по 2-му)</span>'
+'<span>Среднее: $a$ = <b id="lr2w-a">1.0</b> м/с²</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr2-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr2-fb"></div>';
if(appendTo('lr2', wgWrapper('lr2-extra', 'ЛР 2', 'Равноускоренное движение', 'Тележка скатывается с начала и проходит $s_1$ за $t_1$, потом $s_2$ за $t_2$ от старта. $a = 2s/t^2$.', body))){
const upd = ()=>{
const s1 = +document.getElementById('lr2w-s1-r').value;
const t1 = +document.getElementById('lr2w-t1-r').value;
const s2 = +document.getElementById('lr2w-s2-r').value;
const t2 = +document.getElementById('lr2w-t2-r').value;
document.getElementById('lr2w-s1').textContent = s1.toFixed(2);
document.getElementById('lr2w-t1').textContent = t1.toFixed(2);
document.getElementById('lr2w-s2').textContent = s2.toFixed(2);
document.getElementById('lr2w-t2').textContent = t2.toFixed(2);
const a1 = 2*s1/(t1*t1), a2 = 2*s2/(t2*t2);
document.getElementById('lr2w-a1').textContent = a1.toFixed(2);
document.getElementById('lr2w-a2').textContent = a2.toFixed(2);
document.getElementById('lr2w-a').textContent = ((a1+a2)/2).toFixed(2);
};
['lr2w-s1-r','lr2w-t1-r','lr2w-s2-r','lr2w-t2-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
upd();
wireSubmit('lr2');
}
}
/* === ЛР 3. Движение по окружности === */
function add_lr3(){
const body = '<div class="sliders">'
+'<label>$R$ м: <b id="lr3w-R">0.3</b><input type="range" id="lr3w-R-r" min="0.05" max="2" step="0.05" value="0.3"></label>'
+'<label>10 оборотов за $t$ с: <b id="lr3w-t">5</b><input type="range" id="lr3w-t-r" min="1" max="30" step="0.5" value="5"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$T$ = $t$/10 = <b id="lr3w-T">0.50</b> с</span>'
+'<span>$v$ = $2\\pi R/T$ = <b id="lr3w-v">3.77</b> м/с</span>'
+'<span>$a_n$ = $v^2/R$ = <b id="lr3w-an">47</b> м/с²</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr3-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr3-fb"></div>';
if(appendTo('lr3', wgWrapper('lr3-extra', 'ЛР 3', 'Движение по окружности', 'Измерь время 10 оборотов шарика на нити длиной $R$.', body))){
const upd = ()=>{
const R = +document.getElementById('lr3w-R-r').value;
const t = +document.getElementById('lr3w-t-r').value;
document.getElementById('lr3w-R').textContent = R.toFixed(2);
document.getElementById('lr3w-t').textContent = t.toFixed(2);
const T = t/10;
const v = 2*Math.PI*R/T;
const an = v*v/R;
document.getElementById('lr3w-T').textContent = T.toFixed(2);
document.getElementById('lr3w-v').textContent = v.toFixed(2);
document.getElementById('lr3w-an').textContent = an.toFixed(1);
};
document.getElementById('lr3w-R-r').addEventListener('input', upd);
document.getElementById('lr3w-t-r').addEventListener('input', upd);
upd();
wireSubmit('lr3');
}
}
/* === ЛР 4. Маятник, нахождение g === */
function add_lr4(){
const body = '<div class="sliders">'
+'<label>$l$ нить, м: <b id="lr4w-l">1.0</b><input type="range" id="lr4w-l-r" min="0.1" max="3" step="0.05" value="1"></label>'
+'<label>10 колебаний за $t$ с: <b id="lr4w-t">20.1</b><input type="range" id="lr4w-t-r" min="5" max="40" step="0.1" value="20.1"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$T$ = $t/10$ = <b id="lr4w-T">2.01</b> с</span>'
+'<span>$g = 4\\pi^2 l / T^2$ = <b id="lr4w-g">9.77</b> м/с²</span>'
+'<span>Эталон: $9{,}81$ м/с². Погрешность: <b id="lr4w-err">0.4</b>%</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr4-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr4-fb"></div>';
if(appendTo('lr4', wgWrapper('lr4-extra', 'ЛР 4', 'Определение $g$ маятником', 'Засеки время 10 полных колебаний, рассчитай $g$.', body))){
const upd = ()=>{
const l = +document.getElementById('lr4w-l-r').value;
const t = +document.getElementById('lr4w-t-r').value;
document.getElementById('lr4w-l').textContent = l.toFixed(2);
document.getElementById('lr4w-t').textContent = t.toFixed(1);
const T = t/10;
const g = 4*Math.PI*Math.PI*l/(T*T);
const err = Math.abs(g - 9.81)/9.81*100;
document.getElementById('lr4w-T').textContent = T.toFixed(2);
document.getElementById('lr4w-g').textContent = g.toFixed(2);
document.getElementById('lr4w-err').textContent = err.toFixed(1);
};
document.getElementById('lr4w-l-r').addEventListener('input', upd);
document.getElementById('lr4w-t-r').addEventListener('input', upd);
upd();
wireSubmit('lr4');
}
}
/* === ЛР 5. Сила тяги на наклонной === */
function add_lr5(){
const body = '<div class="sliders">'
+'<label>$m$ груза, кг: <b id="lr5w-m">0.2</b><input type="range" id="lr5w-m-r" min="0.05" max="2" step="0.05" value="0.2"></label>'
+'<label>Угол $\\alpha$, &#176;: <b id="lr5w-a">30</b><input type="range" id="lr5w-a-r" min="10" max="60" step="1" value="30"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Сила тяжести $P = mg$ = <b id="lr5w-P">1.96</b> Н</span>'
+'<span>Проекция вдоль накл.: $F_\\parallel = mg\\sin\\alpha$ = <b id="lr5w-Fp">0.98</b> Н</span>'
+'<span>Прижим: $F_\\perp = mg\\cos\\alpha$ = <b id="lr5w-Fn">1.70</b> Н</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr5-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr5-fb"></div>';
if(appendTo('lr5', wgWrapper('lr5-extra', 'ЛР 5', 'Силы на наклонной плоскости', 'Изменяй угол, наблюдай как меняются $F_\\parallel$ и $F_\\perp$.', body))){
const upd = ()=>{
const m = +document.getElementById('lr5w-m-r').value;
const a = +document.getElementById('lr5w-a-r').value;
document.getElementById('lr5w-m').textContent = m.toFixed(2);
document.getElementById('lr5w-a').textContent = a;
const g = 9.8;
const P = m*g;
const Fp = P*Math.sin(a*Math.PI/180);
const Fn = P*Math.cos(a*Math.PI/180);
document.getElementById('lr5w-P').textContent = P.toFixed(2);
document.getElementById('lr5w-Fp').textContent = Fp.toFixed(2);
document.getElementById('lr5w-Fn').textContent = Fn.toFixed(2);
};
document.getElementById('lr5w-m-r').addEventListener('input', upd);
document.getElementById('lr5w-a-r').addEventListener('input', upd);
upd();
wireSubmit('lr5');
}
}
/* === ЛР 6. Свободное падение g = 2h/t² === */
function add_lr6(){
const body = '<div class="sliders">'
+'<label>$h$ высота, м: <b id="lr6w-h">2.0</b><input type="range" id="lr6w-h-r" min="0.5" max="10" step="0.1" value="2"></label>'
+'<label>$t$ время, с: <b id="lr6w-t">0.64</b><input type="range" id="lr6w-t-r" min="0.2" max="2" step="0.01" value="0.64"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$g = 2h/t^2$ = <b id="lr6w-g">9.77</b> м/с²</span>'
+'<span>Эталон: $9{,}81$. Погрешность: <b id="lr6w-err">0.4</b>%</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr6-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr6-fb"></div>';
if(appendTo('lr6', wgWrapper('lr6-extra', 'ЛР 6', 'Свободное падение', 'Сбрось предмет, засеки время. $g = 2h/t^2$.', body))){
const upd = ()=>{
const h = +document.getElementById('lr6w-h-r').value;
const t = +document.getElementById('lr6w-t-r').value;
document.getElementById('lr6w-h').textContent = h.toFixed(2);
document.getElementById('lr6w-t').textContent = t.toFixed(2);
const g = 2*h/(t*t);
const err = Math.abs(g-9.81)/9.81*100;
document.getElementById('lr6w-g').textContent = g.toFixed(2);
document.getElementById('lr6w-err').textContent = err.toFixed(1);
};
document.getElementById('lr6w-h-r').addEventListener('input', upd);
document.getElementById('lr6w-t-r').addEventListener('input', upd);
upd();
wireSubmit('lr6');
}
}
/* === ЛР 7. Закон сохранения механической энергии === */
function add_lr7(){
const body = '<div class="sliders">'
+'<label>$m$ кг: <b id="lr7w-m">0.1</b><input type="range" id="lr7w-m-r" min="0.05" max="2" step="0.05" value="0.1"></label>'
+'<label>$h$ м: <b id="lr7w-h">0.5</b><input type="range" id="lr7w-h-r" min="0.1" max="3" step="0.05" value="0.5"></label>'
+'<label>Измер. $v_{внизу}$: <b id="lr7w-v">3.0</b><input type="range" id="lr7w-v-r" min="0.5" max="10" step="0.05" value="3"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$E_p^{старт} = mgh$ = <b id="lr7w-Ep">0.49</b> Дж</span>'
+'<span>$E_k^{внизу} = mv^2/2$ = <b id="lr7w-Ek">0.45</b> Дж</span>'
+'<span>Сохранение: <b id="lr7w-cons">8%</b> потерь</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr7-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr7-fb"></div>';
if(appendTo('lr7', wgWrapper('lr7-extra', 'ЛР 7', 'ЗСЭ на горке', 'Сравни $E_p$ вверху и $E_k$ внизу.', body))){
const upd = ()=>{
const m = +document.getElementById('lr7w-m-r').value;
const h = +document.getElementById('lr7w-h-r').value;
const v = +document.getElementById('lr7w-v-r').value;
document.getElementById('lr7w-m').textContent = m.toFixed(2);
document.getElementById('lr7w-h').textContent = h.toFixed(2);
document.getElementById('lr7w-v').textContent = v.toFixed(2);
const Ep = m*9.8*h;
const Ek = m*v*v/2;
document.getElementById('lr7w-Ep').textContent = Ep.toFixed(2);
document.getElementById('lr7w-Ek').textContent = Ek.toFixed(2);
const loss = Math.max(0, (Ep-Ek)/Ep*100);
document.getElementById('lr7w-cons').textContent = loss.toFixed(0)+'% потерь';
};
['lr7w-m-r','lr7w-h-r','lr7w-v-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
upd();
wireSubmit('lr7');
}
}
/* === ЛР 8. Закон Архимеда === */
function add_lr8(){
const body = '<div class="sliders">'
+'<label>Вес в воздухе $P_1$, Н: <b id="lr8w-P1">2.0</b><input type="range" id="lr8w-P1-r" min="0.5" max="10" step="0.1" value="2"></label>'
+'<label>Вес в воде $P_2$, Н: <b id="lr8w-P2">1.5</b><input type="range" id="lr8w-P2-r" min="0.1" max="10" step="0.1" value="1.5"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$F_A = P_1 - P_2$ = <b id="lr8w-Fa">0.50</b> Н</span>'
+'<span>$V = F_A/(\\rho g) = $ <b id="lr8w-V">51</b> см³</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr8-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr8-fb"></div>';
if(appendTo('lr8', wgWrapper('lr8-extra', 'ЛР 8', 'Закон Архимеда', 'Взвешиваем тело в воздухе и в воде, находим $F_A$ и $V$.', body))){
const upd = ()=>{
const P1 = +document.getElementById('lr8w-P1-r').value;
const P2 = +document.getElementById('lr8w-P2-r').value;
document.getElementById('lr8w-P1').textContent = P1.toFixed(2);
document.getElementById('lr8w-P2').textContent = P2.toFixed(2);
const Fa = Math.max(0, P1-P2);
const V_m3 = Fa/(1000*9.8);
document.getElementById('lr8w-Fa').textContent = Fa.toFixed(2);
document.getElementById('lr8w-V').textContent = (V_m3*1e6).toFixed(0);
};
document.getElementById('lr8w-P1-r').addEventListener('input', upd);
document.getElementById('lr8w-P2-r').addEventListener('input', upd);
upd();
wireSubmit('lr8');
}
}
/* === ЛР 9. Плавание тел === */
function add_lr9(){
const body = '<div class="sliders">'
+'<label>$\\rho_{тела}$, кг/м³: <b id="lr9w-rt">700</b><input type="range" id="lr9w-rt-r" min="100" max="15000" step="50" value="700"></label>'
+'<label>$\\rho_{жидк}$, кг/м³: <b id="lr9w-rl">1000</b><input type="range" id="lr9w-rl-r" min="500" max="14000" step="50" value="1000"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Доля погружения: <b id="lr9w-h">70</b>%</span>'
+'<span><b id="lr9w-r" style="color:var(--ok)">ПЛАВАЕТ ↑</b></span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr9-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr9-fb"></div>';
if(appendTo('lr9', wgWrapper('lr9-extra', 'ЛР 9', 'Условие плавания', '$h_{погр}/h_{тела} = \\rho_{тела}/\\rho_{жидк}$.', body))){
const upd = ()=>{
const rt = +document.getElementById('lr9w-rt-r').value;
const rl = +document.getElementById('lr9w-rl-r').value;
document.getElementById('lr9w-rt').textContent = rt;
document.getElementById('lr9w-rl').textContent = rl;
const h = (rt/rl)*100;
document.getElementById('lr9w-h').textContent = Math.min(100, h).toFixed(0);
const r = document.getElementById('lr9w-r');
if(rt < rl){ r.innerHTML='ПЛАВАЕТ ↑'; r.style.color='var(--ok,#10b981)'; }
else if(Math.abs(rt-rl) < 5){ r.innerHTML='ВЗВЕШЕНО →'; r.style.color='var(--muted)'; }
else { r.innerHTML='ТОНЕТ ↓'; r.style.color='var(--fail,#dc2626)'; }
};
document.getElementById('lr9w-rt-r').addEventListener('input', upd);
document.getElementById('lr9w-rl-r').addEventListener('input', upd);
upd();
wireSubmit('lr9');
}
}
/* === ЛР 10. Равновесие рычага === */
function add_lr10(){
const body = '<div class="sliders">'
+'<label>$F_1$ Н: <b id="lr10w-F1">2</b><input type="range" id="lr10w-F1-r" min="0.5" max="20" step="0.5" value="2"></label>'
+'<label>$l_1$ см: <b id="lr10w-l1">30</b><input type="range" id="lr10w-l1-r" min="5" max="100" step="1" value="30"></label>'
+'<label>$F_2$ Н: <b id="lr10w-F2">3</b><input type="range" id="lr10w-F2-r" min="0.5" max="20" step="0.5" value="3"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>Для равновесия: $l_2 = F_1 l_1 / F_2$ = <b id="lr10w-l2">20</b> см</span>'
+'<span>Проверка: $F_1 l_1$ = <b id="lr10w-M1">60</b>, $F_2 l_2$ = <b id="lr10w-M2">60</b> (Н·см)</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr10-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr10-fb"></div>';
if(appendTo('lr10', wgWrapper('lr10-extra', 'ЛР 10', 'Равновесие рычага', 'Подбери $l_2$ так чтобы $F_1 l_1 = F_2 l_2$.', body))){
const upd = ()=>{
const F1 = +document.getElementById('lr10w-F1-r').value;
const l1 = +document.getElementById('lr10w-l1-r').value;
const F2 = +document.getElementById('lr10w-F2-r').value;
document.getElementById('lr10w-F1').textContent = F1;
document.getElementById('lr10w-l1').textContent = l1;
document.getElementById('lr10w-F2').textContent = F2;
const l2 = F1*l1/F2;
document.getElementById('lr10w-l2').textContent = l2.toFixed(1);
document.getElementById('lr10w-M1').textContent = (F1*l1).toFixed(1);
document.getElementById('lr10w-M2').textContent = (F2*l2).toFixed(1);
};
['lr10w-F1-r','lr10w-l1-r','lr10w-F2-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
upd();
wireSubmit('lr10');
}
}
/* === ЛР 11. КПД наклонной === */
function add_lr11(){
const body = '<div class="sliders">'
+'<label>$m$ кг: <b id="lr11w-m">0.2</b><input type="range" id="lr11w-m-r" min="0.05" max="2" step="0.05" value="0.2"></label>'
+'<label>$h$ м: <b id="lr11w-h">0.3</b><input type="range" id="lr11w-h-r" min="0.05" max="1.5" step="0.05" value="0.3"></label>'
+'<label>$l$ м (длина накл.): <b id="lr11w-l">0.6</b><input type="range" id="lr11w-l-r" min="0.1" max="3" step="0.05" value="0.6"></label>'
+'<label>Сила тяги $F$, Н: <b id="lr11w-F">1.2</b><input type="range" id="lr11w-F-r" min="0.05" max="5" step="0.05" value="1.2"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$A_{пол} = mgh$ = <b id="lr11w-Ap">0.59</b> Дж</span>'
+'<span>$A_{зат} = Fl$ = <b id="lr11w-Az">0.72</b> Дж</span>'
+'<span>$\\eta = A_{пол}/A_{зат}$ = <b id="lr11w-eta">82</b>%</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr11-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr11-fb"></div>';
if(appendTo('lr11', wgWrapper('lr11-extra', 'ЛР 11', 'КПД наклонной плоскости', 'Измерь силу тяги динамометром и рассчитай КПД.', body))){
const upd = ()=>{
const m = +document.getElementById('lr11w-m-r').value;
const h = +document.getElementById('lr11w-h-r').value;
const l = +document.getElementById('lr11w-l-r').value;
const F = +document.getElementById('lr11w-F-r').value;
document.getElementById('lr11w-m').textContent = m.toFixed(2);
document.getElementById('lr11w-h').textContent = h.toFixed(2);
document.getElementById('lr11w-l').textContent = l.toFixed(2);
document.getElementById('lr11w-F').textContent = F.toFixed(2);
const Ap = m*9.8*h, Az = F*l;
document.getElementById('lr11w-Ap').textContent = Ap.toFixed(2);
document.getElementById('lr11w-Az').textContent = Az.toFixed(2);
document.getElementById('lr11w-eta').textContent = Az > 0 ? Math.min(100, Math.round(Ap/Az*100)) : 0;
};
['lr11w-m-r','lr11w-h-r','lr11w-l-r','lr11w-F-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
upd();
wireSubmit('lr11');
}
}
/* === ЛР 12. Период пружинного маятника === */
function add_lr12(){
const body = '<div class="sliders">'
+'<label>$m$ груза, кг: <b id="lr12w-m">0.2</b><input type="range" id="lr12w-m-r" min="0.05" max="2" step="0.05" value="0.2"></label>'
+'<label>10 колебаний за $t$ с: <b id="lr12w-t">3.0</b><input type="range" id="lr12w-t-r" min="0.5" max="15" step="0.1" value="3"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$T = t/10$ = <b id="lr12w-T">0.30</b> с</span>'
+'<span>$k = 4\\pi^2 m / T^2$ = <b id="lr12w-k">88</b> Н/м</span>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="lr12-sub">Сдать работу (+30 XP)</button></div>'
+'<div class="feedback" id="lr12-fb"></div>';
if(appendTo('lr12', wgWrapper('lr12-extra', 'ЛР 12', 'Жёсткость пружины', 'Засеки время 10 колебаний и рассчитай $k$.', body))){
const upd = ()=>{
const m = +document.getElementById('lr12w-m-r').value;
const t = +document.getElementById('lr12w-t-r').value;
document.getElementById('lr12w-m').textContent = m.toFixed(2);
document.getElementById('lr12w-t').textContent = t.toFixed(2);
const T = t/10;
const k = 4*Math.PI*Math.PI*m/(T*T);
document.getElementById('lr12w-T').textContent = T.toFixed(2);
document.getElementById('lr12w-k').textContent = k.toFixed(0);
};
document.getElementById('lr12w-m-r').addEventListener('input', upd);
document.getElementById('lr12w-t-r').addEventListener('input', upd);
upd();
wireSubmit('lr12');
}
}
window.PHYS9_CH5_WIDGETS = {
lr1:add_lr1, lr2:add_lr2, lr3:add_lr3, lr4:add_lr4, lr5:add_lr5, lr6:add_lr6,
lr7:add_lr7, lr8:add_lr8, lr9:add_lr9, lr10:add_lr10, lr11:add_lr11, lr12:add_lr12
};
})();