diff --git a/frontend/js/phys9_ch5_widgets.js b/frontend/js/phys9_ch5_widgets.js
new file mode 100644
index 0000000..88460be
--- /dev/null
+++ b/frontend/js/phys9_ch5_widgets.js
@@ -0,0 +1,429 @@
+// phys9_ch5_widgets.js — виджеты для Физики 9, Глава 5 (ЛР 1-12).
+(function(){
+'use strict';
+
+function wgWrapper(secId, badge, title, help, body){
+ return '
';
+}
+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;
+}
+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='✓ Работа сдана! +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 = ''
+ +''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'Полный путь $s$ = 3.0 м, время $t$ = 5.0 с'
+ +'$\\langle v\\rangle = s/t$ = 0.60 м/с'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'$a = 2 s_1 / t_1^2$ = 1.0 м/с² (по 1-му)'
+ +'$a = 2 s_2 / t_2^2$ = 1.0 м/с² (по 2-му)'
+ +'Среднее: $a$ = 1.0 м/с²'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'$T$ = $t$/10 = 0.50 с'
+ +'$v$ = $2\\pi R/T$ = 3.77 м/с'
+ +'$a_n$ = $v^2/R$ = 47 м/с²'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'$T$ = $t/10$ = 2.01 с'
+ +'$g = 4\\pi^2 l / T^2$ = 9.77 м/с²'
+ +'Эталон: $9{,}81$ м/с². Погрешность: 0.4%'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'Сила тяжести $P = mg$ = 1.96 Н'
+ +'Проекция вдоль накл.: $F_\\parallel = mg\\sin\\alpha$ = 0.98 Н'
+ +'Прижим: $F_\\perp = mg\\cos\\alpha$ = 1.70 Н'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'$g = 2h/t^2$ = 9.77 м/с²'
+ +'Эталон: $9{,}81$. Погрешность: 0.4%'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'$E_p^{старт} = mgh$ = 0.49 Дж'
+ +'$E_k^{внизу} = mv^2/2$ = 0.45 Дж'
+ +'Сохранение: 8% потерь'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'$F_A = P_1 - P_2$ = 0.50 Н'
+ +'$V = F_A/(\\rho g) = $ 51 см³'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'Доля погружения: 70%'
+ +'ПЛАВАЕТ ↑'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'Для равновесия: $l_2 = F_1 l_1 / F_2$ = 20 см'
+ +'Проверка: $F_1 l_1$ = 60, $F_2 l_2$ = 60 (Н·см)'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'$A_{пол} = mgh$ = 0.59 Дж'
+ +'$A_{зат} = Fl$ = 0.72 Дж'
+ +'$\\eta = A_{пол}/A_{зат}$ = 82%'
+ +'
'
+ +''
+ +'';
+ 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 = ''
+ +''
+ +''
+ +'
'
+ +''
+ +'$T = t/10$ = 0.30 с'
+ +'$k = 4\\pi^2 m / T^2$ = 88 Н/м'
+ +'
'
+ +''
+ +'';
+ 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
+};
+
+})();
diff --git a/frontend/textbooks/physics_9_ch5.html b/frontend/textbooks/physics_9_ch5.html
index d4edd9c..090ccb1 100644
--- a/frontend/textbooks/physics_9_ch5.html
+++ b/frontend/textbooks/physics_9_ch5.html
@@ -17,6 +17,7 @@
+