Files
Learn_System/frontend/js/admin/sections/sims.js
T
Maxim Dolgolyov ea2526dc73 feat(labs): 4 школьные хим. симы + визуальная прокачка лаборатории
4 НОВЫЕ СИМЫ (школьная программа 8-11 классов):

Органика (organic.js, 1545 строк):
- Конструктор молекул: drag атомов C/H/O/N/Cl/S, валентности, click-pair bonds
- Авто-определение класса: алкан/алкен/алкин/спирт/альдегид/кислота/эфир/амин/аромат
- IUPAC-имена для C1-C10
- Гомологические ряды: 7 рядов с slider количества углеродов, M, T_кип, T_пл
- 6 качественных реакций: Br₂ вода, KMnO₄, Ag₂O/NH₃ (серебряное зеркало), Cu(OH)₂, FeCl₃, I₂

Периодическая таблица (periodic.js, 118 элементов):
- Стандартный вид 18×9 + лантаноиды/актиноиды
- Карточка элемента: Z, M, конфигурация, степени окисления, ЭО, ρ, T_пл/T_кип
- Боровская модель электронных оболочек (анимированная)
- Подсветка: 11 типов / s/p/d/f-блоки / без подсветки
- Графики свойств по периоду/группе (ЭО, M, плотность, T_пл/T_кип)
- Поиск по символу/имени/Z/массе

Качественный анализ (qualanalysis.js, 24 иона):
- 15 катионов: Na/K/NH₄/Mg/Ca/Ba/Al/Fe²⁺/Fe³⁺/Cu/Ag/Pb/Zn/H/OH
- 10 анионов: Cl/Br/I/SO₄/SO₃/CO₃/NO₃/PO₄/S²/CH₃COO
- 9 реактивов + пламя
- 2 режима: «определи ион» и «неизвестное вещество» с логом наблюдений
- Анимация капли, осадка с цветом, газовых пузырей, пламени

Растворы (solutions.js, 4 режима):
- Калькулятор: m_в, m_р-ра, ρ, T → ω, ν, C_М, C_Н с понятной логикой пересчёта
- Разбавление с before/after визуализацией
- Смешивание двух растворов с правилом рычага
- Кривые растворимости 8 веществ + задача перекристаллизации
- 15 пресетов веществ (NaCl, NaOH, H₂SO₄, CuSO₄·5H₂O, глюкоза, сахароза, ...)

ВИЗУАЛЬНАЯ ПРОКАЧКА (_chem_visuals.js, helper file):

12 функций школьной лабораторной графики:
- drawErlenmeyer / drawBeaker / drawBurette / drawTube — proper SVG-paths со шкалой
- drawSpiritLamp — стеклянный резервуар + фитиль + анимированное пламя
- animateGasBubbles / animatePrecipitateFall — анимация продуктов
- drawProductLabel — fade-in/out стрелка ↑/↓ с подписью
- drawEduTooltip — bubble с пояснением реакции
- drawDeskBackground / drawVesselShadow — лабораторный фон
- drawPHStrip — pH-индикаторная полоса с маркером

Прокачено 6 chem-сим: chemsandbox, flask, titration, electrolysis, ionexchange, redox
Каждая получила: фон парты, тени под колбами, анимированные стрелки продуктов,
educational tooltips из поля 'why' реакции. Спиртовка с пламенем в flask.
pH-полоса в titration.

Каталог теперь: 39 симуляций (было 35 + 4 новых).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:08:35 +03:00

124 lines
7.1 KiB
JavaScript

