feat(phys9 flagships): F10 аквариум + F12 горки (Wave C+D пилоты)
F10. Виртуальный аквариум (§29 в ch3): - Canvas 640×380 с переключателем жидкости (вода/масло/ртуть) - Палитра 7 материалов: дерево, пенопласт, пластик, лёд, алюминий, железо, золото (с указанием ρ) - Клик по материалу → бросает кубик в аквариум - Реальная физика плавания: F_Архимеда vs F_тяжести - Тела плавают/тонут/висят согласно ρ_тела vs ρ_жидкости - При смене жидкости тела перераспределяются - Феномен: «золото плавает в ртути!» - Контекстный feedback по последнему уложенному телу F12. Американские горки (§35 в ch4): - Canvas 700×360 — рисуй мышкой профиль горки слева направо - Шаблоны: «горка» (V-образная), «петля» (волнистая) - Slider'ы: μ трения (0..0.5), масса шарика (0.1..5 кг) - Кнопки: Старт/Сброс/Очистить - Реальная физика по сегментам: a = g·sinα - μg·cosα·sign(v) - Real-time stats: Ep, Ek, E_total, v - Зелёная пунктир E₀ на canvas — начальная энергия - Красная пунктир — диссипация при трении (растёт со временем) - ЗСМЭ виден визуально: без трения линии совпадают Подключение: - ch3: phys9-flagships.css + base + F10 + хук на p29 - ch4: phys9-flagships.css + base + F12 + хук на p35 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
// F10. Виртуальный аквариум (§29 в ch3) — Архимед, плавание тел.
|
||||
(function(){
|
||||
'use strict';
|
||||
const B = () => window.PHYS9_FLAG_BASE;
|
||||
const C = () => window.PHYS9_COLORS || {};
|
||||
|
||||
const MATERIALS = [
|
||||
{ id:'wood', name:'дерево', rho:600, col:'#a16207' },
|
||||
{ id:'foam', name:'пенопласт', rho:50, col:'#fef3c7' },
|
||||
{ id:'plastic', name:'пластик', rho:950, col:'#06b6d4' },
|
||||
{ id:'ice', name:'лёд', rho:917, col:'#bfdbfe' },
|
||||
{ id:'al', name:'алюминий', rho:2700, col:'#94a3b8' },
|
||||
{ id:'iron', name:'железо', rho:7800, col:'#475569' },
|
||||
{ id:'gold', name:'золото', rho:19300, col:'#fbbf24' }
|
||||
];
|
||||
|
||||
const LIQUIDS = {
|
||||
water: { rho:1000, name:'вода', col:'rgba(96,165,250,0.5)' },
|
||||
oil: { rho:800, name:'масло', col:'rgba(217,119,6,0.4)' },
|
||||
mercury: { rho:13600, name:'ртуть', col:'rgba(229,231,235,0.7)' }
|
||||
};
|
||||
|
||||
function init(secId){
|
||||
if (!B()) return false;
|
||||
let buttons = '';
|
||||
MATERIALS.forEach(m => {
|
||||
buttons += '<button class="flag-btn" data-mat="'+m.id+'" style="background:'+m.col+';color:'+(m.rho>5000?'#fff':'#0f172a')+';font-size:.78rem;padding:6px 10px">'+m.name+' (ρ='+m.rho+')</button>';
|
||||
});
|
||||
const body = ''
|
||||
+ '<div style="margin-bottom:10px;font-size:.85rem;color:var(--muted)">Жидкость:</div>'
|
||||
+ '<div class="flag-controls" style="margin-bottom:6px">'
|
||||
+ '<button class="flag-btn primary" data-liq="water">Вода (1000)</button>'
|
||||
+ '<button class="flag-btn" data-liq="oil">Масло (800)</button>'
|
||||
+ '<button class="flag-btn" data-liq="mercury">Ртуть (13600)</button>'
|
||||
+ '</div>'
|
||||
+ '<div style="margin-bottom:10px;font-size:.85rem;color:var(--muted)">Кликни по телу, чтобы бросить его в аквариум:</div>'
|
||||
+ '<div class="flag-controls">'+buttons+'</div>'
|
||||
+ '<canvas id="F10-cv" class="flag-canvas" width="640" height="380" style="height:380px"></canvas>'
|
||||
+ '<div class="flag-controls">'
|
||||
+ '<button class="flag-btn danger" id="F10-clear">Очистить</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="flag-feedback" id="F10-fb"></div>';
|
||||
|
||||
const card = B().makeCard(secId,
|
||||
'F10. Виртуальный аквариум',
|
||||
'Выбери жидкость и бросай тела. Тело $\\rho_T < \\rho_Ж$ — плавает. Равны — висит. Тяжелее — тонет. Попробуй золото в ртути!',
|
||||
body);
|
||||
if (!card) return false;
|
||||
|
||||
const cv = document.getElementById('F10-cv');
|
||||
const ctx = cv.getContext('2d');
|
||||
const W = cv.width, H = cv.height;
|
||||
const liqTop = 60;
|
||||
|
||||
let bodies = []; /* { x, y, vy, mat, size, settled } */
|
||||
let liquid = 'water';
|
||||
|
||||
function addBody(matId){
|
||||
const mat = MATERIALS.find(m => m.id === matId);
|
||||
if (!mat) return;
|
||||
const size = 22 + Math.random()*10;
|
||||
bodies.push({ x: 60 + Math.random()*(W - 120), y: 30, vy: 0, mat: mat, size: size, settled: false });
|
||||
}
|
||||
|
||||
function clear(){ bodies = []; }
|
||||
|
||||
function tick(dt){
|
||||
const liq = LIQUIDS[liquid];
|
||||
bodies.forEach(b => {
|
||||
if (b.settled) return;
|
||||
const g = 9.8;
|
||||
const inLiquid = b.y > liqTop;
|
||||
const submerged = Math.min(1, Math.max(0, (b.y - liqTop + b.size/2) / b.size));
|
||||
/* Сила тяжести вниз: m*g, m = ρ_T * V (V в условных единицах ∝ size^2) */
|
||||
const V = (b.size * b.size) / 1000;
|
||||
const Fg = b.mat.rho * V * g;
|
||||
const Fa = liq.rho * V * g * submerged;
|
||||
const Fnet = Fg - Fa;
|
||||
/* a = Fnet / m, m = ρ_T * V */
|
||||
const a = Fnet / (b.mat.rho * V);
|
||||
b.vy += a * dt * 30; /* px/m scale */
|
||||
/* демпфирование в жидкости */
|
||||
if (inLiquid) b.vy *= 0.96;
|
||||
b.y += b.vy * dt;
|
||||
/* дно */
|
||||
if (b.y > H - b.size/2){ b.y = H - b.size/2; b.vy = 0; }
|
||||
/* потолок жидкости — для плавающих */
|
||||
if (b.y < liqTop && b.vy < 0){
|
||||
b.y = liqTop;
|
||||
b.vy = 0;
|
||||
}
|
||||
/* «осёл» — если скорость мала и в равновесии, settling */
|
||||
if (Math.abs(b.vy) < 0.05 && Math.abs(a) < 0.1){
|
||||
b.settled = true;
|
||||
}
|
||||
});
|
||||
draw();
|
||||
}
|
||||
|
||||
function draw(){
|
||||
const col = C();
|
||||
/* небо */
|
||||
ctx.fillStyle = col.gas || '#dbeafe';
|
||||
ctx.fillRect(0, 0, W, liqTop);
|
||||
/* жидкость */
|
||||
const liq = LIQUIDS[liquid];
|
||||
ctx.fillStyle = liq.col;
|
||||
ctx.fillRect(0, liqTop, W, H - liqTop);
|
||||
/* поверхность воды */
|
||||
ctx.strokeStyle = col.liquid || '#3b82f6';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath(); ctx.moveTo(0, liqTop); ctx.lineTo(W, liqTop); ctx.stroke();
|
||||
/* стенки аквариума */
|
||||
ctx.strokeStyle = col.axis || '#1e293b';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, liqTop - 5); ctx.lineTo(0, H);
|
||||
ctx.moveTo(W, liqTop - 5); ctx.lineTo(W, H);
|
||||
ctx.moveTo(0, H); ctx.lineTo(W, H);
|
||||
ctx.stroke();
|
||||
/* подпись жидкости */
|
||||
ctx.fillStyle = col.text || '#0f172a';
|
||||
ctx.font = 'bold 13px Inter,sans-serif';
|
||||
ctx.fillText(liq.name + ' (ρ = ' + liq.rho + ' кг/м³)', 12, 24);
|
||||
/* тела */
|
||||
bodies.forEach(b => {
|
||||
ctx.fillStyle = b.mat.col;
|
||||
ctx.fillRect(b.x - b.size/2, b.y - b.size/2, b.size, b.size);
|
||||
ctx.strokeStyle = col.bodyAccent || '#1e293b';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.strokeRect(b.x - b.size/2, b.y - b.size/2, b.size, b.size);
|
||||
ctx.fillStyle = b.mat.rho > 5000 ? '#fff' : '#0f172a';
|
||||
ctx.font = '10px Inter,sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(b.mat.rho, b.x, b.y + 3);
|
||||
ctx.textAlign = 'left';
|
||||
});
|
||||
/* feedback */
|
||||
if (bodies.length > 0){
|
||||
const last = bodies[bodies.length-1];
|
||||
if (last.settled){
|
||||
const fb = document.getElementById('F10-fb');
|
||||
const liqRho = LIQUIDS[liquid].rho;
|
||||
if (last.mat.rho < liqRho){
|
||||
fb.className = 'flag-feedback ok show';
|
||||
fb.innerHTML = '✓ '+last.mat.name+' ('+last.mat.rho+') ПЛАВАЕТ в '+liq.name+' ('+liqRho+'): $\\rho_T < \\rho_Ж$.';
|
||||
} else if (last.mat.rho > liqRho){
|
||||
fb.className = 'flag-feedback warn show';
|
||||
fb.innerHTML = last.mat.name+' ('+last.mat.rho+') ТОНЕТ в '+liq.name+' ('+liqRho+'): $\\rho_T > \\rho_Ж$.';
|
||||
} else {
|
||||
fb.className = 'flag-feedback ok show';
|
||||
fb.innerHTML = last.mat.name+' ВИСИТ в толще: $\\rho_T = \\rho_Ж$.';
|
||||
}
|
||||
try { if(window.renderMathInElement) window.renderMathInElement(fb, { delimiters:[{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* binding */
|
||||
card.querySelectorAll('[data-mat]').forEach(btn => {
|
||||
btn.addEventListener('click', () => addBody(btn.dataset.mat));
|
||||
});
|
||||
card.querySelectorAll('[data-liq]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
liquid = btn.dataset.liq;
|
||||
card.querySelectorAll('[data-liq]').forEach(b => b.classList.remove('primary'));
|
||||
btn.classList.add('primary');
|
||||
/* «разбудить» все тела */
|
||||
bodies.forEach(b => b.settled = false);
|
||||
});
|
||||
});
|
||||
document.getElementById('F10-clear').addEventListener('click', clear);
|
||||
|
||||
draw();
|
||||
B().startLoop('F10', cv, tick);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F10', { init: init, cleanup: function(){} });
|
||||
else document.addEventListener('DOMContentLoaded', ()=>{
|
||||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F10', { init: init, cleanup: function(){} });
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,254 @@
|
||||
// F12. Американские горки (§35 в 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 style="margin-bottom:8px;font-size:.85rem;color:var(--muted)">Нажми и проведи мышкой по канвасу — нарисуй профиль горки (слева направо).</div>'
|
||||
+ '<canvas id="F12-cv" class="flag-canvas" width="700" height="360" style="height:360px;cursor:crosshair"></canvas>'
|
||||
+ '<div class="flag-sliders">'
|
||||
+ '<label>$\\mu$ трение: <b id="F12-muv">0</b><input type="range" id="F12-mu" min="0" max="0.5" step="0.02" value="0"></label>'
|
||||
+ '<label>Масса шарика, кг: <b id="F12-mv">1</b><input type="range" id="F12-m" min="0.1" max="5" step="0.1" value="1"></label>'
|
||||
+ '</div>'
|
||||
+ '<div class="flag-controls">'
|
||||
+ '<button class="flag-btn primary" id="F12-go">Старт</button>'
|
||||
+ '<button class="flag-btn" id="F12-reset">Сброс</button>'
|
||||
+ '<button class="flag-btn" id="F12-preset1">Шаблон: горка</button>'
|
||||
+ '<button class="flag-btn" id="F12-preset2">Шаблон: петля</button>'
|
||||
+ '<button class="flag-btn danger" id="F12-clear">Очистить</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="flag-stats">'
|
||||
+ '<div class="flag-stat"><span class="lbl">$E_p$ (mgh)</span><span class="val" id="F12-Ep">0 Дж</span></div>'
|
||||
+ '<div class="flag-stat"><span class="lbl">$E_k$ (mv²/2)</span><span class="val" id="F12-Ek">0 Дж</span></div>'
|
||||
+ '<div class="flag-stat"><span class="lbl">$E$ полная</span><span class="val" id="F12-Et">0 Дж</span></div>'
|
||||
+ '<div class="flag-stat"><span class="lbl">$v$</span><span class="val" id="F12-v">0 м/с</span></div>'
|
||||
+ '</div>';
|
||||
|
||||
const card = B().makeCard(secId,
|
||||
'F12. Американские горки',
|
||||
'Нарисуй профиль горки и запусти шарик. Без трения $E_k + E_p = $ const. Со трением — энергия диссипирует.',
|
||||
body);
|
||||
if (!card) return false;
|
||||
|
||||
const cv = document.getElementById('F12-cv');
|
||||
const ctx = cv.getContext('2d');
|
||||
const W = cv.width, H = cv.height;
|
||||
const m_per_px = 0.05; /* 5 см / px */
|
||||
|
||||
let profile = []; /* отсортированный по x массив {x, y} */
|
||||
let drawing = false;
|
||||
let ball = { idx: 0, fraction: 0, vAlong: 0, energy0: 0, lossE: 0, running: false };
|
||||
|
||||
function getPos(e){
|
||||
const rect = cv.getBoundingClientRect();
|
||||
const sx = cv.width / rect.width;
|
||||
const sy = cv.height / rect.height;
|
||||
const x = ((e.touches ? e.touches[0].clientX : e.clientX) - rect.left) * sx;
|
||||
const y = ((e.touches ? e.touches[0].clientY : e.clientY) - rect.top) * sy;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function start(e){
|
||||
drawing = true;
|
||||
profile = [getPos(e)];
|
||||
e.preventDefault();
|
||||
draw();
|
||||
}
|
||||
function move(e){
|
||||
if (!drawing) return;
|
||||
const p = getPos(e);
|
||||
const last = profile[profile.length-1];
|
||||
if (p.x > last.x + 3) profile.push(p);
|
||||
e.preventDefault();
|
||||
draw();
|
||||
}
|
||||
function end(){ drawing = false; }
|
||||
|
||||
function presetHill(){
|
||||
profile = [];
|
||||
for (let x = 30; x <= W - 30; x += 8){
|
||||
const norm = x / (W - 60);
|
||||
const y = 50 + 250 * Math.abs(norm - 0.5) * (1 - norm*0.8);
|
||||
profile.push({ x, y });
|
||||
}
|
||||
reset();
|
||||
}
|
||||
function presetLoop(){
|
||||
profile = [];
|
||||
/* три горки */
|
||||
for (let x = 30; x <= W - 30; x += 6){
|
||||
const t = (x - 30) / (W - 60);
|
||||
const y = 70 + 240 * (1 - t) * 0.4 + 100 * Math.sin(t * Math.PI * 2.2);
|
||||
profile.push({ x, y: Math.max(40, Math.min(H - 20, y)) });
|
||||
}
|
||||
reset();
|
||||
}
|
||||
function clear(){ profile = []; reset(); }
|
||||
|
||||
function reset(){
|
||||
if (profile.length > 1){
|
||||
ball = { idx: 0, fraction: 0, vAlong: 0, running: false };
|
||||
const m = +document.getElementById('F12-m').value;
|
||||
const h = (H - profile[0].y) * m_per_px;
|
||||
ball.energy0 = m * 9.8 * h;
|
||||
ball.lossE = 0;
|
||||
}
|
||||
document.getElementById('F12-go').textContent = 'Старт';
|
||||
draw();
|
||||
}
|
||||
|
||||
function tick(dt){
|
||||
if (!ball.running || profile.length < 2) { draw(); return; }
|
||||
/* Движение по профилю: используем сегменты. */
|
||||
const m = +document.getElementById('F12-m').value;
|
||||
const mu = +document.getElementById('F12-mu').value;
|
||||
const g = 9.8;
|
||||
/* Текущая высота */
|
||||
const i = ball.idx;
|
||||
if (i >= profile.length - 1){ ball.running = false; document.getElementById('F12-go').textContent='Старт'; draw(); return; }
|
||||
const p1 = profile[i], p2 = profile[i+1];
|
||||
const segLen_px = Math.hypot(p2.x - p1.x, p2.y - p1.y);
|
||||
const segLen = segLen_px * m_per_px;
|
||||
const slope = (p2.y - p1.y) / (p2.x - p1.x);
|
||||
const sinA = slope / Math.sqrt(1 + slope*slope); /* y SVG вниз — slope>0 = вниз */
|
||||
const cosA = 1 / Math.sqrt(1 + slope*slope);
|
||||
/* Ускорение вдоль профиля: a = g*sinA - μ*g*cosA*sign(v) */
|
||||
let aAlong = g * sinA;
|
||||
if (Math.abs(ball.vAlong) > 0.01) aAlong -= Math.sign(ball.vAlong) * mu * g * cosA;
|
||||
/* шаг */
|
||||
ball.vAlong += aAlong * dt;
|
||||
/* трение тратит энергию */
|
||||
if (mu > 0) ball.lossE += mu * m * g * cosA * Math.abs(ball.vAlong * dt);
|
||||
const ds = ball.vAlong * dt; /* в метрах */
|
||||
ball.fraction += ds / Math.max(0.01, segLen);
|
||||
/* переход к следующему сегменту */
|
||||
while (ball.fraction >= 1 && ball.idx < profile.length - 1){
|
||||
ball.fraction -= 1;
|
||||
ball.idx++;
|
||||
}
|
||||
while (ball.fraction < 0 && ball.idx > 0){
|
||||
ball.fraction += 1;
|
||||
ball.idx--;
|
||||
}
|
||||
if (ball.idx >= profile.length - 1 || ball.idx < 0){
|
||||
ball.running = false;
|
||||
document.getElementById('F12-go').textContent='Старт';
|
||||
}
|
||||
/* статистика */
|
||||
const px = p1.x + ball.fraction * (p2.x - p1.x);
|
||||
const py = p1.y + ball.fraction * (p2.y - p1.y);
|
||||
const h = (H - py) * m_per_px;
|
||||
const v = Math.abs(ball.vAlong);
|
||||
const Ep = m * g * h;
|
||||
const Ek = m * v * v / 2;
|
||||
document.getElementById('F12-Ep').textContent = Ep.toFixed(1) + ' Дж';
|
||||
document.getElementById('F12-Ek').textContent = Ek.toFixed(1) + ' Дж';
|
||||
document.getElementById('F12-Et').textContent = (Ep + Ek).toFixed(1) + ' Дж';
|
||||
document.getElementById('F12-v').textContent = v.toFixed(2) + ' м/с';
|
||||
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, H - 10, W, 10);
|
||||
if (profile.length < 2){
|
||||
ctx.fillStyle = col.textMuted || '#64748b';
|
||||
ctx.font = '15px Inter,sans-serif';
|
||||
ctx.fillText('Нарисуй профиль горки слева направо мышкой/пальцем', 90, H/2);
|
||||
return;
|
||||
}
|
||||
/* профиль */
|
||||
ctx.strokeStyle = col.bodyAccent || '#1e293b';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(profile[0].x, profile[0].y);
|
||||
for (let i = 1; i < profile.length; i++) ctx.lineTo(profile[i].x, profile[i].y);
|
||||
ctx.stroke();
|
||||
/* заливка под профилем */
|
||||
ctx.fillStyle = 'rgba(161,98,7,0.2)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(profile[0].x, H);
|
||||
for (let i = 0; i < profile.length; i++) ctx.lineTo(profile[i].x, profile[i].y);
|
||||
ctx.lineTo(profile[profile.length-1].x, H);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
/* шарик */
|
||||
if (ball.idx < profile.length - 1){
|
||||
const p1 = profile[ball.idx], p2 = profile[ball.idx+1];
|
||||
const px = p1.x + ball.fraction * (p2.x - p1.x);
|
||||
const py = p1.y + ball.fraction * (p2.y - p1.y) - 9;
|
||||
ctx.fillStyle = col.fail || '#dc2626';
|
||||
ctx.beginPath(); ctx.arc(px, py, 9, 0, Math.PI*2); ctx.fill();
|
||||
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
|
||||
}
|
||||
/* Линия энергии — горизонтальный уровень energy0 показывает потери при трении */
|
||||
if (ball.energy0 > 0){
|
||||
const m = +document.getElementById('F12-m').value;
|
||||
const g = 9.8;
|
||||
const E_height = ball.energy0 / (m*g); /* высота, эквивалентная энергии */
|
||||
const E_py = H - E_height / m_per_px;
|
||||
ctx.strokeStyle = '#10b981';
|
||||
ctx.setLineDash([8, 5]);
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath(); ctx.moveTo(0, E_py); ctx.lineTo(W, E_py); ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
ctx.fillStyle = '#10b981';
|
||||
ctx.font = 'bold 11px Inter,sans-serif';
|
||||
ctx.fillText('E₀ = ' + ball.energy0.toFixed(1) + ' Дж', 8, E_py - 4);
|
||||
/* линия потерь */
|
||||
if (ball.lossE > 0){
|
||||
const lossH = ball.lossE / (m*g);
|
||||
const py = E_py + lossH / m_per_px;
|
||||
ctx.strokeStyle = '#dc2626';
|
||||
ctx.setLineDash([4, 3]);
|
||||
ctx.beginPath(); ctx.moveTo(0, py); ctx.lineTo(W, py); ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
ctx.fillStyle = '#dc2626';
|
||||
ctx.fillText('потери: ' + ball.lossE.toFixed(1) + ' Дж', 8, py - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cv.addEventListener('mousedown', start);
|
||||
cv.addEventListener('mousemove', move);
|
||||
cv.addEventListener('mouseup', end);
|
||||
cv.addEventListener('mouseleave', end);
|
||||
cv.addEventListener('touchstart', start, {passive:false});
|
||||
cv.addEventListener('touchmove', move, {passive:false});
|
||||
cv.addEventListener('touchend', end);
|
||||
|
||||
document.getElementById('F12-go').addEventListener('click', ()=>{
|
||||
if (profile.length < 2) return;
|
||||
if (ball.idx >= profile.length - 1) reset();
|
||||
ball.running = !ball.running;
|
||||
document.getElementById('F12-go').textContent = ball.running ? 'Пауза' : 'Старт';
|
||||
});
|
||||
document.getElementById('F12-reset').addEventListener('click', reset);
|
||||
document.getElementById('F12-preset1').addEventListener('click', presetHill);
|
||||
document.getElementById('F12-preset2').addEventListener('click', presetLoop);
|
||||
document.getElementById('F12-clear').addEventListener('click', clear);
|
||||
['F12-mu','F12-m'].forEach(id => document.getElementById(id).addEventListener('input', () => {
|
||||
document.getElementById('F12-muv').textContent = (+document.getElementById('F12-mu').value).toFixed(2);
|
||||
document.getElementById('F12-mv').textContent = (+document.getElementById('F12-m').value).toFixed(1);
|
||||
if (!ball.running) reset();
|
||||
}));
|
||||
|
||||
presetHill();
|
||||
B().startLoop('F12', cv, tick);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F12', { init: init, cleanup: function(){} });
|
||||
else document.addEventListener('DOMContentLoaded', ()=>{
|
||||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F12', { init: init, cleanup: function(){} });
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -9,6 +9,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||
<link rel="stylesheet" href="/css/phys-textbook-widgets.css">
|
||||
<link rel="stylesheet" href="/css/phys9-flagships.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||||
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
|
||||
@@ -773,7 +774,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_CH3_WIDGETS && window.PHYS9_CH3_WIDGETS[id]) window.PHYS9_CH3_WIDGETS[id](); } catch(e){ console.warn('phys9 widget 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_CH3_WIDGETS && window.PHYS9_CH3_WIDGETS[id]) window.PHYS9_CH3_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p29') window.PHYS9_FLAG_BASE.mount('F10','p29'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60);
|
||||
}
|
||||
var _origEnsureBuilt = ensureBuilt;
|
||||
ensureBuilt = function(id){ _origEnsureBuilt(id); _injectTasks(id); };
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||
<link rel="stylesheet" href="/css/phys-textbook-widgets.css">
|
||||
<link rel="stylesheet" href="/css/phys9-flagships.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||||
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
|
||||
@@ -773,7 +774,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_CH4_WIDGETS && window.PHYS9_CH4_WIDGETS[id]) window.PHYS9_CH4_WIDGETS[id](); } catch(e){ console.warn('phys9 widget 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_CH4_WIDGETS && window.PHYS9_CH4_WIDGETS[id]) window.PHYS9_CH4_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p35') window.PHYS9_FLAG_BASE.mount('F12','p35'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60);
|
||||
}
|
||||
var _origEnsureBuilt = ensureBuilt;
|
||||
ensureBuilt = function(id){ _origEnsureBuilt(id); _injectTasks(id); };
|
||||
|
||||
Reference in New Issue
Block a user