feat(labs): planimetry locus + emfield merger + projectile graphs + UI cleanup

Геометрия (планиметрия):
- Живые измерения как объекты: длина / угол / площадь — auto-recompute, draggable chips
- Инструмент ГМТ: sweep мовера через параметр, рисует кривую места точек
- Новые типы точек: on_segment (скользит по отрезку, _t), on_circle (по окружности, _theta)
- Toolbar: «Длина», «Угол», «Площадь», «ГМТ», «На отрезке», «На окружности»

Электромагнитные поля (emfield):
- Merge magnetic.js + coulomb.js в один EMFieldSim с 3 режимами (E / B / комбинированное)
- Унифицированный pipeline: colormap, field lines, vectors, equipotentials, flux loop, test particle
- Combined-режим: полная сила Лоренца F=q(E+v×B)
- Backward compat: #coulomb и #magnetic хеши и ?sim= параметры редиректят в emfield
- Удалены: magnetic.js, coulomb.js. Добавлен: emfield.js

Бросок тела (projectile):
- Режим целей: 3 окна, hit-детекция, HUD «Цели: N/M / Попыток: K»
- Графики x(t), y(t), vx(t), vy(t) — 2×2 Canvas 2D, real-time
- Двойной бросок: одновременно 2 траектории для сравнения (cyan vs gold)