'use strict';
/* admin → sims (simulations) section */
(function () {
'use strict';
let inited = false;
// Full list of available (non-null id) sims mirrored from /lab
const ADMIN_SIMS = [
{ id: 'graph', cat: 'Математика', title: 'График функции' },
{ id: 'graphtransform', cat: 'Математика', title: 'Трансформации графиков' },
{ id: 'geometry', cat: 'Математика', title: 'Планиметрия' },
{ id: 'triangle', cat: 'Математика', title: 'Геометрия треугольника' },
{ id: 'quadratic', cat: 'Математика', title: 'Корни квадратного уравнения' },
{ id: 'stereo', cat: 'Математика', title: 'Стереометрия 3D' },
{ id: 'probability', cat: 'Математика', title: 'Теория вероятностей' },
{ id: 'trigcircle', cat: 'Математика', title: 'Тригонометрическая окружность' },
{ id: 'normaldist', cat: 'Математика', title: 'Нормальное распределение' },
{ id: 'projectile', cat: 'Физика', title: 'Бросок тела' },
{ id: 'pendulum', cat: 'Физика', title: 'Маятник' },
{ id: 'collision', cat: 'Физика', title: 'Столкновение шаров' },
{ id: 'emfield', cat: 'Физика', title: 'Электромагнитные поля' },
{ id: 'circuit', cat: 'Физика', title: 'Электрические цепи' },
{ id: 'hydrostatics', cat: 'Физика', title: 'Гидростатика' },
{ id: 'dynamics', cat: 'Физика', title: 'Динамика' },
{ id: 'opticsbench', cat: 'Физика', title: 'Оптическая скамья' },
{ id: 'isoprocess', cat: 'Физика', title: 'Изопроцессы' },
{ id: 'waves', cat: 'Физика', title: 'Волны и звук' },
{ id: 'heatengine', cat: 'Физика', title: 'Тепловые двигатели' },
{ id: 'radioactive', cat: 'Физика', title: 'Радиоактивный распад' },
{ id: 'logic', cat: 'Физика', title: 'Логические схемы' },
{ id: 'molphys', cat: 'Химия', title: 'Молекулярная физика' },
{ id: 'chemistry', cat: 'Химия', title: 'Химические реакции' },
{ id: 'equilibrium', cat: 'Химия', title: 'Химическое равновесие' },
{ id: 'electrolysis', cat: 'Химия', title: 'Электролиз' },
{ id: 'bohratom', cat: 'Химия', title: 'Атом Бора' },
{ id: 'orbitals', cat: 'Химия', title: 'Молекулярные орбитали' },
{ id: 'titration', cat: 'Химия', title: 'pH и кривая титрования' },
{ id: 'chemsandbox', cat: 'Химия', title: 'Химическая песочница' },
{ id: 'stoichiometry', cat: 'Химия', title: 'Стехиометрия' },
{ id: 'crystal', cat: 'Химия', title: 'Кристаллическая решётка' },
{ id: 'qualanalysis', cat: 'Химия', title: 'Качественный анализ' },
{ id: 'periodic', cat: 'Химия', title: 'Периодическая таблица' },
{ id: 'organic', cat: 'Химия', title: 'Органическая химия' },
{ id: 'solutions', cat: 'Химия', title: 'Растворы' },
{ id: 'celldivision', cat: 'Биология', title: 'Деление клетки' },
{ id: 'photosynthesis', cat: 'Биология', title: 'Фотосинтез и дыхание' },
{ id: 'angrybirds', cat: 'Игры', title: 'Angry Birds Physics' },
];
let _simsSettings = { module_disabled: false, disabled_ids: [] };
async function load() {
try {
const data = await LS.api('/api/settings/sims');
_simsSettings = data;
_render();
} catch(e) { LS.toast('Ошибка загрузки настроек: ' + e.message, 'error'); }
}
function _render() {
// master toggle
const masterChk = document.getElementById('sims-master-chk');
if (masterChk) masterChk.checked = !_simsSettings.module_disabled;
// per-sim cards
const grid = document.getElementById('sims-grid');
const dis = new Set(_simsSettings.disabled_ids || []);
// group by category
const byCat = {};
ADMIN_SIMS.forEach(s => { (byCat[s.cat] = byCat[s.cat] || []).push(s); });
let html = '';
Object.entries(byCat).forEach(([cat, sims]) => {
html += `<div style="grid-column:1/-1;font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--text-3);margin-top:12px;margin-bottom:2px">${esc(cat)}</div>`;
sims.forEach(s => {
const enabled = !dis.has(s.id);
html += `<div class="perm-card${enabled ? ' enabled' : ''}" id="simcard-${s.id}">
<div class="perm-info">
<div class="perm-label">${esc(s.title)}</div>
<div class="perm-desc" style="font-size:11px;margin-top:2px;opacity:.7">${esc(s.id)}</div>
</div>
<label class="perm-toggle" title="${enabled ? 'Отключить' : 'Включить'}">
<input type="checkbox" ${enabled ? 'checked' : ''} onchange="simToggleOne('${s.id}', this.checked)" />
<span class="perm-track"></span>
<span class="perm-thumb"></span>
</label>
</div>`;
});
});
grid.innerHTML = html;
if (window.lucide) lucide.createIcons();
}
async function simsMasterToggle(checked) {
try {
await LS.api('/api/settings/sims', { method: 'PUT', body: JSON.stringify({ module_disabled: !checked }) });
_simsSettings.module_disabled = !checked;
LS.toast(checked ? 'Модуль симуляций включён' : 'Модуль симуляций отключён', checked ? 'success' : 'warning');
} catch(e) { LS.toast('Ошибка: ' + e.message, 'error'); }
}
async function simToggleOne(simId, enabled) {
const dis = new Set(_simsSettings.disabled_ids || []);
if (enabled) dis.delete(simId); else dis.add(simId);
const disabled_ids = [...dis];
try {
await LS.api('/api/settings/sims', { method: 'PUT', body: JSON.stringify({ disabled_ids }) });
_simsSettings.disabled_ids = disabled_ids;
const card = document.getElementById('simcard-' + simId);
if (card) card.classList.toggle('enabled', enabled);
LS.toast(enabled ? ${simId}» включена` : ${simId}» отключена`, enabled ? 'success' : 'warning');
} catch(e) { LS.toast('Ошибка: ' + e.message, 'error'); }
}
window.simsMasterToggle = simsMasterToggle;
window.simToggleOne = simToggleOne;
window.AdminSections = window.AdminSections || {};
window.AdminSections.sims = {
init: async () => { if (inited) return; inited = true; await load(); },
reload: load,
};
})();