e316d39264
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>
164 lines
7.4 KiB
JavaScript
164 lines
7.4 KiB
JavaScript
// F13. Маятник Фуко (§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>Широта φ, °: <b id="F13-lv">52</b><input type="range" id="F13-l" min="0" max="90" step="1" value="52"></label>'
|
||
+ '<label>Ускорение времени, ×: <b id="F13-sv">3600</b><input type="range" id="F13-s" min="100" max="20000" step="100" value="3600"></label>'
|
||
+ '</div>'
|
||
+ '<canvas id="F13-cv" class="flag-canvas" width="640" height="380" style="height:380px"></canvas>'
|
||
+ '<div class="flag-controls">'
|
||
+ '<button class="flag-btn primary" id="F13-go">Запустить</button>'
|
||
+ '<button class="flag-btn" id="F13-reset">Сброс</button>'
|
||
+ '<button class="flag-btn" id="F13-clear">Очистить следы</button>'
|
||
+ '</div>'
|
||
+ '<div class="flag-stats">'
|
||
+ '<div class="flag-stat"><span class="lbl">Текущая широта</span><span class="val" id="F13-loc">Минск (52°)</span></div>'
|
||
+ '<div class="flag-stat"><span class="lbl">Период оборота плоскости</span><span class="val" id="F13-T">30.4 ч</span></div>'
|
||
+ '<div class="flag-stat"><span class="lbl">Реальное время</span><span class="val" id="F13-t">0 ч</span></div>'
|
||
+ '<div class="flag-stat"><span class="lbl">Поворот плоскости</span><span class="val" id="F13-rot">0°</span></div>'
|
||
+ '</div>'
|
||
+ '<div class="flag-feedback" id="F13-fb"></div>';
|
||
|
||
const card = B().makeCard(secId,
|
||
'F13. Маятник Фуко',
|
||
'Маятник в Париже (1851) доказал вращение Земли. Период оборота плоскости: $T = 24/\\sin\\varphi$ часов. На полюсе — 24 ч, на экваторе — никогда.',
|
||
body);
|
||
if (!card) return false;
|
||
|
||
const cv = document.getElementById('F13-cv');
|
||
const ctx = cv.getContext('2d');
|
||
const W = cv.width, H = cv.height;
|
||
const cx = W/2, cy = H/2;
|
||
const R = 130;
|
||
|
||
let st = { phase: 0, planeAngle: 0, t: 0, running: false, trail: [] };
|
||
|
||
function update(){
|
||
const lat = +document.getElementById('F13-l').value;
|
||
const speed = +document.getElementById('F13-s').value;
|
||
document.getElementById('F13-lv').textContent = lat;
|
||
document.getElementById('F13-sv').textContent = speed;
|
||
let loc;
|
||
if (lat >= 89) loc = 'Северный полюс (90°)';
|
||
else if (lat >= 70) loc = 'Заполярье (70-89°)';
|
||
else if (lat >= 55) loc = 'Москва (55°)';
|
||
else if (lat >= 50) loc = 'Минск (52°)';
|
||
else if (lat >= 40) loc = 'Рим (42°)';
|
||
else if (lat >= 20) loc = 'Каир (30°)';
|
||
else if (lat >= 5) loc = 'тропики';
|
||
else loc = 'экватор (0°)';
|
||
document.getElementById('F13-loc').textContent = loc;
|
||
const sinL = Math.sin(lat * Math.PI/180);
|
||
const T = sinL > 0.001 ? 24 / sinL : Infinity;
|
||
document.getElementById('F13-T').textContent = isFinite(T) ? T.toFixed(1) + ' ч' : '∞ (нет поворота)';
|
||
}
|
||
|
||
function reset(){
|
||
st = { phase: 0, planeAngle: 0, t: 0, running: false, trail: [] };
|
||
document.getElementById('F13-go').textContent = 'Запустить';
|
||
draw();
|
||
}
|
||
|
||
function tick(dt){
|
||
if (!st.running) { draw(); return; }
|
||
const lat = +document.getElementById('F13-l').value;
|
||
const speed = +document.getElementById('F13-s').value;
|
||
const sinL = Math.sin(lat * Math.PI/180);
|
||
const realDt = dt * speed; /* условное реальное время в секундах */
|
||
st.t += realDt;
|
||
/* колебание маятника: период ~6 с реального времени */
|
||
st.phase += dt * 2 * Math.PI * 0.5; /* псевдо-период для красоты */
|
||
/* поворот плоскости: ω = 2π/(24·3600/sinL) рад/с */
|
||
const omega = sinL * 2 * Math.PI / (24*3600);
|
||
st.planeAngle += omega * realDt;
|
||
/* trail — текущее положение маятника */
|
||
const r = R * Math.cos(st.phase);
|
||
const px = cx + r * Math.cos(st.planeAngle);
|
||
const py = cy + r * Math.sin(st.planeAngle);
|
||
st.trail.push({ x: px, y: py });
|
||
if (st.trail.length > 5000) st.trail.shift();
|
||
document.getElementById('F13-t').textContent = (st.t/3600).toFixed(2) + ' ч';
|
||
document.getElementById('F13-rot').textContent = ((st.planeAngle * 180/Math.PI) % 360).toFixed(1) + '°';
|
||
draw();
|
||
}
|
||
|
||
function draw(){
|
||
const col = C();
|
||
ctx.fillStyle = col.bg || '#fafafa';
|
||
ctx.fillRect(0, 0, W, H);
|
||
/* пол */
|
||
ctx.fillStyle = col.bgSubtle || '#f8fafc';
|
||
ctx.beginPath(); ctx.arc(cx, cy, R + 30, 0, Math.PI*2); ctx.fill();
|
||
ctx.strokeStyle = col.axis || '#1e293b';
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
/* радиальные направления (страны света) */
|
||
ctx.font = '12px Inter,sans-serif';
|
||
ctx.fillStyle = col.textMuted || '#64748b';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('С', cx, cy - R - 15);
|
||
ctx.fillText('Ю', cx, cy + R + 25);
|
||
ctx.fillText('З', cx - R - 18, cy + 4);
|
||
ctx.fillText('В', cx + R + 18, cy + 4);
|
||
/* следы маятника */
|
||
if (st.trail.length > 1){
|
||
ctx.strokeStyle = col.velocity || '#0891b2';
|
||
ctx.lineWidth = 1.2;
|
||
ctx.globalAlpha = 0.5;
|
||
ctx.beginPath();
|
||
ctx.moveTo(st.trail[0].x, st.trail[0].y);
|
||
for (let i = 1; i < st.trail.length; i++) ctx.lineTo(st.trail[i].x, st.trail[i].y);
|
||
ctx.stroke();
|
||
ctx.globalAlpha = 1;
|
||
}
|
||
/* центральная точка крепления */
|
||
ctx.fillStyle = col.axis || '#1e293b';
|
||
ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI*2); ctx.fill();
|
||
/* плоскость колебаний — линия */
|
||
ctx.strokeStyle = col.acceleration || '#ea580c';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.setLineDash([6, 4]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - R * Math.cos(st.planeAngle), cy - R * Math.sin(st.planeAngle));
|
||
ctx.lineTo(cx + R * Math.cos(st.planeAngle), cy + R * Math.sin(st.planeAngle));
|
||
ctx.stroke();
|
||
ctx.setLineDash([]);
|
||
/* маятник — груз */
|
||
const r = R * Math.cos(st.phase);
|
||
const px = cx + r * Math.cos(st.planeAngle);
|
||
const py = cy + r * Math.sin(st.planeAngle);
|
||
ctx.fillStyle = col.forceGravity || '#2563eb';
|
||
ctx.beginPath(); ctx.arc(px, py, 10, 0, Math.PI*2); ctx.fill();
|
||
ctx.strokeStyle = col.bodyAccent || '#1e293b';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.textAlign = 'left';
|
||
}
|
||
|
||
document.getElementById('F13-go').addEventListener('click', () => {
|
||
st.running = !st.running;
|
||
document.getElementById('F13-go').textContent = st.running ? 'Пауза' : 'Запустить';
|
||
});
|
||
document.getElementById('F13-reset').addEventListener('click', reset);
|
||
document.getElementById('F13-clear').addEventListener('click', () => { st.trail = []; draw(); });
|
||
['F13-l','F13-s'].forEach(id => document.getElementById(id).addEventListener('input', update));
|
||
|
||
update();
|
||
draw();
|
||
B().startLoop('F13', cv, tick);
|
||
return true;
|
||
}
|
||
|
||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F13', { init: init, cleanup: function(){} });
|
||
else document.addEventListener('DOMContentLoaded', ()=>{
|
||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F13', { init: init, cleanup: function(){} });
|
||
});
|
||
|
||
})();
|