ea2526dc73
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>
124 lines
7.1 KiB
JavaScript
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,
|
|
};
|
|
})();
|