UI fixes (по результатам аудита):
- Заменены emoji/unicode на inline SVG .ic: switch ⌇, spring 〜 (5 мест), download ⬇ (2), camera 📷
- Убраны декоративные символы ☉ ○ из geometry tool labels
- Добавлены THEORY entries: geometry, hydrostatics (раньше показывали fallback)
- Стандартизирована ширина panel для sim-proj и sim-coll (240px)
- waves перенесён в физический блок SIMS catalog (был после биологии)
- Очищен дефолтный sim-topbar-title (был «График функции»)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-23 12:09:44 +03:00
parent 085b7322cf
commit 7f75c96acd
11 changed files with 3037 additions and 2239 deletions
+45 -6
View File
@@ -31,18 +31,18 @@
var wavesSim = null;
var geomSim = null;
var ALL_SIM_BODIES = ['sim-graph','sim-proj','sim-coll','sim-tri','sim-trigcircle','sim-mag',
var ALL_SIM_BODIES = ['sim-graph','sim-proj','sim-coll','sim-tri','sim-trigcircle','sim-emfield',
'sim-molphys',
'sim-coulomb','sim-circuit','sim-chemistry','sim-dynamics',
'sim-circuit','sim-chemistry','sim-dynamics',
'sim-crystal','sim-orbitals','sim-stereo','sim-chemsandbox',
'sim-celldivision','sim-photosynthesis','sim-angrybirds',
'sim-quadratic','sim-normaldist','sim-graphtransform',
'sim-pendulum','sim-equilibrium','sim-thinlens','sim-titration',
'sim-refraction','sim-mirrors','sim-isoprocess','sim-probability','sim-bohratom','sim-electrolysis',
'sim-waves','sim-hydro','sim-geometry'];
var ALL_CTRL_BARS = ['ctrl-graph','ctrl-proj','ctrl-coll','ctrl-tri','ctrl-trigcircle','ctrl-mag',
var ALL_CTRL_BARS = ['ctrl-graph','ctrl-proj','ctrl-coll','ctrl-tri','ctrl-trigcircle','ctrl-emfield',
'ctrl-molphys',
'ctrl-coulomb','ctrl-circuit','ctrl-chemistry','ctrl-dynamics','ctrl-chemsandbox',
'ctrl-circuit','ctrl-chemistry','ctrl-dynamics','ctrl-chemsandbox',
'ctrl-celldivision','ctrl-photosynthesis','ctrl-angrybirds','ctrl-waves','ctrl-hydro',
'ctrl-geometry'];
@@ -65,10 +65,12 @@
if (id === 'collision') _openCollision();
if (id === 'triangle') _openTriangle();
if (id === 'trigcircle') _openTrigCircle();
if (id === 'magnetic') _openMagnetic();
if (id === 'magnetic') _openEMField('B'); // backward compat: #magnetic → emfield B-mode
if (id === 'coulomb') _openEMField('E'); // backward compat: #coulomb → emfield E-mode
if (id === 'emfield') _openEMField('E');
if (id.startsWith('emfield:')) { _openEMField(id.split(':')[1]); }
if (id === 'molphys') _openMolPhys();
if (id.startsWith('molphys:')) { _openMolPhys(id.split(':')[1]); }
if (id === 'coulomb') _openCoulomb();
if (id === 'circuit') _openCircuit();
if (id === 'chemistry') _openChemistry();
if (id.startsWith('chemistry:')) { _openChemistry(id.split(':')[1]); }
@@ -222,6 +224,19 @@
{ head: 'Коэффициент восстановления', formula: 'e = \\frac{v_2\' - v_1\'}{v_1 - v_2}', text: 'e=1 — упругий, e=0 — абсолютно неупругий удар.' },
]
},
emfield: {
title: 'Электромагнитные поля',
sections: [
{ head: 'Закон Кулона', formula: 'F = k \\frac{|q_1 q_2|}{r^2}', vars: [['k','8.99·10⁹ Н·м²/Кл²'],['q','заряд, Кл'],['r','расстояние, м']] },
{ head: 'Напряжённость E', formula: '\\vec{E} = k \\frac{q}{r^2} \\hat{r}', text: 'Вектор направлен от «+» и к «−» заряду.' },
{ head: 'Потенциал', formula: '\\varphi = k \\frac{q}{r}', text: 'Эквипотенциальные линии — окружности вокруг заряда.' },
{ head: 'Поле прямого тока', formula: 'B = \\frac{\\mu_0 I}{2\\pi r}', vars: [['μ₀','4π·10⁻⁷ Тл·м/А'],['I','сила тока, А'],['r','расстояние от провода, м']] },
{ head: 'Суперпозиция B', formula: '\\vec{B} = \\sum_i \\vec{B}_i', text: 'Результирующее поле — векторная сумма полей всех проводов.' },
{ head: 'Сила Лоренца', formula: '\\vec{F} = q(\\vec{E} + \\vec{v} \\times \\vec{B})', text: 'Полная электромагнитная сила на движущийся заряд.' },
{ head: 'Сила Ампера', formula: 'F = I L B \\sin\\theta', text: 'Сила на проводник с током в магнитном поле.' },
]
},
/* backward-compat aliases — loadTheory() maps these to emfield */
magnetic: {
title: 'Магнитное поле',
sections: [
@@ -275,6 +290,30 @@
{ head: 'Теорема Пифагора', formula: 'a^2 + b^2 = c^2', text: 'В прямоугольном треугольнике квадрат гипотенузы равен сумме квадратов катетов.' },
]
},
geometry: {
title: 'Планиметрия',
sections: [
{ head: 'Базовые объекты', text: 'Точка, прямая, луч, отрезок, окружность, многоугольник — основные фигуры планиметрии. Каждая прямая однозначно задаётся двумя точками.' },
{ head: 'Параллельность и перпендикулярность', text: 'Прямые параллельны, если не пересекаются. Перпендикулярны — если угол между ними 90°.' },
{ head: 'Теорема Фалеса', text: 'Если на одной из двух прямых отложить равные отрезки и провести через их концы параллельные прямые, они высекут равные отрезки и на второй прямой.' },
{ head: 'Признаки подобия треугольников', text: 'По двум углам, по двум пропорциональным сторонам и углу между ними, по трём пропорциональным сторонам.' },
{ head: 'Площадь треугольника', formula: 'S = \\frac{1}{2} a h_a', text: 'Также S = ½·a·b·sin C; формула Герона: S = √(p(p-a)(p-b)(p-c)).' },
{ head: 'Площадь параллелограмма', formula: 'S = a h_a = a b \\sin\\alpha' },
{ head: 'Длина окружности', formula: 'C = 2\\pi r', text: 'Площадь круга: S = π·r².' },
{ head: 'Геометрическое место точек (ГМТ)', text: 'Множество точек, удовлетворяющих заданному условию. Эллипс — ГМТ, сумма расстояний от которых до двух фокусов постоянна. Окружность — ГМТ, равноудалённых от центра.' },
]
},
hydrostatics: {
title: 'Гидростатика',
sections: [
{ head: 'Гидростатическое давление', formula: 'P = \\rho g h', vars: [['ρ','плотность жидкости, кг/м³'],['g','ускорение свободного падения, 9.81 м/с²'],['h','глубина под поверхностью, м']] },
{ head: 'Закон Паскаля', text: 'Давление в покоящейся жидкости передаётся одинаково во все стороны. Основа гидравлического пресса: F₁/S₁ = F₂/S₂.' },
{ head: 'Закон Архимеда', formula: 'F_A = \\rho_{ж} g V_{погр}', text: 'Сила, выталкивающая тело из жидкости, равна весу вытесненной жидкости. Условие плавания: ρ_тела ≤ ρ_жидкости.' },
{ head: 'Сообщающиеся сосуды', text: 'Уровни однородной жидкости в сообщающихся сосудах одинаковы. Для двух разных жидкостей: ρ₁·h₁ = ρ₂·h₂.' },
{ head: 'Поверхностное натяжение', formula: '\\sigma = \\frac{F}{l}', text: 'Сила, действующая по касательной к поверхности жидкости на единицу длины. Капиллярная высота: h = 2σ·cos θ / (ρ g r).' },
{ head: 'Капиллярность', text: 'В тонких трубках жидкость поднимается (смачивает) или опускается (не смачивает) относительно общего уровня. Зависит от угла смачивания θ.' },
]
},
molphys: {
title: 'Молекулярная физика',
sections: [