// F1. Конструктор траектории (§5) — рисуй мышкой, видишь s vs |Δr|. (function(){ 'use strict'; const B = () => window.PHYS9_FLAG_BASE; const C = () => window.PHYS9_COLORS || {}; function init(secId){ if (!B()) return false; const body = '' + '' + '
' + '' + '' + '' + '' + '' + '
' + '
' + '
Путь $s$0
' + '
r| перемещение0
' + '
Отношение $s/|Δ r|$
' + '
' + '
'; const card = B().makeCard(secId, 'F1. Конструктор траектории', 'Нарисуй мышкой/пальцем кривую — посмотри, как соотносятся путь $s$ и перемещение $|Δ\\vec r|$. Они равны только для прямой.', body); if (!card) return false; const cv = document.getElementById('F1-cv'); const ctx = cv.getContext('2d'); let points = []; /* [{x, y}] */ let drawing = false; function getPos(e){ const rect = cv.getBoundingClientRect(); const sx = cv.width / rect.width; const sy = cv.height / rect.height; const tx = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left; const ty = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top; return { x: tx * sx, y: ty * sy }; } function calc(){ let s = 0; for (let i = 1; i < points.length; i++){ s += Math.hypot(points[i].x - points[i-1].x, points[i].y - points[i-1].y); } let dr = 0; if (points.length >= 2) { const a = points[0], b = points[points.length-1]; dr = Math.hypot(b.x - a.x, b.y - a.y); } /* px → м: считаем что canvas 600px = 6 м */ const m_per_px = 6 / 600; return { s: s * m_per_px, dr: dr * m_per_px }; } function draw(){ ctx.clearRect(0, 0, cv.width, cv.height); /* сетка */ const col = C(); ctx.strokeStyle = col.grid || '#e5e7eb'; ctx.lineWidth = 1; for (let x = 0; x < cv.width; x += 50){ ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, cv.height); ctx.stroke(); } for (let y = 0; y < cv.height; y += 50){ ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(cv.width, y); ctx.stroke(); } if (points.length === 0){ ctx.fillStyle = col.textMuted || '#64748b'; ctx.font = '15px Inter,sans-serif'; ctx.fillText('Нажми и проведи курсором/пальцем — нарисуй траекторию', 60, cv.height/2); return; } /* траектория */ ctx.strokeStyle = col.velocity || '#0891b2'; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y); ctx.stroke(); /* перемещение */ if (points.length >= 2){ const a = points[0], b = points[points.length-1]; ctx.strokeStyle = col.displacement || '#2563eb'; ctx.lineWidth = 2.5; ctx.setLineDash([8, 5]); ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); ctx.setLineDash([]); /* стрелка перемещения */ B().arrow(ctx, a.x, a.y, b.x, b.y, col.displacement || '#2563eb', 2.5); /* точки start/end */ ctx.fillStyle = '#10b981'; ctx.beginPath(); ctx.arc(a.x, a.y, 7, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#dc2626'; ctx.beginPath(); ctx.arc(b.x, b.y, 7, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = col.text || '#0f172a'; ctx.font = 'bold 13px Inter,sans-serif'; ctx.fillText('старт', a.x + 10, a.y - 8); ctx.fillText('конец', b.x + 10, b.y - 8); } /* обновить статы */ const r = calc(); document.getElementById('F1-s').textContent = r.s.toFixed(2) + ' м'; document.getElementById('F1-dr').textContent = r.dr.toFixed(2) + ' м'; const ratio = r.dr > 0.01 ? (r.s / r.dr).toFixed(2) : '∞'; document.getElementById('F1-ratio').textContent = ratio; /* feedback */ const fb = document.getElementById('F1-fb'); if (r.dr > 0.01 && Math.abs(r.s/r.dr - 1) < 0.05){ fb.className = 'flag-feedback ok show'; fb.innerHTML = '✓ Прямая траектория: $s = |Δ\\vec r|$ — это единственный случай равенства!'; } else if (r.dr < 0.05 && r.s > 0.1){ fb.className = 'flag-feedback warn show'; fb.innerHTML = 'Замкнутая или почти замкнутая кривая: $|Δ\\vec r| \\to 0$, но путь $s$ остался.'; } else if (r.s > 0.1) { fb.className = 'flag-feedback ok show'; fb.innerHTML = 'Криволинейное движение: $s > |Δ\\vec r|$ всегда (в '+ratio+' раз больше).'; } else { fb.className = 'flag-feedback'; } try { if(window.renderMathInElement) window.renderMathInElement(card, { delimiters:[{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} } function start(e){ drawing = true; points = [getPos(e)]; draw(); e.preventDefault(); } function move(e){ if (!drawing) return; const p = getPos(e); const last = points[points.length-1]; if (Math.hypot(p.x - last.x, p.y - last.y) > 3) { points.push(p); draw(); } e.preventDefault(); } function end(){ drawing = false; } 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('F1-clear').addEventListener('click', ()=>{ points = []; draw(); }); document.getElementById('F1-close').addEventListener('click', ()=>{ if (points.length < 2) return; points.push(Object.assign({}, points[0])); draw(); }); document.getElementById('F1-line').addEventListener('click', ()=>{ points = [{x:80, y:160}, {x:520, y:160}]; draw(); }); document.getElementById('F1-arc').addEventListener('click', ()=>{ points = []; const cx = 300, cy = 250, r = 180; for (let a = Math.PI; a >= 0; a -= 0.05) points.push({ x: cx + r*Math.cos(a), y: cy - r*Math.sin(a)*0.6 }); draw(); }); document.getElementById('F1-circle').addEventListener('click', ()=>{ points = []; const cx = 300, cy = 160, r = 110; for (let a = 0; a <= 2*Math.PI + 0.05; a += 0.05) points.push({ x: cx + r*Math.cos(a), y: cy + r*Math.sin(a)*0.7 }); draw(); }); draw(); return true; } if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F1', { init: init, cleanup: function(){} }); else { /* base ещё не загружен — отложить регистрацию */ document.addEventListener('DOMContentLoaded', ()=>{ if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F1', { init: init, cleanup: function(){} }); }); } })();