// F14. Резонанс (§36 в ch4) — пружина + внешняя сила. (function(){ 'use strict'; const B = () => window.PHYS9_FLAG_BASE; const C = () => window.PHYS9_COLORS || {}; function init(secId){ if (!B()) return false; const body = '' + '
' + '' + '' + '' + '' + '
' + '' + '
' + '' + '' + '' + '
' + '
' + '
$\\nu_{собств}$1.01 Гц
' + '
$\\nu_{внешн}$1.00 Гц
' + '
Амплитуда0 м
' + '
Состояние
' + '
' + '
'; const card = B().makeCard(secId, 'F14. Резонанс пружинного маятника', 'Слева: пружина с грузом + внешняя сила. Справа: график амплитуды от частоты — резонансный пик при $\\nu_{вн} = \\nu_{собств}$.', body); if (!card) return false; const cv = document.getElementById('F14-cv'); const ctx = cv.getContext('2d'); const W = cv.width, H = cv.height; const cx = W * 0.25; let st = { x: 0, v: 0, t: 0, running: false, ampHistory: [] }; function nuOwn(){ const m = +document.getElementById('F14-m').value; const k = +document.getElementById('F14-k').value; return Math.sqrt(k/m) / (2*Math.PI); } function update(){ document.getElementById('F14-mv').textContent = (+document.getElementById('F14-m').value).toFixed(1); document.getElementById('F14-kv').textContent = +document.getElementById('F14-k').value; document.getElementById('F14-fv').textContent = (+document.getElementById('F14-f').value).toFixed(2); document.getElementById('F14-gv').textContent = (+document.getElementById('F14-g').value).toFixed(2); document.getElementById('F14-nu0').textContent = nuOwn().toFixed(2) + ' Гц'; document.getElementById('F14-nuext').textContent = (+document.getElementById('F14-f').value).toFixed(2) + ' Гц'; } function reset(){ st = { x: 0, v: 0, t: 0, running: false, ampHistory: [] }; document.getElementById('F14-go').textContent = 'Запустить'; draw(); } function tick(dt){ if (!st.running) { draw(); return; } const m = +document.getElementById('F14-m').value; const k = +document.getElementById('F14-k').value; const omega_ext = 2*Math.PI*(+document.getElementById('F14-f').value); const gamma = +document.getElementById('F14-g').value; const F0 = 5; /* m·x'' = -kx - γ·v + F0·cos(ωt) */ const N = 8; const ddt = dt / N; for (let i = 0; i < N; i++){ const F = F0 * Math.cos(omega_ext * st.t); const a = (-k*st.x - gamma*st.v + F) / m; st.v += a * ddt; st.x += st.v * ddt; st.t += ddt; } /* записываем амплитуду */ st.ampHistory.push({ t: st.t, x: st.x }); if (st.ampHistory.length > 800) st.ampHistory.shift(); /* текущая амплитуда — максимум за последние 2 с */ let amp = 0; for (const h of st.ampHistory){ if (st.t - h.t < 5) amp = Math.max(amp, Math.abs(h.x)); } document.getElementById('F14-A').textContent = amp.toFixed(3) + ' м'; /* классификация */ const nu0 = nuOwn(); const nuExt = +document.getElementById('F14-f').value; const mode = document.getElementById('F14-mode'); const fb = document.getElementById('F14-fb'); if (Math.abs(nu0 - nuExt) / nu0 < 0.08){ mode.textContent = 'РЕЗОНАНС!'; mode.style.color = 'var(--fail,#dc2626)'; fb.className = 'flag-feedback fail show'; fb.innerHTML = '⚠ РЕЗОНАНС! Амплитуда нарастает. Так разрушаются мосты — рота не должна маршировать через них в ногу.'; } else if (Math.abs(nu0 - nuExt) / nu0 < 0.25){ mode.textContent = 'близко к резонансу'; mode.style.color = 'var(--warn,#f59e0b)'; fb.className = 'flag-feedback warn show'; fb.innerHTML = 'Близко к резонансу — амплитуда заметно больше внешней силы.'; } else { mode.textContent = 'вынужденные колебания'; mode.style.color = 'var(--ok,#10b981)'; fb.className = 'flag-feedback'; } draw(); } function draw(){ const col = C(); ctx.fillStyle = col.bg || '#fafafa'; ctx.fillRect(0, 0, W, H); /* левая половина — пружина с грузом */ /* потолок */ ctx.fillStyle = col.surface || '#a16207'; ctx.fillRect(cx - 60, 30, 120, 14); /* пружина */ const massY = 160 + st.x * 400; ctx.strokeStyle = col.forceSpring || '#d97706'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.moveTo(cx, 44); for (let i = 0; i < 14; i++){ const t = i / 14; const y = 44 + t * (massY - 44); const x = cx + Math.sin(t * 14 * Math.PI) * 12; ctx.lineTo(x, y); } ctx.lineTo(cx, massY); ctx.stroke(); /* груз */ ctx.fillStyle = col.forceGravity || '#2563eb'; ctx.fillRect(cx - 25, massY, 50, 30); ctx.strokeStyle = col.bodyAccent || '#1e293b'; ctx.strokeRect(cx - 25, massY, 50, 30); /* нулевая линия */ ctx.strokeStyle = col.grid || '#e5e7eb'; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.moveTo(cx - 60, 160); ctx.lineTo(cx + 60, 160); ctx.stroke(); ctx.setLineDash([]); /* стрелка внешней силы */ const F_now = 5 * Math.cos(2*Math.PI*(+document.getElementById('F14-f').value) * st.t); if (st.running){ B().arrow(ctx, cx + 35, massY + 15, cx + 35 + F_now*8, massY + 15, col.acceleration || '#ea580c', 2); } /* правая половина — график x(t) */ const gx = W/2 + 20, gy = 40, gw = W/2 - 40, gh = H - 90; ctx.fillStyle = col.bgSubtle || '#f8fafc'; ctx.fillRect(gx, gy, gw, gh); ctx.strokeStyle = col.axis || '#1e293b'; ctx.lineWidth = 1.5; ctx.strokeRect(gx, gy, gw, gh); /* центральная ось */ ctx.strokeStyle = col.grid || '#e5e7eb'; ctx.beginPath(); ctx.moveTo(gx, gy + gh/2); ctx.lineTo(gx + gw, gy + gh/2); ctx.stroke(); /* линия x(t) */ if (st.ampHistory.length > 1){ const tMin = Math.max(0, st.t - 20); const tMax = st.t; let maxAbsX = 0.1; for (const h of st.ampHistory) if (h.t >= tMin) maxAbsX = Math.max(maxAbsX, Math.abs(h.x)); ctx.strokeStyle = col.velocity || '#0891b2'; ctx.lineWidth = 2; ctx.beginPath(); let first = true; for (const h of st.ampHistory){ if (h.t < tMin) continue; const px = gx + ((h.t - tMin)/(tMax - tMin)) * gw; const py = gy + gh/2 - (h.x / maxAbsX) * (gh/2 - 5); if (first){ ctx.moveTo(px, py); first = false; } else ctx.lineTo(px, py); } ctx.stroke(); } ctx.fillStyle = col.textMuted || '#64748b'; ctx.font = '11px Inter,sans-serif'; ctx.fillText('x(t) — последние 20 с', gx + 6, gy + 14); ctx.fillText('t →', gx + gw - 24, gy + gh - 4); } document.getElementById('F14-go').addEventListener('click', () => { st.running = !st.running; document.getElementById('F14-go').textContent = st.running ? 'Пауза' : 'Запустить'; }); document.getElementById('F14-reset').addEventListener('click', reset); document.getElementById('F14-tune').addEventListener('click', () => { document.getElementById('F14-f').value = nuOwn().toFixed(2); update(); reset(); }); ['F14-m','F14-k','F14-f','F14-g'].forEach(id => document.getElementById(id).addEventListener('input', update)); update(); reset(); B().startLoop('F14', cv, tick); return true; } if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F14', { init: init, cleanup: function(){} }); else document.addEventListener('DOMContentLoaded', ()=>{ if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F14', { init: init, cleanup: function(){} }); }); })();