refactor: distribute lab-init.js into 34 engine files

lab-init.js: 4098 -> 543 lines (infrastructure + THEORY only)

Each sim's _open*() + UI helpers moved to its engine file:
graph.js, projectile.js, collision.js, magnetic.js, triangle.js,
geometry.js, trigcircle.js, gas.js (molphys), coulomb.js, circuit.js,
reactions.js (chemistry), newton.js (dynamics), chemsandbox.js,
celldivision.js, photosynthesis.js, angrybirds.js, quadratic.js,
normaldist.js, graphtransform.js, pendulum.js, equilibrium.js,
thinlens.js, mirror.js, isoprocess.js, titration.js, refraction.js,
probability.js, bohratom.js, electrolysis.js, waves.js,
crystal.js, orbitals.js, stereo.js, hydrostatics.js

All 34 engine files syntax-checked OK.
This commit is contained in:
Maxim Dolgolyov
2026-05-08 14:54:54 +03:00
parent d5f77bb648
commit ae31e4c4e8
35 changed files with 3657 additions and 3589 deletions
+156 -1
View File
@@ -1,4 +1,4 @@
'use strict';
'use strict';
/* Strip SVG markup for canvas fillText — replaces icon SVGs with Unicode */
function _csClean(s) {
@@ -1680,3 +1680,158 @@ class ChemSandboxSim {
if (this.onUpdate) this.onUpdate(this.info());
}
}
/* ─── lab UI init ─────────────────────────────────── */
function _openChemSandbox() {
document.getElementById('sim-topbar-title').textContent = 'Химическая песочница';
_simShow('sim-chemsandbox');
_simShow('ctrl-chemsandbox');
requestAnimationFrame(() => requestAnimationFrame(() => {
const c = document.getElementById('chemsandbox-canvas');
if (!chemSandSim) {
chemSandSim = new ChemSandboxSim(c);
chemSandSim.onUpdate = _chemSandUpdateUI;
chemSandSim.onQuizUpdate = _chemSandQuizUI;
c.addEventListener('click', e => chemSandSim.handleClick(e));
c.addEventListener('mousedown', e => chemSandSim.handleMouseDown(e));
c.addEventListener('mousemove', e => chemSandSim.handleMouseMove(e));
c.addEventListener('mouseup', e => chemSandSim.handleMouseUp(e));
c.addEventListener('wheel', e => chemSandSim.handleWheel(e), { passive: false });
c.addEventListener('contextmenu', e => chemSandSim.handleContextMenu(e));
_addTouchSupport(c, chemSandSim);
_chemSandBuildReagents('all');
}
chemSandSim.fit();
chemSandSim.start();
chemSandSim.draw();
}));
}
function chemSandCat(cat, el) {
document.querySelectorAll('.chemsand-cat').forEach(b => b.classList.remove('active'));
el.classList.add('active');
if (chemSandSim) chemSandSim.setCategory(cat);
_chemSandBuildReagents(cat);
if (chemSandSim) chemSandSim.draw();
}
function chemSandPreset(name) { if (chemSandSim) { chemSandSim.preset(name); _chemSandBuildReagents(chemSandSim.filterCat); } }
function chemSandReset() { if (chemSandSim) { chemSandSim.reset(); _chemSandBuildReagents(chemSandSim.filterCat); } }
function chemSandResetReaction() { if (chemSandSim) { chemSandSim.resetReaction(); _chemSandBuildReagents(chemSandSim.filterCat); } }
function chemSandConcChange() {
const v = +document.getElementById('sl-csand-conc').value;
document.getElementById('csand-conc-val').textContent = v + '%';
}
function chemSandTempChange() {
const v = +document.getElementById('sl-csand-temp').value;
document.getElementById('csand-temp-val').textContent = v + '°C';
}
function chemSandAdd(formula) {
if (!chemSandSim) return;
// toggle: if already in mix — remove, else add
if (chemSandSim.mixContents.includes(formula)) {
chemSandSim.removeFromMix(formula);
} else {
chemSandSim.addToMix(formula);
}
_chemSandBuildReagents(chemSandSim.filterCat);
}
function _chemSandBuildReagents(cat) {
const box = document.getElementById('chemsand-reagents');
if (!box) return;
const subs = ChemSandboxSim.SUBSTANCES;
const keys = Object.keys(subs).filter(k => cat === 'all' || subs[k].cat === cat);
const inMix = chemSandSim ? chemSandSim.mixContents : [];
box.innerHTML = keys.map(k => {
const s = subs[k];
const active = inMix.includes(k);
const cls = active ? 'proj-preset-chip reac-mode-btn active' : 'proj-preset-chip reac-mode-btn';
const sf = chemSandSim ? chemSandSim._shortFormula(k) : k;
const removeHint = active ? ' (клик — убрать)' : '';
return `<button class="${cls}" onclick="chemSandAdd('${k}')" title="${s.name}${removeHint}" style="font-size:.68rem;padding:4px 7px">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${s.color};margin-right:3px;vertical-align:middle"></span>${sf}${active ? ' ×' : ''}</button>`;
}).join('');
}
function chemSandSetMode(mode, el) {
document.querySelectorAll('.chemsand-mode').forEach(b => b.classList.remove('active'));
if (el) el.classList.add('active');
if (!chemSandSim) return;
if (mode === 'quiz') {
if (window._simQuizAllowed === false) {
LS.toast('Режим заданий недоступен — администратор ограничил доступ', 'error');
// revert button state
document.querySelectorAll('.chemsand-mode').forEach(b => b.classList.remove('active'));
document.getElementById('csand-mode-free')?.classList.add('active');
return;
}
chemSandSim.startQuiz();
// reset category filter to 'all' so all reagents are accessible
document.querySelectorAll('.chemsand-cat').forEach(b => b.classList.remove('active'));
const allBtn = document.querySelector('.chemsand-cat');
if (allBtn) allBtn.classList.add('active');
_chemSandBuildReagents('all');
} else {
chemSandSim.stopQuiz();
document.getElementById('csand-quiz-question').style.display = 'none';
document.getElementById('csand-quiz-result').style.display = 'none';
document.getElementById('csand-quiz-next').style.display = 'none';
document.getElementById('csand-quiz-score').textContent = '';
}
}
function chemSandQuizNext() {
if (chemSandSim && chemSandSim._quizMode) {
chemSandSim._nextQuizTask();
_chemSandBuildReagents(chemSandSim.filterCat);
}
}
function _chemSandQuizUI(qi) {
const qEl = document.getElementById('csand-quiz-question');
const rEl = document.getElementById('csand-quiz-result');
const nEl = document.getElementById('csand-quiz-next');
const sEl = document.getElementById('csand-quiz-score');
if (!qi.active) {
qEl.style.display = 'none'; rEl.style.display = 'none'; nEl.style.display = 'none';
sEl.textContent = '';
return;
}
qEl.style.display = 'block';
qEl.textContent = qi.question || '';
sEl.textContent = qi.total > 0 ? `${qi.score}/${qi.total}` : '';
if (qi.result) {
rEl.style.display = 'block';
rEl.style.color = qi.result === 'correct' ? '#7BF5A4' : '#EF476F';
rEl.textContent = qi.result === 'correct' ? 'Верно!' : 'Неверно — ' + (qi.answer || '');
nEl.style.display = qi.result === 'wrong' ? 'inline-block' : 'none';
} else {
rEl.style.display = 'none'; nEl.style.display = 'none';
}
}
let _lastReportedEquation = null;
function _chemSandUpdateUI(info) {
document.getElementById('csbar-v1').textContent = info.mixed;
document.getElementById('csbar-v3').textContent = info.type || '—';
const eqEl = document.getElementById('csbar-v4');
eqEl.innerHTML = info.equation || '—';
eqEl.title = (info.equation || '').replace(/<[^>]*>/g, '');
document.getElementById('csbar-v5').textContent = info.products || '—';
const ionEl = document.getElementById('csbar-v6');
ionEl.innerHTML = info.ionNet || '—';
ionEl.title = (info.ionNet || '').replace(/<[^>]*>/g, '');
// rebuild reagent buttons to reflect active state
_chemSandBuildReagents(chemSandSim ? chemSandSim.filterCat : 'all');
// Report lab activity for gamification (once per unique reaction)
if (info.reaction && info.equation && info.equation !== _lastReportedEquation) {
_lastReportedEquation = info.equation;
if (window.LS?.reportLabActivity) LS.reportLabActivity(1).catch(() => {});
}
}
/* ── Cell Division ── */