Files
Maxim Dolgolyov c0442d6803 feat(labs): задания ещё для 12 симуляций + прогресс плана
LAB_TASKS расширен: waves, circuit, radioactive, heatengine, hydrostatics,
isoprocess, probability, emfield, geometry, photosynthesis, celldivision (+
ранее quadratic/trigcircle/normaldist/projectile/pendulum) — итого 17.
Только валидные single-concept id (мульти-модули molphys/chemistry пропущены).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:44:28 +03:00

191 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/* LabTasks — лёгкий фреймворк учебных заданий для симуляций (Фаза 1 плана).
* Превращает «песочницу» в учебный инструмент: задание → ответ числом с допуском →
* проверка/подсказка/прогресс. Данные — LAB_TASKS[simId] = [{q, formula?, a, tol, unit?, hint?}].
* Рендерится панелью в теорию (loadTheory дописывает LabTasks.mountHtml). Ответы
* проверяются на клиенте (учебные, не оценочные) — без обращения к серверу. */
(function (global) {
/* ── Данные заданий (наполняется по симуляциям) ── */
var LAB_TASKS = {
quadratic: [
{ q: 'Найди дискриминант уравнения ниже.', formula: 'x^2 - 5x + 6 = 0', a: 1, tol: 0.01, hint: 'D = b² 4ac' },
{ q: 'Сколько действительных корней у уравнения x² + 2x + 5 = 0?', a: 0, tol: 0.01, hint: 'Если D < 0 — корней нет' },
{ q: 'Чему равна абсцисса вершины параболы y = x² − 4x + 1?', a: 2, tol: 0.01, hint: 'x вершины = b / (2a)' },
],
trigcircle: [
{ q: 'Чему равен sin 30°? (десятичной дробью)', a: 0.5, tol: 0.02, hint: 'sin — это ордината (y) точки на окружности' },
{ q: 'Чему равен cos 90°?', a: 0, tol: 0.02, hint: 'cos — это абсцисса (x) точки' },
{ q: 'Чему равен tg 45°?', a: 1, tol: 0.02, hint: 'tg = sin / cos' },
],
normaldist: [
{ q: 'Какая доля значений попадает в интервал ±1σ? (например 0.68)', a: 0.68, tol: 0.03, hint: 'Правило 689599.7' },
{ q: 'Какая доля попадает в ±2σ?', a: 0.95, tol: 0.03, hint: 'Правило 689599.7' },
],
projectile: [
{ q: 'При каком угле броска дальность максимальна (без сопротивления)? В градусах.', a: 45, tol: 1, hint: 'Дальность ∝ sin(2α), максимум при 2α = 90°' },
],
pendulum: [
{ q: 'Период математического маятника длиной 1 м при g ≈ 9.8 м/с²? В секундах.', a: 2.0, tol: 0.15, formula: 'T = 2\\pi\\sqrt{L/g}', hint: 'Подставь L = 1, g = 9.8' },
],
waves: [
{ q: 'Скорость волны v = λ·f. Если λ = 2 м, f = 3 Гц, то v = ? (м/с)', a: 6, tol: 0.01, hint: 'Просто перемножь' },
],
circuit: [
{ q: 'Закон Ома I = U/R. При U = 12 В, R = 4 Ом ток I = ? (А)', a: 3, tol: 0.01, hint: 'I = U / R' },
],
radioactive: [
{ q: 'Через сколько периодов полураспада останется 25% вещества?', a: 2, tol: 0.01, hint: 'Каждый период — вдвое: 100→50→25' },
],
heatengine: [
{ q: 'КПД идеального цикла Карно при T_хол = 300 К, T_гор = 600 К? (долей)', a: 0.5, tol: 0.02, formula: '\\eta = 1 - T_{х}/T_{г}', hint: '1 300/600' },
],
hydrostatics: [
{ q: 'Давление на глубине 2 м в воде (ρ=1000, g=10)? В килопаскалях (кПа).', a: 20, tol: 0.5, formula: 'p = \\rho g h', hint: 'p = 1000·10·2 Па, переведи в кПа' },
],
isoprocess: [
{ q: 'Изотермический процесс идеального газа: чему равно ΔU? (Дж)', a: 0, tol: 0.01, hint: 'T = const → внутренняя энергия не меняется' },
],
probability: [
{ q: 'Вероятность выпадения орла у честной монеты? (дробью)', a: 0.5, tol: 0.02, hint: '1 из 2 равновероятных исходов' },
{ q: 'Вероятность выпадения шестёрки на кубике?', a: 0.1667, tol: 0.02, hint: '1 из 6' },
],
emfield: [
{ q: 'Сила Кулона ∝ 1/r². Во сколько раз изменится сила, если расстояние удвоить?', a: 0.25, tol: 0.01, hint: '1 / 2² = 1/4' },
],
geometry: [
{ q: 'Чему равна сумма углов треугольника? (в градусах)', a: 180, tol: 0.5, hint: 'Постоянна для любого треугольника' },
],
photosynthesis: [
{ q: 'Сколько молекул CO₂ нужно для синтеза одной молекулы глюкозы (цикл Кальвина)?', a: 6, tol: 0.01, hint: '6 CO₂ + 6 H₂O → C₆H₁₂O₆ + 6 O₂' },
],
celldivision: [
{ q: 'Сколько хромосом в дочерней клетке после митоза, если у материнской 46?', a: 46, tol: 0.01, hint: 'Митоз сохраняет набор' },
{ q: 'А после мейоза?', a: 23, tol: 0.01, hint: 'Мейоз уменьшает набор вдвое' },
],
};
var ICON_CHECK = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px"><polyline points="20 6 9 17 4 12"/></svg>';
var ICON_X = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
var ICON_TASK = '<svg class="ic" viewBox="0 0 24 24" style="width:15px;height:15px;vertical-align:-2px"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>';
var ICON_TROPHY = '<svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2z"/></svg>';
function esc(s) { return (global.LS && LS.esc) ? LS.esc(s) : String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]; }); }
/* состояние прохождения (сбрасывается при перезагрузке — задел под persist в Фазе 2) */
var state = {};
function st(simId) {
if (!state[simId]) state[simId] = { idx: 0, done: LAB_TASKS[simId].map(function () { return false; }) };
return state[simId];
}
function ensureStyle() {
if (document.getElementById('lt-style')) return;
var s = document.createElement('style'); s.id = 'lt-style';
s.textContent = [
'.lt-host{margin-top:18px;}',
'.lt-panel{border:1.5px solid rgba(155,93,229,.25);border-radius:14px;padding:14px 15px;background:rgba(155,93,229,.05);}',
'.lt-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;}',
'.lt-lbl{display:inline-flex;align-items:center;gap:6px;font-size:.74rem;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--violet,#9B5DE5);}',
'.lt-dots{display:inline-flex;gap:5px;}',
'.lt-dot{width:7px;height:7px;border-radius:50%;background:rgba(155,93,229,.25);}',
'.lt-dot.ok{background:var(--green,#06D664);}',
'.lt-dot.cur{box-shadow:0 0 0 2px rgba(155,93,229,.35);}',
'.lt-q{font-size:.9rem;line-height:1.5;color:var(--text,#0f172a);margin-bottom:8px;}',
'.lt-formula{margin:6px 0 10px;}',
'.lt-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}',
'.lt-input{width:120px;padding:8px 11px;border:1.5px solid var(--border-h,#cbd5e1);border-radius:9px;font:inherit;font-size:.88rem;background:var(--surface,#fff);color:var(--text,#0f172a);}',
'.lt-unit{font-size:.82rem;color:var(--text-3,#56687A);}',
'.lt-btn{padding:8px 16px;border:none;border-radius:9px;background:var(--violet,#9B5DE5);color:#fff;font:700 .82rem Manrope,sans-serif;cursor:pointer;}',
'.lt-btn:hover{background:#8347d4;}',
'.lt-fb{margin-top:9px;font-size:.84rem;font-weight:600;min-height:1px;}',
'.lt-fb.ok{color:var(--green,#06A552);}',
'.lt-fb.err{color:var(--pink,#E23C8E);}',
'.lt-fb.warn{color:var(--amber,#D9831A);}',
'.lt-done{text-align:center;padding:20px 14px;}',
'.lt-done .ic{color:var(--violet,#9B5DE5);}',
'.lt-done-t{font-weight:800;font-size:.95rem;margin-top:6px;}',
'.lt-done-s{font-size:.8rem;color:var(--text-3,#56687A);margin-top:2px;}',
'#theory-toggle.lt-has{position:relative;}',
'#theory-toggle.lt-has::after{content:"";position:absolute;top:5px;right:5px;width:7px;height:7px;border-radius:50%;background:var(--violet,#9B5DE5);box-shadow:0 0 0 2px var(--bg,#EEF2FF);}',
].join('');
document.head.appendChild(s);
}
function inner(simId) {
var list = LAB_TASKS[simId]; if (!list) return '';
var s = st(simId);
var total = list.length, solved = s.done.filter(Boolean).length;
if (solved === total) {
return '<div class="lt-panel lt-done">' + ICON_TROPHY +
'<div class="lt-done-t">Все задания выполнены!</div>' +
'<div class="lt-done-s">' + total + ' из ' + total + '</div></div>';
}
var i = s.idx, t = list[i];
var dots = list.map(function (_, k) { return '<span class="lt-dot' + (s.done[k] ? ' ok' : '') + (k === i ? ' cur' : '') + '"></span>'; }).join('');
return '<div class="lt-panel">' +
'<div class="lt-head"><span class="lt-lbl">' + ICON_TASK + ' Задание ' + (i + 1) + ' из ' + total + '</span><span class="lt-dots">' + dots + '</span></div>' +
'<div class="lt-q">' + esc(t.q) + '</div>' +
(t.formula ? '<div class="lt-formula" data-formula="' + t.formula.replace(/"/g, '&quot;') + '"></div>' : '') +
'<div class="lt-row">' +
'<input class="lt-input" id="lt-in-' + simId + '" type="number" step="any" placeholder="ответ" onkeydown="if(event.key===\'Enter\')LabTasks.check(\'' + simId + '\')">' +
(t.unit ? '<span class="lt-unit">' + esc(t.unit) + '</span>' : '') +
'<button class="lt-btn" onclick="LabTasks.check(\'' + simId + '\')">Проверить</button>' +
'</div>' +
'<div class="lt-fb" id="lt-fb-' + simId + '"></div>' +
'</div>';
}
function renderFormulas(host) {
if (!global.katex) return;
host.querySelectorAll('.lt-formula[data-formula]').forEach(function (d) {
try { katex.render(d.dataset.formula, d, { displayMode: true, throwOnError: false }); }
catch (e) { d.textContent = d.dataset.formula; }
});
}
function rerender(simId) {
var host = document.getElementById('lt-host-' + simId);
if (!host) return;
host.innerHTML = inner(simId);
renderFormulas(host);
}
global.LabTasks = {
has: function (simId) { return !!(LAB_TASKS[simId] && LAB_TASKS[simId].length); },
/** HTML панели заданий для вставки в теорию */
mountHtml: function (simId) {
if (!this.has(simId)) return '';
ensureStyle();
return '<div class="lt-host" id="lt-host-' + simId + '">' + inner(simId) + '</div>';
},
/** отрисовать формулы после вставки в DOM */
afterMount: function (simId) {
var host = document.getElementById('lt-host-' + simId);
if (host) renderFormulas(host);
},
check: function (simId) {
var list = LAB_TASKS[simId]; if (!list) return;
var s = st(simId), t = list[s.idx];
var inp = document.getElementById('lt-in-' + simId);
var fb = document.getElementById('lt-fb-' + simId);
if (!inp || !fb) return;
var v = parseFloat(String(inp.value || '').replace(',', '.'));
if (isNaN(v)) { fb.className = 'lt-fb warn'; fb.textContent = 'Введи число'; return; }
if (Math.abs(v - t.a) <= (t.tol || 0)) {
s.done[s.idx] = true;
fb.className = 'lt-fb ok'; fb.innerHTML = ICON_CHECK + ' Верно!';
setTimeout(function () {
// перейти к следующему невыполненному
var next = -1;
for (var k = 1; k <= list.length; k++) { var j = (s.idx + k) % list.length; if (!s.done[j]) { next = j; break; } }
if (next !== -1) s.idx = next;
rerender(simId);
}, 650);
} else {
fb.className = 'lt-fb err'; fb.innerHTML = ICON_X + ' Не совсем.' + (t.hint ? ' Подсказка: ' + esc(t.hint) : '');
}
},
};
})(window);