Files
Learn_System/frontend/js/flagships/phys9_flag_F14_resonance.js
Maxim Dolgolyov e316d39264 feat(phys9 flagships): F6 дорога + F13 Фуко + F14 резонанс
F6. Симулятор скоростной дороги (§20 в ch2):
- 5 покрытий: сухой/мокрый асфальт, гравий, снег, лёд (μ=0.7..0.08)
- Slider скорости 20..180 км/ч
- Автомобиль едет по дороге, кнопка ТОРМОЗ → тормозит до 0
- Расчёт: s = v²/(2μg), t = v/(μg)
- На льду тормозной путь в ~8 раз длиннее асфальта

F13. Маятник Фуко (§36 в ch4):
- Маятник в виде розетки, плоскость вращается со скоростью
  ω = sin(φ) · 2π / 24h
- Slider широты 0..90° (от экватора до полюса)
- На полюсе — 24ч полного оборота, на экваторе — никогда
- Slider «ускорение времени» × (100..20000) — чтобы увидеть розетку
- Места: экватор/Каир/Рим/Минск/Москва/Заполярье/полюс

F14. Резонанс пружинного маятника (§36 в ch4):
- Слева: пружина с грузом + внешняя гармоническая сила
- Slider'ы: m, k, ν_внешн, γ затухание
- Кнопка «Настроить на резонанс» (ν_внешн = ν_собств)
- Реальная физика затухания: m·x'' = -kx - γv + F0·cos(ωt)
- Справа: график x(t) за последние 20 с
- Классификация: вынужденные / близко к резонансу / РЕЗОНАНС!

Подключение:
- ch2: F6 на p20
- ch4: F13 + F14 оба на p36 (маятники)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 10:23:57 +03:00

210 lines
9.2 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.
// 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 = ''
+ '<div class="flag-sliders">'
+ '<label>$m$, кг: <b id="F14-mv">1</b><input type="range" id="F14-m" min="0.1" max="5" step="0.1" value="1"></label>'
+ '<label>$k$, Н/м: <b id="F14-kv">40</b><input type="range" id="F14-k" min="5" max="200" step="5" value="40"></label>'
+ '<label>Внешняя частота $\\nu_{вн}$, Гц: <b id="F14-fv">1.00</b><input type="range" id="F14-f" min="0.05" max="3" step="0.05" value="1"></label>'
+ '<label>Затухание $\\gamma$: <b id="F14-gv">0.1</b><input type="range" id="F14-g" min="0.02" max="1" step="0.02" value="0.1"></label>'
+ '</div>'
+ '<canvas id="F14-cv" class="flag-canvas" width="700" height="380" style="height:380px"></canvas>'
+ '<div class="flag-controls">'
+ '<button class="flag-btn primary" id="F14-go">Запустить</button>'
+ '<button class="flag-btn" id="F14-reset">Сброс</button>'
+ '<button class="flag-btn" id="F14-tune">Настроить на резонанс</button>'
+ '</div>'
+ '<div class="flag-stats">'
+ '<div class="flag-stat"><span class="lbl">$\\nu_{собств}$</span><span class="val" id="F14-nu0">1.01 Гц</span></div>'
+ '<div class="flag-stat"><span class="lbl">$\\nu_{внешн}$</span><span class="val" id="F14-nuext">1.00 Гц</span></div>'
+ '<div class="flag-stat"><span class="lbl">Амплитуда</span><span class="val" id="F14-A">0 м</span></div>'
+ '<div class="flag-stat"><span class="lbl">Состояние</span><span class="val" id="F14-mode">—</span></div>'
+ '</div>'
+ '<div class="flag-feedback" id="F14-fb"></div>';
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 = '&#9888; РЕЗОНАНС! Амплитуда нарастает. Так разрушаются мосты — рота не должна маршировать через них в ногу.';
} 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(){} });
});
})();