diff --git a/frontend/js/phys9_ch3_widgets.js b/frontend/js/phys9_ch3_widgets.js
new file mode 100644
index 0000000..8c5bd31
--- /dev/null
+++ b/frontend/js/phys9_ch3_widgets.js
@@ -0,0 +1,256 @@
+// phys9_ch3_widgets.js — виджеты для Физики 9, Глава 3 (§25-§30): статика и гидростатика.
+(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 += '
'; });
+ 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 '';
+}
+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;
+}
+
+/* ====== §25 — Равновесие рычага F₁ l₁ = F₂ l₂ ====== */
+function add_p25(){
+ const body = ''
+ +''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +''
+ +'$M_1 = m_1 g l_1$ = 29.4 Н·м'
+ +'$M_2 = m_2 g l_2$ = 29.4 Н·м'
+ +'РАВНОВЕСИЕ ✓'
+ +'
';
+ if(appendTo('p25', wgWrapper('p25-extra', 'CALC+VIS', 'Равновесие рычага', '$F_1 l_1 = F_2 l_2$ — моменты сил равны.', body))){
+ const upd = ()=>{
+ const m1 = +document.getElementById('p25w-m1-r').value;
+ const l1 = +document.getElementById('p25w-l1-r').value;
+ const m2 = +document.getElementById('p25w-m2-r').value;
+ const l2 = +document.getElementById('p25w-l2-r').value;
+ document.getElementById('p25w-m1').textContent = m1;
+ document.getElementById('p25w-l1').textContent = l1.toFixed(1);
+ document.getElementById('p25w-m2').textContent = m2;
+ document.getElementById('p25w-l2').textContent = l2.toFixed(1);
+ const M1 = m1*9.8*l1, M2 = m2*9.8*l2;
+ document.getElementById('p25w-M1').textContent = M1.toFixed(1);
+ document.getElementById('p25w-M2').textContent = M2.toFixed(1);
+ const eq = document.getElementById('p25w-eq');
+ if(Math.abs(M1-M2) < 0.5){ eq.innerHTML = 'РАВНОВЕСИЕ ✓'; eq.style.color = 'var(--ok,#10b981)'; }
+ else if(M1 > M2){ eq.innerHTML = 'ЛЕВАЯ ПЕРЕВЕШИВАЕТ ⤵'; eq.style.color = 'var(--fail,#dc2626)'; }
+ else { eq.innerHTML = 'ПРАВАЯ ПЕРЕВЕШИВАЕТ ⤵'; eq.style.color = 'var(--fail,#dc2626)'; }
+ /* SVG */
+ const col = C();
+ const tilt = Math.max(-15, Math.min(15, (M2-M1)*0.3));
+ const cx = 230, cy = 100;
+ const len = 180;
+ const rad = tilt*PI/180;
+ const lx = cx - len*Math.cos(rad), ly = cy + len*Math.sin(rad);
+ const rx = cx + len*Math.cos(rad), ry = cy - len*Math.sin(rad);
+ let s = '';
+ /* опора */
+ s += '';
+ /* балка */
+ s += '';
+ /* грузы */
+ const r1 = Math.min(20, 5 + m1*2);
+ const r2 = Math.min(20, 5 + m2*2);
+ s += '';
+ s += '';
+ /* подписи */
+ s += ''+m1+' кг';
+ s += ''+m2+' кг';
+ document.getElementById('p25w-svg').innerHTML = s;
+ };
+ ['p25w-m1-r','p25w-l1-r','p25w-m2-r','p25w-l2-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
+ upd();
+ }
+}
+
+/* ====== §26 — Простые механизмы ====== */
+function add_p26(){
+ const items = [
+ {id:'i1', cat:'force', html:'рычаг с длинным плечом'},
+ {id:'i2', cat:'force', html:'наклонная плоскость (длинная)'},
+ {id:'i3', cat:'force', html:'неподвижный блок + полиспаст'},
+ {id:'i4', cat:'force', html:'клин'},
+ {id:'i5', cat:'dist', html:'рычаг с коротким плечом (метла)'},
+ {id:'i6', cat:'dist', html:'педаль велосипеда'},
+ {id:'i7', cat:'none', html:'неподвижный блок (один)'},
+ {id:'i8', cat:'none', html:'жёсткий стержень'}
+ ];
+ const body = dndPool('p26ex', items, [
+ {cat:'force', label:'Выигрыш в силе'},
+ {cat:'dist', label:'Выигрыш в скор./пути'},
+ {cat:'none', label:'Без выигрыша'}
+ ]) + '';
+ if(appendTo('p26', wgWrapper('p26-extra', 'DnD', 'Что даёт выигрыш?', 'Золотое правило: выигрываем в силе — проигрываем в расстоянии. И наоборот.', body))){
+ wireDnd('p26-extra', items);
+ }
+}
+
+/* ====== §27 — КПД наклонной плоскости ====== */
+function add_p27(){
+ const body = ''
+ +''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'$A_{пол} = m g h$ = 98 Дж'
+ +'$A_{зат} = F \\cdot l$ = 131 Дж'
+ +'$\\eta$ = 75%'
+ +'
';
+ if(appendTo('p27', wgWrapper('p27-extra', 'CALC', 'КПД наклонной плоскости', 'Полезная работа = $mgh$. Затраченная = $F \\cdot l$, где $F$ учитывает трение.', body))){
+ const upd = ()=>{
+ const m = +document.getElementById('p27w-m-r').value;
+ const h = +document.getElementById('p27w-h-r').value;
+ const a = +document.getElementById('p27w-an-r').value;
+ const mu = +document.getElementById('p27w-mu-r').value;
+ document.getElementById('p27w-m').textContent = m;
+ document.getElementById('p27w-h').textContent = h.toFixed(1);
+ document.getElementById('p27w-an').textContent = a;
+ document.getElementById('p27w-mu').textContent = mu.toFixed(2);
+ const g = 9.8;
+ const l = h / Math.sin(a*PI/180);
+ const F = m*g*(Math.sin(a*PI/180) + mu*Math.cos(a*PI/180));
+ const Apol = m*g*h;
+ const Azat = F*l;
+ const eta = (Apol/Azat)*100;
+ document.getElementById('p27w-Apol').textContent = Apol.toFixed(0);
+ document.getElementById('p27w-Azat').textContent = Azat.toFixed(0);
+ document.getElementById('p27w-eta').textContent = eta.toFixed(0);
+ };
+ ['p27w-m-r','p27w-h-r','p27w-an-r','p27w-mu-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
+ upd();
+ }
+}
+
+/* ====== §28 — Виды равновесия ====== */
+function add_p28(){
+ const items = [
+ {id:'i1', cat:'st', html:'шар в углублении'},
+ {id:'i2', cat:'st', html:'маятник в нижней точке'},
+ {id:'i3', cat:'st', html:'столб с широким основанием'},
+ {id:'i4', cat:'un', html:'шар на вершине горы'},
+ {id:'i5', cat:'un', html:'карандаш на остром конце'},
+ {id:'i6', cat:'un', html:'пирамида на вершине'},
+ {id:'i7', cat:'in', html:'шар на горизонтальном столе'},
+ {id:'i8', cat:'in', html:'цилиндр на ровной поверхности'}
+ ];
+ const body = dndPool('p28ex', items, [
+ {cat:'st', label:'Устойчивое'},
+ {cat:'un', label:'Неустойчивое'},
+ {cat:'in', label:'Безразличное'}
+ ]) + '';
+ if(appendTo('p28', wgWrapper('p28-extra', 'DnD', 'Вид равновесия', 'Устойчивое — возврат, неустойчивое — уход, безразличное — без изменений.', body))){
+ wireDnd('p28-extra', items);
+ }
+}
+
+/* ====== §29 — Закон Архимеда ====== */
+function add_p29(){
+ const body = ''
+ +''
+ +''
+ +''
+ +'
'
+ +''
+ +'$F_A = \\rho g V$ = 0.98 Н'
+ +'Вес тела $P$ = 0.49 Н'
+ +'ПЛАВАЕТ ↑'
+ +'
';
+ if(appendTo('p29', wgWrapper('p29-extra', 'CALC', 'Закон Архимеда', '$F_A = \\rho g V$. Сравни с весом тела.', body))){
+ const upd = ()=>{
+ const V_cm3 = +document.getElementById('p29w-V-r').value;
+ const V = V_cm3 * 1e-6;
+ const rho_l = +document.getElementById('p29w-liq').value;
+ const rho_t = +document.getElementById('p29w-rt-r').value;
+ document.getElementById('p29w-V').textContent = V_cm3;
+ document.getElementById('p29w-rt').textContent = rho_t;
+ const g = 9.8;
+ const Fa = rho_l*g*V;
+ const P = rho_t*g*V;
+ document.getElementById('p29w-Fa').textContent = Fa.toFixed(2);
+ document.getElementById('p29w-P').textContent = P.toFixed(2);
+ const r = document.getElementById('p29w-result');
+ if(Math.abs(Fa-P) < 0.01){ r.innerHTML = 'ВИСИТ В ТОЛЩЕ →'; r.style.color = 'var(--muted)'; }
+ else if(Fa > P){ r.innerHTML = 'ПЛАВАЕТ ↑'; r.style.color = 'var(--ok,#10b981)'; }
+ else { r.innerHTML = 'ТОНЕТ ↓'; r.style.color = 'var(--fail,#dc2626)'; }
+ };
+ ['p29w-V-r','p29w-liq','p29w-rt-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
+ document.getElementById('p29w-liq').addEventListener('change', upd);
+ upd();
+ }
+}
+
+/* ====== §30 — Плотности жидкостей по возрастанию ====== */
+function add_p30(){
+ const items = [
+ {id:'i1', cat:'r1', html:'спирт ($789$)'},
+ {id:'i2', cat:'r2', html:'керосин ($800$)'},
+ {id:'i3', cat:'r3', html:'вода ($1000$)'},
+ {id:'i4', cat:'r4', html:'морская вода ($1030$)'},
+ {id:'i5', cat:'r5', html:'ртуть ($13600$)'}
+ ];
+ const body = dndPool('p30ex', items, [
+ {cat:'r1', label:'$<800$'},
+ {cat:'r2', label:'$800–999$'},
+ {cat:'r3', label:'$1000–1029$'},
+ {cat:'r4', label:'$1030–1500$'},
+ {cat:'r5', label:'$>10000$'}
+ ]) + '';
+ if(appendTo('p30', wgWrapper('p30-extra', 'DnD', 'Плотности жидкостей (кг/м³)', 'Расставь жидкости по группам плотности.', body))){
+ wireDnd('p30-extra', items);
+ }
+}
+
+window.PHYS9_CH3_WIDGETS = { p25:add_p25, p26:add_p26, p27:add_p27, p28:add_p28, p29:add_p29, p30:add_p30 };
+
+})();
diff --git a/frontend/textbooks/physics_9_ch3.html b/frontend/textbooks/physics_9_ch3.html
index 556788e..97860b0 100644
--- a/frontend/textbooks/physics_9_ch3.html
+++ b/frontend/textbooks/physics_9_ch3.html
@@ -17,6 +17,7 @@
+