feat(labs): wave 2 — depth features across 6 sims

Электрические цепи (circuit):
- Индуктивность L как новый компонент (1–1000 мГн, шорт в DC, jωL в AC)
- RLC preset для демонстрации резонанса
- Осциллограф: U(t)/I(t) для выбранного компонента, 100 sample, dual-axis
- Heatmap мощности: радиальный градиент halo от blue→red пропорционально P=UI

Стереометрия 3D (stereo):
- Сечение через 3 произвольные точки: pick на гранях/рёбрах/вершинах
- Плоскость + полигон пересечения с авто-определением типа (3–6-угольник) и площадью
- Step-by-step режим: визуализация P1→линия→P2→линия→P3→плоскость→сечение
- Поддержка всех solids (включая cylinder/cone через sampling fallback)

Планиметрия (geometry):
- Задачник framework: CHALLENGES[] с setup/check функциями
- 5 стартовых задач: серединный перпендикуляр, биссектриса, описанная окружность, ГМТ, касательная
- Авто-checker: толерантности ±0.5° для углов, ±1–5% для расстояний
- UI: collapsible панель с статус-иконками, конфетти + «Молодец!» на success

Электромагнитные поля (emfield):
- Preset «Тороид»: 16+16 проводов в концентрических кольцах
- Поверхность Гаусса: draggable круг, считает Φ = q_enc/ε₀, подсвечивает охваченные заряды
- Motional EMF: draggable rod, arrow-keys управление, считает ε = ∫(v×B)·dl

Химическая песочница (chemsandbox):
- Live-overlay с уравнением реакции: молекулярное / полное ионное / сокращённое ионное
- Coverage: 49/49 молекулярных, 34/49 ионных, 36/49 сокращённых
- Auto-hide через 5 сек, fade-in animation, цветовая кодировка типов

Волны и звук (waves):
- Doppler: source+observer drag, expanding wavefronts, f_obs формула, Mach cone при v>c
- Beats: f1+f2, sum waveform с envelope, индикация f_beat=|f1-f2|
- Spectrum (DFT): N=256 samples pure JS, bar-chart с пиками и labels, «Добавить гармонику»

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-23 12:48:14 +03:00
parent 7f75c96acd
commit 8f30a8cef6
8 changed files with 2367 additions and 36 deletions
+54 -1
View File
@@ -500,7 +500,8 @@ class ChemSandboxSim {
type: r ? r.type : null,
equation: r ? r.eq : null,
products: r && !r.fx.none ? this._productsStr(r) : null,
ionNet: r ? r.ionNet || null : null,
ionFull: r ? r.ionFull || null : null,
ionNet: r ? r.ionNet || null : null,
why: r ? r.why || null : null,
};
}
@@ -1834,6 +1835,58 @@ class ChemSandboxSim {
_lastReportedEquation = info.equation;
if (window.LS?.reportLabActivity) LS.reportLabActivity(1).catch(() => {});
}
// ── HTML overlay: 3-form equation display ──
_chemSandShowEqOverlay(info);
}
/* equation overlay timer handle */
let _csEqOverlayTimer = null;
function _chemSandShowEqOverlay(info) {
const overlay = document.getElementById('chemsand-eq-overlay');
if (!overlay) return;
// clear any existing hide timer
if (_csEqOverlayTimer) { clearTimeout(_csEqOverlayTimer); _csEqOverlayTimer = null; }
if (!info.reaction || !info.equation) {
overlay.classList.remove('visible');
return;
}
/* helpers: strip SVG arrow markup → plain text "=" */
function _stripSvg(s) {
if (!s) return '';
return s.replace(/<svg[^>]*class="ic"[^>]*>[\s\S]*?<\/svg>/g, '=');
}
const molLine = document.getElementById('chemsand-eq-mol');
const fullLine = document.getElementById('chemsand-eq-full');
const netLine = document.getElementById('chemsand-eq-net');
const typeBadge = document.getElementById('chemsand-eq-type');
molLine.innerHTML = _stripSvg(info.equation);
fullLine.innerHTML = info.ionFull ? _stripSvg(info.ionFull) : '<span style="opacity:.45">ионное уравнение недоступно</span>';
netLine.innerHTML = info.ionNet ? _stripSvg(info.ionNet) : '<span style="opacity:.45">сокращённое ионное недоступно</span>';
const tpColor = info.type === 'Нет реакции' ? '#EF476F'
: info.type === 'Индикатор' ? '#9B59B6'
: info.type === 'Нейтрализация'? '#FFD166'
: info.type === 'Замещение' ? '#06D6E0'
: info.type === 'Обмен' ? '#7BF5A4'
: info.type === 'Акт. металл' ? '#EF476F'
: 'rgba(255,255,255,.5)';
typeBadge.textContent = info.type || '';
typeBadge.style.color = tpColor;
typeBadge.style.borderColor = tpColor + '55';
overlay.classList.add('visible');
/* auto-hide after 5 s */
_csEqOverlayTimer = setTimeout(() => {
overlay.classList.remove('visible');
_csEqOverlayTimer = null;
}, 5000);
}
/* ── Cell Division ── */