diff --git a/frontend/js/flagships/phys9_flag_F5_atwood.js b/frontend/js/flagships/phys9_flag_F5_atwood.js new file mode 100644 index 0000000..04c1dca --- /dev/null +++ b/frontend/js/flagships/phys9_flag_F5_atwood.js @@ -0,0 +1,182 @@ +// F5. Машина Атвуда (§22 в ch2) — связанные массы через блок. +(function(){ +'use strict'; +const B = () => window.PHYS9_FLAG_BASE; +const C = () => window.PHYS9_COLORS || {}; + +function init(secId){ + if (!B()) return false; + const body = '' + + '
' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '' + + '
' + + '
' + + '
$a = (m_1-m_2)g/(m_1+m_2)$1.96 м/с²
' + + '
Натяжение T23.5 Н
' + + '
$v$ текущая0 м/с
' + + '
$t$ прошло0 с
' + + '
'; + + const card = B().makeCard(secId, + 'F5. Машина Атвуда', + 'Две массы через блок на нити. Бо́льшая опускается, меньшая поднимается. Физика: $a = (m_1-m_2)g/(m_1+m_2)$.', + body); + if (!card) return false; + + const cv = document.getElementById('F5-cv'); + const ctx = cv.getContext('2d'); + const W = cv.width, H = cv.height; + const blockX = W/2, blockY = 60, blockR = 28; + + let st = { y1: 220, y2: 220, v: 0, t: 0, running: false }; + /* y — расстояние от блока вниз, +v = m1 опускается */ + + function readSliders(){ + const m1 = +document.getElementById('F5-m1').value; + const m2 = +document.getElementById('F5-m2').value; + const fr = +document.getElementById('F5-fr').value; + document.getElementById('F5-m1v').textContent = m1.toFixed(2); + document.getElementById('F5-m2v').textContent = m2.toFixed(2); + document.getElementById('F5-frv').textContent = fr.toFixed(2); + const g = 9.8; + const a = ((m1 - m2)*g - fr) / (m1 + m2); + const T = m2 * (g + a); + document.getElementById('F5-a').textContent = a.toFixed(2) + ' м/с²'; + document.getElementById('F5-T').textContent = T.toFixed(1) + ' Н'; + } + + function reset(){ + st = { y1: 220, y2: 220, v: 0, t: 0, running: false }; + document.getElementById('F5-go').textContent = 'Запустить'; + document.getElementById('F5-v').textContent = '0 м/с'; + document.getElementById('F5-t').textContent = '0 с'; + draw(); + } + + function tick(dt){ + if (!st.running) { draw(); return; } + const m1 = +document.getElementById('F5-m1').value; + const m2 = +document.getElementById('F5-m2').value; + const fr = +document.getElementById('F5-fr').value; + const g = 9.8; + const a = ((m1 - m2)*g - fr*Math.sign(st.v || (m1-m2))) / (m1 + m2); + st.v += a * dt; + /* движение: m1 опускается со скоростью v, m2 поднимается */ + const dy = st.v * dt * 30; /* масштаб 30 px/м */ + st.y1 += dy; + st.y2 -= dy; + /* ограничения */ + if (st.y1 > H - 80){ st.y1 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; } + if (st.y2 < 100){ st.y2 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; } + if (st.y1 < 100){ st.y1 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; } + if (st.y2 > H - 80){ st.y2 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; } + st.t += dt; + document.getElementById('F5-v').textContent = st.v.toFixed(2) + ' м/с'; + document.getElementById('F5-t').textContent = st.t.toFixed(1) + ' с'; + draw(); + } + + function draw(){ + const col = C(); + ctx.fillStyle = col.bg || '#fafafa'; + ctx.fillRect(0, 0, W, H); + /* потолок */ + ctx.fillStyle = col.surface || '#a16207'; + ctx.fillRect(0, 0, W, 30); + for (let x = 0; x < W; x += 12){ + ctx.strokeStyle = '#7c4a08'; + ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x+6, 6); ctx.stroke(); + } + /* кронштейн к блоку */ + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 4; + ctx.beginPath(); ctx.moveTo(blockX, 30); ctx.lineTo(blockX, blockY - blockR); ctx.stroke(); + /* блок */ + ctx.fillStyle = col.bodyLight || '#cbd5e1'; + ctx.beginPath(); ctx.arc(blockX, blockY, blockR, 0, Math.PI*2); ctx.fill(); + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 2.5; + ctx.stroke(); + /* радиальные линии (вращение) */ + const rot = st.v * 0.1; + for (let i = 0; i < 4; i++){ + const a = rot + i*Math.PI/2; + ctx.beginPath(); + ctx.moveTo(blockX + (blockR-5)*Math.cos(a), blockY + (blockR-5)*Math.sin(a)); + ctx.lineTo(blockX + 5*Math.cos(a), blockY + 5*Math.sin(a)); + ctx.stroke(); + } + /* нити */ + const cordL = blockX - blockR; + const cordR = blockX + blockR; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(cordL, blockY); ctx.lineTo(cordL, st.y1); + ctx.moveTo(cordR, blockY); ctx.lineTo(cordR, st.y2); + ctx.stroke(); + /* грузы */ + const m1 = +document.getElementById('F5-m1').value; + const m2 = +document.getElementById('F5-m2').value; + const r1 = Math.min(40, 12 + m1*3); + const r2 = Math.min(40, 12 + m2*3); + ctx.fillStyle = col.forceGravity || '#2563eb'; + ctx.fillRect(cordL - r1, st.y1, r1*2, r1); + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 1.5; + ctx.strokeRect(cordL - r1, st.y1, r1*2, r1); + ctx.fillStyle = col.fail || '#dc2626'; + ctx.fillRect(cordR - r2, st.y2, r2*2, r2); + ctx.strokeRect(cordR - r2, st.y2, r2*2, r2); + ctx.fillStyle = '#fff'; + ctx.font = 'bold 12px Inter,sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('m₁ = '+m1.toFixed(1)+' кг', cordL, st.y1 + r1/2 + 4); + ctx.fillText('m₂ = '+m2.toFixed(1)+' кг', cordR, st.y2 + r2/2 + 4); + ctx.textAlign = 'left'; + /* векторы силы тяжести */ + if (st.t < 0.5 || !st.running){ + B().arrow(ctx, cordL, st.y1 + r1, cordL, st.y1 + r1 + 40, col.forceGravity || '#2563eb', 2); + ctx.fillStyle = col.forceGravity || '#2563eb'; + ctx.font = 'bold 12px Inter,sans-serif'; + ctx.fillText('m₁g', cordL + 8, st.y1 + r1 + 30); + B().arrow(ctx, cordR, st.y2 + r2, cordR, st.y2 + r2 + 40, col.forceGravity || '#2563eb', 2); + ctx.fillText('m₂g', cordR + 8, st.y2 + r2 + 30); + /* T снизу */ + B().arrow(ctx, cordL, st.y1, cordL, st.y1 - 30, col.forceTension || '#16a34a', 2); + ctx.fillStyle = col.forceTension || '#16a34a'; + ctx.fillText('T', cordL + 8, st.y1 - 20); + B().arrow(ctx, cordR, st.y2, cordR, st.y2 - 30, col.forceTension || '#16a34a', 2); + ctx.fillText('T', cordR + 8, st.y2 - 20); + } + } + + document.getElementById('F5-go').addEventListener('click', ()=>{ + if (st.y1 >= H - 80 || st.y2 >= H - 80 || st.y1 < 110 || st.y2 < 110) reset(); + st.running = !st.running; + document.getElementById('F5-go').textContent = st.running ? 'Пауза' : 'Запустить'; + }); + document.getElementById('F5-reset').addEventListener('click', reset); + ['F5-m1','F5-m2','F5-fr'].forEach(id => document.getElementById(id).addEventListener('input', ()=>{ + readSliders(); + if (!st.running) reset(); + })); + + readSliders(); + draw(); + B().startLoop('F5', cv, tick); + return true; +} + +if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} }); +else document.addEventListener('DOMContentLoaded', ()=>{ + if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} }); +}); + +})(); diff --git a/frontend/js/flagships/phys9_flag_F7_elevator.js b/frontend/js/flagships/phys9_flag_F7_elevator.js new file mode 100644 index 0000000..7310c1b --- /dev/null +++ b/frontend/js/flagships/phys9_flag_F7_elevator.js @@ -0,0 +1,236 @@ +// F7. Лифт с динамометром (§24 в ch2) — переживание перегрузки и невесомости. +(function(){ +'use strict'; +const B = () => window.PHYS9_FLAG_BASE; +const C = () => window.PHYS9_COLORS || {}; + +function init(secId){ + if (!B()) return false; + const body = '' + + '
' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
Вес динамометра9.8 Н
' + + '
Отношение P/mg1.0 g
' + + '
$v$ лифта0 м/с
' + + '
$h$ высота0 м
' + + '
' + + '
'; + + const card = B().makeCard(secId, + 'F7. Лифт с динамометром', + 'Управляй лифтом — наблюдай, как меняется показание динамометра. $P = m(g + a)$. При $a = -g$ — невесомость.', + body); + if (!card) return false; + + const cv = document.getElementById('F7-cv'); + const ctx = cv.getContext('2d'); + const W = cv.width, H = cv.height; + + let st = { y: 0, v: 0, a: 0, t: 0, mode: 'idle' }; + + function readSliders(){ + document.getElementById('F7-mv').textContent = (+document.getElementById('F7-m').value).toFixed(2); + document.getElementById('F7-av').textContent = (+document.getElementById('F7-a').value).toFixed(1); + } + + function reset(){ + st = { y: 0, v: 0, a: 0, t: 0, mode: 'idle' }; + document.getElementById('F7-fb').className = 'flag-feedback'; + draw(); + } + + function tick(dt){ + const m = +document.getElementById('F7-m').value; + const a_in = +document.getElementById('F7-a').value; + const g = 9.8; + /* Управление режимом */ + if (st.mode === 'up') st.a = a_in; + else if (st.mode === 'dn') st.a = -a_in; + else if (st.mode === 'stop') st.a = 0; + else if (st.mode === 'fall') st.a = -g; + else st.a = 0; + st.v += st.a * dt; + st.y += st.v * dt; + /* ограничения */ + if (st.y < 0){ st.y = 0; if (st.v < 0) st.v = 0; } + if (st.y > 50){ st.y = 50; if (st.v > 0) st.v = 0; } + st.t += dt; + /* Вес */ + const P = m * (g + st.a); + const ratio = P/(m*g); + document.getElementById('F7-P').textContent = P.toFixed(2) + ' Н'; + document.getElementById('F7-pmg').textContent = ratio.toFixed(2) + ' g'; + document.getElementById('F7-v').textContent = st.v.toFixed(2) + ' м/с'; + document.getElementById('F7-h').textContent = st.y.toFixed(1) + ' м'; + const fb = document.getElementById('F7-fb'); + if (Math.abs(P) < m*g*0.05){ + fb.className = 'flag-feedback warn show'; + fb.innerHTML = '⚠ НЕВЕСОМОСТЬ! Динамометр показывает почти 0.'; + } else if (P < 0){ + fb.className = 'flag-feedback fail show'; + fb.innerHTML = 'P < 0: предмет «отрывается» от пола (или сильно прижимается к потолку).'; + } else if (ratio > 1.5){ + fb.className = 'flag-feedback warn show'; + fb.innerHTML = 'ПЕРЕГРУЗКА '+ratio.toFixed(1)+'g — космонавты тренируются на таких.'; + } else if (Math.abs(ratio - 1) < 0.05){ + fb.className = 'flag-feedback ok show'; + fb.innerHTML = 'Нормальный вес — $a = 0$ или равномерное движение.'; + } else { + fb.className = 'flag-feedback'; + } + draw(); + } + + function draw(){ + const col = C(); + ctx.fillStyle = col.bg || '#fafafa'; + ctx.fillRect(0, 0, W, H); + /* Шахта */ + const shaftX = 80, shaftY = 30, shaftW = 280, shaftH = H - 60; + ctx.fillStyle = col.bgSubtle || '#f8fafc'; + ctx.fillRect(shaftX, shaftY, shaftW, shaftH); + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 2; + ctx.strokeRect(shaftX, shaftY, shaftW, shaftH); + /* Этажи */ + ctx.strokeStyle = col.grid || '#e5e7eb'; + ctx.lineWidth = 1; + for (let i = 0; i <= 5; i++){ + const py = shaftY + (i/5) * shaftH; + ctx.beginPath(); ctx.moveTo(shaftX, py); ctx.lineTo(shaftX + shaftW, py); ctx.stroke(); + ctx.fillStyle = col.textMuted || '#64748b'; + ctx.font = '11px Inter,sans-serif'; + ctx.fillText((5-i)+' эт', shaftX + 6, py + 12); + } + /* Лифт (кабина) */ + const yNorm = st.y / 50; /* 0..1 */ + const liftY = shaftY + shaftH - 100 - yNorm * (shaftH - 120); + ctx.fillStyle = col.bodyLight || '#cbd5e1'; + ctx.fillRect(shaftX + 50, liftY, shaftW - 100, 90); + ctx.strokeStyle = col.bodyAccent || '#1e293b'; + ctx.lineWidth = 2; + ctx.strokeRect(shaftX + 50, liftY, shaftW - 100, 90); + /* Трос */ + ctx.strokeStyle = st.mode === 'fall' ? col.fail : col.axis || '#1e293b'; + ctx.lineWidth = st.mode === 'fall' ? 1 : 2.5; + if (st.mode === 'fall'){ + ctx.setLineDash([4, 4]); + } + ctx.beginPath(); + ctx.moveTo(shaftX + shaftW/2, shaftY); + ctx.lineTo(shaftX + shaftW/2, liftY); + ctx.stroke(); + ctx.setLineDash([]); + /* Динамометр внутри лифта */ + const dynaX = shaftX + shaftW/2, dynaY = liftY + 20; + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 1.5; + ctx.fillStyle = col.bg || '#fafafa'; + ctx.fillRect(dynaX - 12, dynaY, 24, 22); + ctx.strokeRect(dynaX - 12, dynaY, 24, 22); + /* стрелка */ + const m = +document.getElementById('F7-m').value; + const g = 9.8; + const P = m*(g + st.a); + const Pmax = m*g*2.5; + const norm = Math.max(0, Math.min(1, P/Pmax)); + const angle = -Math.PI*0.75 + norm * Math.PI*1.5; + ctx.strokeStyle = col.fail || '#dc2626'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(dynaX, dynaY + 11); + ctx.lineTo(dynaX + 9*Math.cos(angle), dynaY + 11 + 9*Math.sin(angle)); + ctx.stroke(); + /* Груз ниже динамометра */ + ctx.fillStyle = col.forceGravity || '#2563eb'; + const gSize = Math.min(30, 10 + m*3); + ctx.fillRect(dynaX - gSize/2, dynaY + 30, gSize, gSize); + ctx.strokeStyle = col.bodyAccent || '#1e293b'; + ctx.strokeRect(dynaX - gSize/2, dynaY + 30, gSize, gSize); + ctx.fillStyle = '#fff'; + ctx.font = 'bold 10px Inter,sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(m.toFixed(1)+' кг', dynaX, dynaY + 30 + gSize/2 + 3); + ctx.textAlign = 'left'; + /* подпись режима */ + ctx.fillStyle = col.text || '#0f172a'; + ctx.font = 'bold 13px Inter,sans-serif'; + const modeText = { idle: '— пауза —', up: '↑ РАЗГОН ВВЕРХ', dn: '↓ РАЗГОН ВНИЗ', stop: '— равномерно/стоит', fall: 'СВОБОДНОЕ ПАДЕНИЕ' }[st.mode]; + ctx.fillText(modeText, shaftX + shaftW + 20, 60); + /* большой циферблат справа */ + const dialX = shaftX + shaftW + 130, dialY = 200, dialR = 70; + ctx.fillStyle = col.bg || '#fafafa'; + ctx.beginPath(); ctx.arc(dialX, dialY, dialR, 0, Math.PI*2); ctx.fill(); + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 3; + ctx.stroke(); + /* шкала 0..2.5 g */ + ctx.lineWidth = 2; + for (let i = 0; i <= 10; i++){ + const a = Math.PI*0.75 + (i/10) * Math.PI*1.5; + const r1 = dialR - 12, r2 = dialR; + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.beginPath(); + ctx.moveTo(dialX + r1*Math.cos(a), dialY + r1*Math.sin(a)); + ctx.lineTo(dialX + r2*Math.cos(a), dialY + r2*Math.sin(a)); + ctx.stroke(); + ctx.fillStyle = col.textMuted || '#64748b'; + ctx.font = '10px Inter,sans-serif'; + const lbl = (i*0.25).toFixed(1); + const lblR = dialR - 25; + ctx.fillText(lbl, dialX + lblR*Math.cos(a) - 8, dialY + lblR*Math.sin(a) + 4); + } + /* стрелка на большом */ + const normBig = Math.max(0, Math.min(1, P/(m*g*2.5))); + const aBig = Math.PI*0.75 + normBig * Math.PI*1.5; + ctx.strokeStyle = col.fail || '#dc2626'; + ctx.lineWidth = 4; + ctx.lineCap = 'round'; + ctx.beginPath(); + ctx.moveTo(dialX, dialY); + ctx.lineTo(dialX + (dialR-15)*Math.cos(aBig), dialY + (dialR-15)*Math.sin(aBig)); + ctx.stroke(); + ctx.fillStyle = col.fail || '#dc2626'; + ctx.beginPath(); ctx.arc(dialX, dialY, 5, 0, Math.PI*2); ctx.fill(); + /* подпись */ + ctx.fillStyle = col.text || '#0f172a'; + ctx.textAlign = 'center'; + ctx.font = 'bold 14px Inter,sans-serif'; + ctx.fillText(P.toFixed(1) + ' Н', dialX, dialY + dialR + 22); + ctx.font = '11px Inter,sans-serif'; + ctx.fillText('(' + (P/(m*g)).toFixed(2) + ' g)', dialX, dialY + dialR + 38); + ctx.textAlign = 'left'; + } + + document.getElementById('F7-up').addEventListener('mousedown', ()=>{ st.mode = 'up'; }); + document.getElementById('F7-up').addEventListener('mouseup', ()=>{ st.mode = 'stop'; }); + document.getElementById('F7-dn').addEventListener('mousedown', ()=>{ st.mode = 'dn'; }); + document.getElementById('F7-dn').addEventListener('mouseup', ()=>{ st.mode = 'stop'; }); + document.getElementById('F7-stop').addEventListener('click', ()=>{ st.mode = 'stop'; }); + document.getElementById('F7-fall').addEventListener('click', ()=>{ st.mode = 'fall'; }); + document.getElementById('F7-reset').addEventListener('click', reset); + ['F7-m','F7-a'].forEach(id => document.getElementById(id).addEventListener('input', readSliders)); + + readSliders(); + draw(); + B().startLoop('F7', cv, tick); + return true; +} + +if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F7', { init: init, cleanup: function(){} }); +else document.addEventListener('DOMContentLoaded', ()=>{ + if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F7', { init: init, cleanup: function(){} }); +}); + +})(); diff --git a/frontend/textbooks/physics_9_ch2.html b/frontend/textbooks/physics_9_ch2.html index 409ee91..5d38daa 100644 --- a/frontend/textbooks/physics_9_ch2.html +++ b/frontend/textbooks/physics_9_ch2.html @@ -790,7 +790,7 @@ function _injectTasks(id){ var body = document.getElementById(id + '-body'); if(!body || body.querySelector('.legacy-tasks')) return; body.insertAdjacentHTML('beforeend', _makeTaskBlock(id)); - setTimeout(function(){ try { if(window.renderTask) window.renderTask(id); if(window.renderNav) window.renderNav(id); } catch(e){} try { if(window.PHYS9_FINALS_INIT && /^final\d+$/.test(id)) window.PHYS9_FINALS_INIT(id); } catch(e){ console.warn("phys9 final init:", e.message); } try { if(window.PHYS9_CH2_WIDGETS && window.PHYS9_CH2_WIDGETS[id]) window.PHYS9_CH2_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p17') window.PHYS9_FLAG_BASE.mount('F4','p17'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60); + setTimeout(function(){ try { if(window.renderTask) window.renderTask(id); if(window.renderNav) window.renderNav(id); } catch(e){} try { if(window.PHYS9_FINALS_INIT && /^final\d+$/.test(id)) window.PHYS9_FINALS_INIT(id); } catch(e){ console.warn("phys9 final init:", e.message); } try { if(window.PHYS9_CH2_WIDGETS && window.PHYS9_CH2_WIDGETS[id]) window.PHYS9_CH2_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p17') window.PHYS9_FLAG_BASE.mount('F4','p17'); else if(id==='p22') window.PHYS9_FLAG_BASE.mount('F5','p22'); else if(id==='p24') window.PHYS9_FLAG_BASE.mount('F7','p24'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60); } var _origEnsureBuilt = ensureBuilt; ensureBuilt = function(id){ _origEnsureBuilt(id); _injectTasks(id); };