Files
Learn_System/frontend/js/phys9_ch2_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

377 lines
22 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_ch2_widgets.js — виджеты для Физики 9, Глава 2 (§15-§24): гравитация, окружн., силы.
(function(){
'use strict';
const C = () => window.PHYS9_COLORS || {};
const PI = Math.PI;
const G = 6.674e-11; /* гравитационная постоянная */
/* ====== Хелперы (дублируются с ch1 — small file) ====== */
function arrow(x1, y1, x2, y2, color, w){
const dx=x2-x1, dy=y2-y1, len=Math.hypot(dx,dy);
if(len<1e-6) return '';
const ux=dx/len, uy=dy/len, h=10, hw=6;
const bx=x2-ux*h, by=y2-uy*h;
const lx=bx-uy*hw, ly=by+ux*hw;
const rx=bx+uy*hw, ry=by-ux*hw;
return '<line x1="'+x1.toFixed(1)+'" y1="'+y1.toFixed(1)+'" x2="'+bx.toFixed(1)+'" y2="'+by.toFixed(1)+'" stroke="'+color+'" stroke-width="'+(w||2.5)+'" stroke-linecap="round"/>'
+ '<polygon points="'+x2.toFixed(1)+','+y2.toFixed(1)+' '+lx.toFixed(1)+','+ly.toFixed(1)+' '+rx.toFixed(1)+','+ry.toFixed(1)+'" fill="'+color+'"/>';
}
function dndPool(secId, items, cats){
let pool='<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=>{
pool += '<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>';
});
pool += '</div>';
let boxes = '<div style="display:grid;grid-template-columns:repeat('+cats.length+',1fr);gap:10px">';
cats.forEach(c=>{
boxes += '<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>';
});
boxes += '</div>';
return pool + boxes;
}
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);
});
});
const checkBtn = scope.querySelector('.dnd-check');
if(checkBtn) checkBtn.addEventListener('click', ()=>{
let wrong = 0, 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, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){}
return true;
}
/* ====== §15 — Гравитация F = G m1 m2 / r² ====== */
function add_p15(){
const body = '<div class="sliders">'
+'<label>$m_1$, кг: <b id="p15w-m1">1e30</b><input type="range" id="p15w-m1-r" min="20" max="32" step="0.5" value="30"></label>'
+'<label>$m_2$, кг: <b id="p15w-m2">1e24</b><input type="range" id="p15w-m2-r" min="18" max="30" step="0.5" value="24"></label>'
+'<label>$r$, м: <b id="p15w-r">1e8</b><input type="range" id="p15w-r-r" min="6" max="12" step="0.5" value="8"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$F = G\\dfrac{m_1 m_2}{r^2}$ = <b id="p15w-F">6.67</b> Н</span>'
+'<span style="font-size:.85rem;color:var(--muted)">Аналог: <b id="p15w-cmp">Земля-Луна</b></span>'
+'</div>';
if(appendTo('p15', wgWrapper('p15-extra', 'CALC', 'Закон всемирного тяготения', '$G = 6{,}67 \\cdot 10^{-11}$ Н·м²/кг². Slider'+"'"+'ы в показателях $10^a$.', body))){
const upd = ()=>{
const m1e = +document.getElementById('p15w-m1-r').value;
const m2e = +document.getElementById('p15w-m2-r').value;
const re = +document.getElementById('p15w-r-r').value;
const m1 = Math.pow(10, m1e); const m2 = Math.pow(10, m2e); const r = Math.pow(10, re);
document.getElementById('p15w-m1').textContent = '10^'+m1e.toFixed(1);
document.getElementById('p15w-m2').textContent = '10^'+m2e.toFixed(1);
document.getElementById('p15w-r').textContent = '10^'+re.toFixed(1);
const F = G * m1 * m2 / (r*r);
document.getElementById('p15w-F').textContent = F.toExponential(2);
let cmp = '';
if(m1e >= 29 && m2e >= 23 && re <= 9) cmp = 'Земля-Луна (~2·10²⁰ Н)';
else if(m1e >= 29 && m2e <= 21) cmp = 'Земля-человек (~700 Н)';
else if(m1e >= 30) cmp = 'Солнце-планета';
else cmp = '—';
document.getElementById('p15w-cmp').textContent = cmp;
};
['p15w-m1-r','p15w-m2-r','p15w-r-r'].forEach(id=>document.getElementById(id).addEventListener('input', upd));
upd();
}
}
/* ====== §16 — Кеплер: T² = a³ (в годах и а.е.) ====== */
function add_p16(){
const items = [
{id:'i1', cat:'fast', html:'Меркурий ($T = 0{,}24$ г)'},
{id:'i2', cat:'fast', html:'Венера ($T = 0{,}62$ г)'},
{id:'i3', cat:'med', html:'Земля ($T = 1$ г)'},
{id:'i4', cat:'med', html:'Марс ($T = 1{,}88$ г)'},
{id:'i5', cat:'slow', html:'Юпитер ($T = 11{,}9$ г)'},
{id:'i6', cat:'slow', html:'Сатурн ($T = 29{,}5$ г)'}
];
const body = dndPool('p16ex', items, [
{cat:'fast', label:'$T < 1$ г'},
{cat:'med', label:'$1 \\le T < 5$ г'},
{cat:'slow', label:'$T \\ge 5$ г'}
]) + '<div class="actions"><button class="btn primary dnd-check">Проверить</button></div>'
+ '<div class="feedback dnd-fb"></div>';
if(appendTo('p16', wgWrapper('p16-extra', 'DnD', 'Планеты по периоду', 'III закон Кеплера: чем дальше от Солнца, тем больше период.', body))){
wireDnd('p16-extra', items);
}
}
/* ====== §17 — Период, частота, угловая скорость ====== */
function add_p17(){
const body = '<div class="sliders">'
+'<label>Известно: <select id="p17w-mode" class="tinp" style="width:auto;padding:6px 10px"><option value="T">период T</option><option value="nu">частота ν</option><option value="om">угл. скор. ω</option></select></label>'
+'<label>Значение: <b id="p17w-vv">2.0</b><input type="range" id="p17w-v-r" min="0.05" max="10" step="0.05" value="2"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$T$ = <b id="p17w-T">2.0</b> с</span>'
+'<span>$\\nu = 1/T$ = <b id="p17w-nu">0.50</b> Гц</span>'
+'<span>$\\omega = 2\\pi/T$ = <b id="p17w-om">3.14</b> рад/с</span>'
+'</div>';
if(appendTo('p17', wgWrapper('p17-extra', 'CALC', 'Связь $T$, $\\nu$, $\\omega$', '$\\nu = 1/T$, $\\omega = 2\\pi\\nu = 2\\pi/T$.', body))){
const upd = ()=>{
const mode = document.getElementById('p17w-mode').value;
const v = +document.getElementById('p17w-v-r').value;
document.getElementById('p17w-vv').textContent = v.toFixed(2);
let T, nu, om;
if(mode === 'T'){ T = v; nu = 1/T; om = 2*PI/T; }
else if(mode === 'nu'){ nu = v; T = 1/nu; om = 2*PI*nu; }
else { om = v; T = 2*PI/om; nu = 1/T; }
document.getElementById('p17w-T').textContent = T.toFixed(2);
document.getElementById('p17w-nu').textContent = nu.toFixed(2);
document.getElementById('p17w-om').textContent = om.toFixed(2);
};
document.getElementById('p17w-mode').addEventListener('change', upd);
document.getElementById('p17w-v-r').addEventListener('input', upd);
upd();
}
}
/* ====== §18 — Центростремительное ускорение ====== */
function add_p18(){
const body = '<div class="sliders">'
+'<label>$v$, м/с: <b id="p18w-v">10</b><input type="range" id="p18w-v-r" min="1" max="50" step="1" value="10"></label>'
+'<label>$R$, м: <b id="p18w-R">5</b><input type="range" id="p18w-R-r" min="0.5" max="50" step="0.5" value="5"></label>'
+'</div>'
+'<svg id="p18w-svg" viewBox="0 0 360 240" style="width:100%;height:auto;background:var(--bg-subtle,#f8fafc);border:1px solid var(--border);border-radius:9px"></svg>'
+'<div class="score-display"><span>$a_n = v^2/R$ = <b id="p18w-an">20</b> м/с²</span><span>(<b id="p18w-g">2.0</b> g)</span></div>';
if(appendTo('p18', wgWrapper('p18-extra', 'CALC+VIS', '$a_n = v^2/R$', '$\\vec a_n$ всегда направлено к центру окружности.', body))){
const cx = 180, cy = 120, R0 = 60;
const upd = ()=>{
const v = +document.getElementById('p18w-v-r').value;
const R = +document.getElementById('p18w-R-r').value;
document.getElementById('p18w-v').textContent = v;
document.getElementById('p18w-R').textContent = R;
const an = v*v/R;
document.getElementById('p18w-an').textContent = an.toFixed(1);
document.getElementById('p18w-g').textContent = (an/9.8).toFixed(2);
const col = C();
const ang = (Date.now()/1000) % (2*PI);
const tipX = cx + R0*Math.cos(ang);
const tipY = cy + R0*Math.sin(ang);
let s = '';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R0+'" fill="none" stroke="'+(col.grid||'#e2e8f0')+'" stroke-width="1.5" stroke-dasharray="3 3"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="3" fill="'+(col.text||'#0f172a')+'"/>';
s += '<circle cx="'+tipX.toFixed(1)+'" cy="'+tipY.toFixed(1)+'" r="8" fill="'+(col.body||'#475569')+'"/>';
/* v касательно (90° от радиуса) */
const vx = -Math.sin(ang), vy = Math.cos(ang);
s += arrow(tipX, tipY, tipX + vx*40, tipY + vy*40, col.velocity||'#0891b2', 2.5);
/* a_n к центру */
s += arrow(tipX, tipY, cx, cy, col.acceleration||'#ea580c', 2.5);
s += '<text x="'+(tipX+vx*44).toFixed(1)+'" y="'+(tipY+vy*44).toFixed(1)+'" font-size="13" font-weight="800" fill="'+(col.velocity||'#0891b2')+'">v</text>';
s += '<text x="'+((tipX+cx)/2-12)+'" y="'+((tipY+cy)/2-6)+'" font-size="13" font-weight="800" fill="'+(col.acceleration||'#ea580c')+'">a_n</text>';
document.getElementById('p18w-svg').innerHTML = s;
};
document.getElementById('p18w-v-r').addEventListener('input', upd);
document.getElementById('p18w-R-r').addEventListener('input', upd);
upd();
setInterval(upd, 80);
}
}
/* ====== §19 — Закон Гука F = kx ====== */
function add_p19(){
const body = '<div class="sliders">'
+'<label>$k$, Н/м: <b id="p19w-k">200</b><input type="range" id="p19w-k-r" min="20" max="2000" step="20" value="200"></label>'
+'<label>$x$, м: <b id="p19w-x">0.05</b><input type="range" id="p19w-x-r" min="0.005" max="0.5" step="0.005" value="0.05"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:4px">'
+'<span>$F = kx$ = <b id="p19w-F">10</b> Н</span>'
+'<span style="font-size:.85rem;color:var(--muted)">это вес тела массой <b id="p19w-m">1.02</b> кг</span>'
+'</div>';
if(appendTo('p19', wgWrapper('p19-extra', 'CALC', 'Закон Гука', 'Сила упругости пропорциональна растяжению/сжатию.', body))){
const upd = ()=>{
const k = +document.getElementById('p19w-k-r').value;
const x = +document.getElementById('p19w-x-r').value;
document.getElementById('p19w-k').textContent = k;
document.getElementById('p19w-x').textContent = x.toFixed(3);
const F = k*x;
document.getElementById('p19w-F').textContent = F.toFixed(2);
document.getElementById('p19w-m').textContent = (F/9.8).toFixed(2);
};
document.getElementById('p19w-k-r').addEventListener('input', upd);
document.getElementById('p19w-x-r').addEventListener('input', upd);
upd();
}
}
/* ====== §20 — Трение μ ====== */
function add_p20(){
const items = [
{id:'i1', cat:'h', html:'резина по сухому асфальту ($\\mu \\sim 0{,}7$)'},
{id:'i2', cat:'h', html:'дерево по дереву ($\\mu \\sim 0{,}5$)'},
{id:'i3', cat:'h', html:'кирпич по кирпичу'},
{id:'i4', cat:'m', html:'сталь по стали ($\\mu \\sim 0{,}2$)'},
{id:'i5', cat:'m', html:'паркет под обувью'},
{id:'i6', cat:'l', html:'лёд по льду ($\\mu \\sim 0{,}03$)'},
{id:'i7', cat:'l', html:'тефлон по тефлону ($\\mu \\sim 0{,}04$)'},
{id:'i8', cat:'l', html:'шина по льду'}
];
const body = dndPool('p20ex', items, [
{cat:'h', label:'Большое трение'},
{cat:'m', label:'Среднее'},
{cat:'l', label:'Малое'}
]) + '<div class="actions"><button class="btn primary dnd-check">Проверить</button></div>'
+ '<div class="feedback dnd-fb"></div>';
if(appendTo('p20', wgWrapper('p20-extra', 'DnD', 'Коэффициент трения', 'Резина-асфальт ~0,7; сталь-сталь ~0,2; лёд-лёд ~0,03.', body))){
wireDnd('p20-extra', items);
}
}
/* ====== §21 — Инерц / неинерц СО ====== */
function add_p21(){
const items = [
{id:'i1', cat:'i', html:'покоящаяся комната'},
{id:'i2', cat:'i', html:'автомобиль с $v = $ const на прямой'},
{id:'i3', cat:'i', html:'самолёт в горизонтальном полёте'},
{id:'i4', cat:'i', html:'космич. корабль с двигателями off'},
{id:'i5', cat:'n', html:'разгоняющийся автобус'},
{id:'i6', cat:'n', html:'тормозящий поезд'},
{id:'i7', cat:'n', html:'карусель'},
{id:'i8', cat:'n', html:'центрифуга'}
];
const body = dndPool('p21ex', items, [
{cat:'i', label:'Инерц. СО'},
{cat:'n', label:'Неинерц. СО'}
]) + '<div class="actions"><button class="btn primary dnd-check">Проверить</button></div>'
+ '<div class="feedback dnd-fb"></div>';
if(appendTo('p21', wgWrapper('p21-extra', 'DnD', 'Инерциальная или нет?', 'Инерциальная — где $\\vec a = 0$ (тело покоится или движется равномерно прямолинейно).', body))){
wireDnd('p21-extra', items);
}
}
/* ====== §22 — F = ma ====== */
function add_p22(){
const body = '<div class="sliders">'
+'<label>$F$, Н: <b id="p22w-F">50</b><input type="range" id="p22w-F-r" min="5" max="500" step="5" value="50"></label>'
+'<label>$m$, кг: <b id="p22w-m">10</b><input type="range" id="p22w-m-r" min="0.5" max="100" step="0.5" value="10"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$a = F/m$ = <b id="p22w-a">5.00</b> м/с²</span>'
+'<span>За 1 с скорость возрастёт на <b id="p22w-dv">5.0</b> м/с</span>'
+'</div>';
if(appendTo('p22', wgWrapper('p22-extra', 'CALC', '2-й закон Ньютона', 'Удвой силу — удвоится ускорение. Удвой массу — ускорение упадёт в 2 раза.', body))){
const upd = ()=>{
const F = +document.getElementById('p22w-F-r').value;
const m = +document.getElementById('p22w-m-r').value;
document.getElementById('p22w-F').textContent = F;
document.getElementById('p22w-m').textContent = m;
const a = F/m;
document.getElementById('p22w-a').textContent = a.toFixed(2);
document.getElementById('p22w-dv').textContent = a.toFixed(2);
};
document.getElementById('p22w-F-r').addEventListener('input', upd);
document.getElementById('p22w-m-r').addEventListener('input', upd);
upd();
}
}
/* ====== §23 — g на разных высотах ====== */
function add_p23(){
const body = '<div class="sliders">'
+'<label>Высота $h$, км: <b id="p23w-h">0</b><input type="range" id="p23w-h-r" min="0" max="50000" step="100" value="0"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>$g(h) = GM/(R+h)^2$ = <b id="p23w-g">9.80</b> м/с²</span>'
+'<span style="font-size:.85rem;color:var(--muted)">место: <b id="p23w-loc">поверхность Земли</b></span>'
+'</div>';
if(appendTo('p23', wgWrapper('p23-extra', 'CALC', '$g$ на разных высотах', '$g$ уменьшается с высотой как $1/(R+h)^2$.', body))){
const upd = ()=>{
const h_km = +document.getElementById('p23w-h-r').value;
const h = h_km * 1000;
const R = 6.371e6, M = 5.972e24;
const g = G * M / Math.pow(R+h, 2);
document.getElementById('p23w-h').textContent = h_km;
document.getElementById('p23w-g').textContent = g.toFixed(2);
let loc = 'поверхность Земли';
if(h_km > 30000) loc = 'геостационарная орбита (~36000 км)';
else if(h_km > 8000) loc = 'дальняя орбита';
else if(h_km > 1000) loc = 'высокая орбита';
else if(h_km > 400) loc = 'МКС орбита (~408 км)';
else if(h_km > 100) loc = 'низкая орбита';
else if(h_km > 10) loc = 'стратосфера';
else if(h_km > 0) loc = 'тропосфера';
document.getElementById('p23w-loc').textContent = loc;
};
document.getElementById('p23w-h-r').addEventListener('input', upd);
upd();
}
}
/* ====== §24 — Вес в лифте ====== */
function add_p24(){
const body = '<div class="sliders">'
+'<label>$m$, кг: <b id="p24w-m">70</b><input type="range" id="p24w-m-r" min="10" max="150" step="1" value="70"></label>'
+'<label>$a$ лифта, м/с² (вверх+): <b id="p24w-a">0</b><input type="range" id="p24w-a-r" min="-20" max="20" step="0.5" value="0"></label>'
+'</div>'
+'<div class="score-display" style="flex-direction:column;align-items:flex-start;gap:5px">'
+'<span>Вес $P = m(g + a)$ = <b id="p24w-P">686</b> Н</span>'
+'<span style="font-size:.85rem"><b id="p24w-mode">НОРМАЛЬНЫЙ ВЕС</b></span>'
+'</div>';
if(appendTo('p24', wgWrapper('p24-extra', 'CALC', 'Вес в лифте', '$a > 0$ — разгон вверх (перегрузка). $a < 0$ — свободное падение (невесомость при $a = -g$).', body))){
const upd = ()=>{
const m = +document.getElementById('p24w-m-r').value;
const a = +document.getElementById('p24w-a-r').value;
document.getElementById('p24w-m').textContent = m;
document.getElementById('p24w-a').textContent = a;
const g = 9.8;
const P = m*(g+a);
document.getElementById('p24w-P').textContent = P.toFixed(0);
const mode = document.getElementById('p24w-mode');
if(Math.abs(P) < 5){ mode.textContent = 'НЕВЕСОМОСТЬ'; mode.style.color = 'var(--warn,#f59e0b)'; }
else if(P < 0){ mode.textContent = 'ОТРИЦАТЕЛЬНЫЙ ВЕС (пол давит вниз)'; mode.style.color = 'var(--fail,#dc2626)'; }
else if(P > m*g*1.5){ mode.textContent = 'ПЕРЕГРУЗКА '+(P/(m*g)).toFixed(1)+'g'; mode.style.color = 'var(--fail,#dc2626)'; }
else if(P > m*g*1.05){ mode.textContent = 'РАЗГОН ВВЕРХ ('+(P/(m*g)).toFixed(2)+'g)'; mode.style.color = 'var(--warn,#f59e0b)'; }
else if(P < m*g*0.95){ mode.textContent = 'РАЗГОН ВНИЗ ('+(P/(m*g)).toFixed(2)+'g)'; mode.style.color = 'var(--warn,#f59e0b)'; }
else { mode.textContent = 'НОРМАЛЬНЫЙ ВЕС'; mode.style.color = 'var(--ok,#10b981)'; }
};
document.getElementById('p24w-m-r').addEventListener('input', upd);
document.getElementById('p24w-a-r').addEventListener('input', upd);
upd();
}
}
window.PHYS9_CH2_WIDGETS = {
p15:add_p15, p16:add_p16, p17:add_p17, p18:add_p18, p19:add_p19,
p20:add_p20, p21:add_p21, p22:add_p22, p23:add_p23, p24:add_p24
};
})();