be4d43105e
Node.js/Express backend + vanilla JS frontend. Features: real-time collaborative whiteboard (SSE), multi-page support, LaTeX formulas, shapes/connectors, coordinate systems, number lines, compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with rotation & resize controls, minimap navigation overlay, auto-measurements, multi-page thumbnails sidebar, PNG export, page templates. Student/teacher workflows: classes, assignments, library, dashboard. Mobile responsive. SQLite (better-sqlite3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1007 lines
66 KiB
JavaScript
1007 lines
66 KiB
JavaScript
/**
|
||
* Seed: все теоретические курсы с уроками
|
||
*
|
||
* Курсы:
|
||
* [math] Тригонометрия — 4 урока
|
||
* [math] Производные и интегралы — 4 урока
|
||
* [math] Теория вероятностей — 3 урока
|
||
* [phys] Механика — 4 урока
|
||
* [phys] Электричество и магнетизм — 4 урока
|
||
* [phys] Оптика и волны — 3 урока
|
||
* [chem] Строение вещества — 4 урока
|
||
* [chem] Органическая химия — 4 урока
|
||
* [bio] Клетка и генетика — 4 урока
|
||
* [inf] Алгоритмы — 3 урока
|
||
*
|
||
* Запуск: node src/db/seed-theory.js (из папки backend/)
|
||
*/
|
||
'use strict';
|
||
require('./migrate'); // применить все миграции (включая lesson_blocks v4)
|
||
const db = require('./db');
|
||
|
||
/* ── Guard: пропустить, если уже засеяно ─────────────────────────────── */
|
||
const TITLES = [
|
||
'Тригонометрия', 'Производные и интегралы', 'Теория вероятностей и статистика',
|
||
'Механика', 'Электричество и магнетизм', 'Оптика и волны',
|
||
'Строение вещества', 'Органическая химия', 'Клетка и генетика', 'Алгоритмы и структуры данных',
|
||
];
|
||
const already = db.prepare(
|
||
`SELECT 1 FROM courses WHERE title IN (${TITLES.map(() => '?').join(',')}) LIMIT 1`
|
||
).get(...TITLES);
|
||
if (already) { console.log('seed-theory: already seeded — skipping.'); process.exit(0); }
|
||
|
||
/* ── Найти/создать преподавателя ─────────────────────────────────────── */
|
||
let teacher = db.prepare("SELECT id FROM users WHERE role IN ('teacher','admin') LIMIT 1").get();
|
||
if (!teacher) {
|
||
const bcrypt = require('bcryptjs');
|
||
const hash = bcrypt.hashSync('teacher123', 10);
|
||
const r = db.prepare(
|
||
"INSERT INTO users (email, password_hash, name, role) VALUES (?, ?, ?, 'teacher')"
|
||
).run('teacher@learnspace.local', hash, 'Преподаватель');
|
||
teacher = { id: Number(r.lastInsertRowid) };
|
||
console.log('Created system teacher, id =', teacher.id);
|
||
}
|
||
const TEACHER = Number(teacher.id);
|
||
|
||
/* ── Хелперы ─────────────────────────────────────────────────────────── */
|
||
function J(obj) { return JSON.stringify(obj); }
|
||
|
||
function course(slug, title, desc, emoji, order) {
|
||
return Number(db.prepare(
|
||
`INSERT INTO courses (subject_slug, title, description, cover_emoji, order_index, is_published, created_by)
|
||
VALUES (?, ?, ?, ?, ?, 1, ?)`
|
||
).run(slug, title, desc, emoji, order, TEACHER).lastInsertRowid);
|
||
}
|
||
function section(courseId, title, order) {
|
||
return Number(db.prepare(
|
||
`INSERT INTO course_sections (course_id, title, order_index) VALUES (?, ?, ?)`
|
||
).run(courseId, title, order).lastInsertRowid);
|
||
}
|
||
function lesson(courseId, sectionId, title, order) {
|
||
return Number(db.prepare(
|
||
`INSERT INTO lessons (course_id, section_id, title, order_index, is_published) VALUES (?, ?, ?, ?, 1)`
|
||
).run(courseId, sectionId, title, order).lastInsertRowid);
|
||
}
|
||
function block(lid, type, order, data) {
|
||
db.prepare(
|
||
`INSERT INTO lesson_blocks (lesson_id, type, order_index, data) VALUES (?, ?, ?, ?)`
|
||
).run(lid, type, order, J(data));
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════
|
||
Транзакция
|
||
═══════════════════════════════════════════════════════════════════════ */
|
||
db.transaction(() => {
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Тригонометрия [math]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_TRIG = course('math', 'Тригонометрия',
|
||
'Единичная окружность, тригонометрические функции, формулы и уравнения', '📐', 1);
|
||
const S_T1 = section(C_TRIG, 'Основы', 1);
|
||
const S_T2 = section(C_TRIG, 'Формулы', 2);
|
||
|
||
{
|
||
const L = lesson(C_TRIG, S_T1, 'Единичная окружность и углы', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Единичная окружность' });
|
||
block(L, 'text', ++b, { text: 'Единичная окружность — окружность с центром в начале координат и радиусом 1. Любая точка на ней имеет координаты (cos α, sin α), где α — угол от положительной оси OX против часовой стрелки.' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Радиус равен 1, поэтому sin α = y/1 = y, cos α = x/1 = x. Это упрощает все формулы.' });
|
||
block(L, 'heading', ++b, { level: 3, text: 'Радианы и градусы' });
|
||
block(L, 'text', ++b, { text: 'Один радиан — угол, при котором длина дуги равна радиусу. Полный оборот: 360° = 2π рад.' });
|
||
block(L, 'formula', ++b, { tex: '\\alpha_{\\text{рад}} = \\dfrac{\\alpha_{\\text{град}}}{180°} \\cdot \\pi' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Градусы', '0°', '30°', '45°', '60°', '90°', '180°', '270°', '360°'],
|
||
rows: [['Радианы', '0', 'π/6', 'π/4', 'π/3', 'π/2', 'π', '3π/2', '2π']],
|
||
});
|
||
block(L, 'heading', ++b, { level: 3, text: 'Знаки в четвертях' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Четверть', 'Углы', 'sin', 'cos', 'tg'],
|
||
rows: [
|
||
['I', '0°–90°', '+', '+', '+'],
|
||
['II', '90°–180°', '+', '−', '−'],
|
||
['III', '180°–270°', '−', '−', '+'],
|
||
['IV', '270°–360°', '−', '+', '−'],
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'success', text: 'Запоминалка: «Все Студенты Такие Кретины» (All Students Take Calculus) — в I все положительны, во II только sin, в III только tg, в IV только cos.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'В какой четверти cos α < 0 и sin α > 0?',
|
||
options: ['I', 'II', 'III', 'IV'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_TRIG, S_T1, 'Синус и косинус', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Синус и косинус угла' });
|
||
block(L, 'text', ++b, { text: 'sin α — ордината точки на единичной окружности. cos α — абсцисса. Оба лежат в [−1, 1].' });
|
||
block(L, 'formula', ++b, { tex: '-1 \\le \\sin \\alpha \\le 1, \\quad -1 \\le \\cos \\alpha \\le 1' });
|
||
block(L, 'table', ++b, {
|
||
header: ['α', '0°', '30°', '45°', '60°', '90°'],
|
||
rows: [
|
||
['sin α', '0', '½', '√2/2', '√3/2', '1'],
|
||
['cos α', '1', '√3/2', '√2/2', '½', '0'],
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'success', text: 'Для sin: √0/2, √1/2, √2/2, √3/2, √4/2 = 0, ½, √2/2, √3/2, 1. Для cos — в обратном порядке.' });
|
||
block(L, 'heading', ++b, { level: 3, text: 'Основное тригонометрическое тождество' });
|
||
block(L, 'formula', ++b, { tex: '\\sin^2 \\alpha + \\cos^2 \\alpha = 1' });
|
||
block(L, 'text', ++b, { text: 'Следует из теоремы Пифагора: точка (cos α, sin α) лежит на окружности r = 1, поэтому x² + y² = 1.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'sin²(60°) + cos²(60°) = ?',
|
||
options: ['0', '½', '1', '√3'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'sin 30° = ?', back: '½ = 0.5' });
|
||
block(L, 'flashcard', ++b, { front: 'cos 45° = ?', back: '√2/2 ≈ 0.707' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_TRIG, S_T1, 'Тангенс и котангенс', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Тангенс и котангенс' });
|
||
block(L, 'formula', ++b, { label: 'Тангенс', tex: '\\tan \\alpha = \\dfrac{\\sin \\alpha}{\\cos \\alpha}' });
|
||
block(L, 'formula', ++b, { label: 'Котангенс', tex: '\\cot \\alpha = \\dfrac{\\cos \\alpha}{\\sin \\alpha} = \\dfrac{1}{\\tan \\alpha}' });
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'tg α не определён при cos α = 0 (α = 90° + 180°·n).\nctg α не определён при sin α = 0 (α = 180°·n).' });
|
||
block(L, 'table', ++b, {
|
||
header: ['α', '0°', '30°', '45°', '60°', '90°'],
|
||
rows: [
|
||
['tg α', '0', '√3/3', '1', '√3', '—'],
|
||
['ctg α', '—', '√3', '1', '√3/3', '0'],
|
||
],
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'tg 45° = ?',
|
||
options: ['0', '½', '1', '√3'],
|
||
correctIndex: 2,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_TRIG, S_T2, 'Формулы приведения и сложения', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Формулы приведения' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Правило: 1) определите четверть → знак результата; 2) если аргумент π/2 или 3π/2 — функция меняется на кофункцию (sin↔cos, tg↔ctg); π или 2π — не меняется.' });
|
||
block(L, 'table', ++b, {
|
||
header: ['', 'sin', 'cos', 'tg'],
|
||
rows: [
|
||
['π/2 − α', 'cos α', 'sin α', 'ctg α'],
|
||
['π − α', 'sin α', '−cos α', '−tg α'],
|
||
['π + α', '−sin α', '−cos α', 'tg α'],
|
||
['2π − α', '−sin α', 'cos α', '−tg α'],
|
||
],
|
||
});
|
||
block(L, 'heading', ++b, { level: 2, text: 'Формулы сложения' });
|
||
block(L, 'formula', ++b, { tex: '\\sin(\\alpha \\pm \\beta) = \\sin\\alpha\\cos\\beta \\pm \\cos\\alpha\\sin\\beta' });
|
||
block(L, 'formula', ++b, { tex: '\\cos(\\alpha \\pm \\beta) = \\cos\\alpha\\cos\\beta \\mp \\sin\\alpha\\sin\\beta' });
|
||
block(L, 'formula', ++b, { tex: '\\tan(\\alpha \\pm \\beta) = \\dfrac{\\tan\\alpha \\pm \\tan\\beta}{1 \\mp \\tan\\alpha\\tan\\beta}' });
|
||
block(L, 'heading', ++b, { level: 3, text: 'Формулы двойного угла' });
|
||
block(L, 'formula', ++b, { tex: '\\sin 2\\alpha = 2\\sin\\alpha\\cos\\alpha' });
|
||
block(L, 'formula', ++b, { tex: '\\cos 2\\alpha = \\cos^2\\alpha - \\sin^2\\alpha = 2\\cos^2\\alpha - 1 = 1 - 2\\sin^2\\alpha' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'sin(π − α) = ?',
|
||
options: ['sin α', '−sin α', 'cos α', '−cos α'],
|
||
correctIndex: 0,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'sin(α + β) = ?', back: 'sin α · cos β + cos α · sin β' });
|
||
block(L, 'flashcard', ++b, { front: 'cos 2α = ?', back: 'cos²α − sin²α = 2cos²α − 1 = 1 − 2sin²α' });
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Производные и интегралы [math]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_CALC = course('math', 'Производные и интегралы',
|
||
'Дифференциальное и интегральное исчисление: производная, правила, применение, первообразная', '∫', 2);
|
||
const S_C1 = section(C_CALC, 'Дифференциальное исчисление', 1);
|
||
const S_C2 = section(C_CALC, 'Интегральное исчисление', 2);
|
||
|
||
{
|
||
const L = lesson(C_CALC, S_C1, 'Понятие производной', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Производная функции' });
|
||
block(L, 'text', ++b, { text: 'Производная — мгновенная скорость изменения функции. Геометрически: тангенс угла наклона касательной к графику.' });
|
||
block(L, 'formula', ++b, { label: 'Определение', tex: "f'(x) = \\lim_{\\Delta x \\to 0} \\frac{f(x + \\Delta x) - f(x)}{\\Delta x}" });
|
||
block(L, 'table', ++b, {
|
||
header: ['f(x)', "f'(x)"],
|
||
rows: [
|
||
['C', '0'], ['xⁿ', 'n·xⁿ⁻¹'], ['eˣ', 'eˣ'], ['aˣ', 'aˣ·ln a'],
|
||
['ln x', '1/x'], ['sin x', 'cos x'], ['cos x', '−sin x'],
|
||
['tg x', '1/cos²x'], ['√x', '1/(2√x)'],
|
||
],
|
||
});
|
||
block(L, 'formula', ++b, { label: 'Произведение', tex: '(fg)\' = f\'g + fg\'' });
|
||
block(L, 'formula', ++b, { label: 'Частное', tex: '\\left(\\dfrac{f}{g}\\right)\' = \\dfrac{f\'g - fg\'}{g^2}' });
|
||
block(L, 'formula', ++b, { label: 'Сложная функция', tex: '(f(g(x)))\' = f\'(g(x)) \\cdot g\'(x)' });
|
||
block(L, 'accordion', ++b, { title: 'Пример: f(x) = x³ − 5x + 2', content: "f'(x) = 3x² − 5\n(x³)' = 3x², (−5x)' = −5, (2)' = 0" });
|
||
block(L, 'quiz', ++b, {
|
||
question: "(4x³)' = ?",
|
||
options: ['4x²', '12x²', '12x³', '4x⁴'],
|
||
correctIndex: 1,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: "(xⁿ)' = ?", back: 'n · xⁿ⁻¹\nПример: (x⁵)\' = 5x⁴' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_CALC, S_C1, 'Применение производной', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Применение производной' });
|
||
block(L, 'callout', ++b, { style: 'info', text: "Алгоритм нахождения экстремумов:\n1. Найти f'(x)\n2. Решить f'(x) = 0\n3. Определить знак f' слева и справа\n4. «+→−» — максимум, «−→+» — минимум" });
|
||
block(L, 'formula', ++b, { label: 'Признак через вторую производную', tex: "f''(x_0) > 0 \\Rightarrow \\text{min}, \\quad f''(x_0) < 0 \\Rightarrow \\text{max}" });
|
||
block(L, 'formula', ++b, { label: 'Уравнение касательной', tex: "y = f(x_0) + f'(x_0)(x - x_0)" });
|
||
block(L, 'diagram', ++b, {
|
||
code: "graph LR\n A[\"Найти f'(x)\"] --> B[\"Нули f'(x)\"]\n B --> C[\"Знаки f'(x)\"]\n C --> D[\"min / max\"]\n D --> E[\"f''(x) — выпуклость\"]",
|
||
caption: 'Алгоритм исследования функции',
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'f(x) = x² − 4x + 3. При каком x минимум?',
|
||
options: ['x = 0', 'x = 2', 'x = 3', 'x = −2'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_CALC, S_C2, 'Первообразная и неопределённый интеграл', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Первообразная' });
|
||
block(L, 'text', ++b, { text: 'F(x) — первообразная f(x), если F\'(x) = f(x). Первообразных бесконечно много: F(x) + C.' });
|
||
block(L, 'formula', ++b, { label: 'Неопределённый интеграл', tex: '\\int f(x)\\, dx = F(x) + C' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Интеграл', 'Результат'],
|
||
rows: [
|
||
['∫ xⁿ dx', 'xⁿ⁺¹/(n+1) + C'], ['∫ dx/x', 'ln|x| + C'],
|
||
['∫ eˣ dx', 'eˣ + C'], ['∫ sin x dx', '−cos x + C'],
|
||
['∫ cos x dx', 'sin x + C'], ['∫ aˣ dx', 'aˣ/ln a + C'],
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'Проверить интеграл: взять производную результата — должна получиться подынтегральная функция.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: '∫ 3x² dx = ?',
|
||
options: ['6x + C', 'x³ + C', '3x³ + C', 'x² + C'],
|
||
correctIndex: 1,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: '∫ cos x dx = ?', back: "sin x + C\nПроверка: (sin x)' = cos x ✓" });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_CALC, S_C2, 'Определённый интеграл и площадь', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Определённый интеграл' });
|
||
block(L, 'formula', ++b, { label: 'Формула Ньютона–Лейбница', tex: '\\int_a^b f(x)\\, dx = F(b) - F(a)' });
|
||
block(L, 'text', ++b, { text: 'Геометрический смысл: площадь под графиком f(x) ≥ 0 на [a, b]. Ниже оси — вычитается.' });
|
||
block(L, 'formula', ++b, { label: 'Площадь между двумя кривыми', tex: 'S = \\int_a^b |f(x) - g(x)|\\, dx' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Свойство', 'Запись'],
|
||
rows: [
|
||
['Линейность', '∫[αf + βg] = α∫f + β∫g'],
|
||
['Аддитивность', '∫ₐᵇ = ∫ₐᶜ + ∫ᶜᵇ'],
|
||
['Смена пределов', '∫ₐᵇ = −∫ᵦₐ'],
|
||
],
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: '∫₀² x dx = ?',
|
||
options: ['1', '2', '4', '½'],
|
||
correctIndex: 1,
|
||
});
|
||
block(L, 'alert', ++b, { style: 'exam', text: 'ЕГЭ профиль: отработайте алгоритм — найти первообразную → подставить пределы → вычесть F(a) из F(b).' });
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Теория вероятностей [math]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_PROB = course('math', 'Теория вероятностей и статистика',
|
||
'Классическая вероятность, формулы сложения и умножения, случайные величины', '🎲', 3);
|
||
const S_P1 = section(C_PROB, 'Классическая вероятность', 1);
|
||
const S_P2 = section(C_PROB, 'Случайные величины', 2);
|
||
|
||
{
|
||
const L = lesson(C_PROB, S_P1, 'Классическое определение вероятности', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Вероятность событий' });
|
||
block(L, 'formula', ++b, { label: 'Классическая вероятность', tex: 'P(A) = \\dfrac{m}{n}' });
|
||
block(L, 'text', ++b, { text: 'm — число благоприятных исходов, n — общее число равновозможных.\nВсегда: 0 ≤ P(A) ≤ 1.' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Схема', 'Формула', 'Смысл'],
|
||
rows: [
|
||
['Перестановки', 'Pₙ = n!', 'Расставить n предметов'],
|
||
['Размещения', 'Aₙᵏ = n!/(n−k)!', 'k из n с учётом порядка'],
|
||
['Сочетания', 'Cₙᵏ = n!/(k!(n−k)!)', 'k из n без порядка'],
|
||
],
|
||
});
|
||
block(L, 'accordion', ++b, {
|
||
title: 'Пример: вероятность выпадения 3 при броске кубика',
|
||
content: 'n = 6, m = 1 → P = 1/6 ≈ 0.167',
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'В урне 4 белых и 6 чёрных. Вероятность вытащить белый?',
|
||
options: ['0.4', '0.6', '0.25', '0.5'],
|
||
correctIndex: 0,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_PROB, S_P1, 'Формулы сложения и умножения', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Операции над событиями' });
|
||
block(L, 'formula', ++b, { label: 'Сложение (несовместные)', tex: 'P(A \\cup B) = P(A) + P(B)' });
|
||
block(L, 'formula', ++b, { label: 'Сложение (общий случай)', tex: 'P(A \\cup B) = P(A) + P(B) - P(A \\cap B)' });
|
||
block(L, 'formula', ++b, { label: 'Противоположное событие', tex: 'P(\\bar{A}) = 1 - P(A)' });
|
||
block(L, 'formula', ++b, { label: 'Умножение (независимые)', tex: 'P(A \\cap B) = P(A) \\cdot P(B)' });
|
||
block(L, 'formula', ++b, { label: 'Условная вероятность', tex: 'P(B|A) = \\dfrac{P(A \\cap B)}{P(A)}' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Стрелок A попадает с P=0.7, B с P=0.8. Оба стреляют. P(хотя бы одного попадания)?',
|
||
options: ['1.5', '0.56', '0.94', '0.75'],
|
||
correctIndex: 2,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_PROB, S_P2, 'Случайные величины и математическое ожидание', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Дискретная случайная величина' });
|
||
block(L, 'formula', ++b, { label: 'Математическое ожидание', tex: 'M(X) = \\sum_{i=1}^{n} x_i p_i' });
|
||
block(L, 'formula', ++b, { label: 'Дисперсия', tex: 'D(X) = \\sum_{i}(x_i - M(X))^2 p_i' });
|
||
block(L, 'formula', ++b, { label: 'СКО', tex: '\\sigma = \\sqrt{D(X)}' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'M(X) — «центр тяжести» распределения. σ имеет те же единицы, что X. Правило трёх сигм: 68% в [μ−σ, μ+σ], 95% в [μ−2σ, μ+2σ], 99.7% в [μ−3σ, μ+3σ].' });
|
||
block(L, 'formula', ++b, { label: 'Нормальное распределение (плотность)', tex: 'f(x) = \\dfrac{1}{\\sigma\\sqrt{2\\pi}} e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'X принимает 1, 2, 3 с вероятностями 0.2, 0.5, 0.3. M(X) = ?',
|
||
options: ['1.5', '2.0', '2.1', '1.8'],
|
||
correctIndex: 2,
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Механика [phys]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_MECH = course('phys', 'Механика',
|
||
'Кинематика, динамика, законы Ньютона, энергия и импульс', '🚀', 1);
|
||
const S_M1 = section(C_MECH, 'Кинематика', 1);
|
||
const S_M2 = section(C_MECH, 'Динамика', 2);
|
||
const S_M3 = section(C_MECH, 'Законы сохранения', 3);
|
||
|
||
{
|
||
const L = lesson(C_MECH, S_M1, 'Прямолинейное движение', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Основные понятия кинематики' });
|
||
block(L, 'text', ++b, { text: 'Кинематика описывает движение без анализа причин.\n\nПеремещение s⃗ [м] — вектор от старта к финишу.\nСкорость v⃗ [м/с] — быстрота изменения положения.\nУскорение a⃗ [м/с²] — быстрота изменения скорости.' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Путь ≠ перемещение. Путь — длина траектории (скаляр ≥ 0). При движении «туда-обратно» путь > 0, перемещение = 0.' });
|
||
block(L, 'formula', ++b, { label: 'Равномерное движение', tex: 'x = x_0 + v \\cdot t' });
|
||
block(L, 'formula', ++b, { label: 'Скорость при равноускоренном', tex: 'v = v_0 + a \\cdot t' });
|
||
block(L, 'formula', ++b, { label: 'Координата', tex: 'x = x_0 + v_0 t + \\dfrac{a t^2}{2}' });
|
||
block(L, 'formula', ++b, { label: 'Без времени', tex: 'v^2 = v_0^2 + 2a \\cdot s' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Автомобиль разгоняется из покоя с a = 2 м/с². Путь за 5 с?',
|
||
options: ['10 м', '25 м', '50 м', '5 м'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_MECH, S_M2, 'Законы Ньютона', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Три закона Ньютона' });
|
||
block(L, 'text', ++b, { text: 'I закон (инерция): тело сохраняет покой или равномерное прямолинейное движение, если равнодействующая сил = 0.' });
|
||
block(L, 'formula', ++b, { label: 'II закон', tex: '\\vec{F} = m \\vec{a}' });
|
||
block(L, 'formula', ++b, { label: 'III закон', tex: '\\vec{F}_{12} = -\\vec{F}_{21}' });
|
||
block(L, 'text', ++b, { text: 'Силы в механике:\n• Тяжесть: F = mg (вниз)\n• Нормальная реакция N ⊥ опоре\n• Трение: F = μN (против движения)\n• Упругость: F = −kx (закон Гука)' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'К телу m = 4 кг приложена F = 12 Н (без трения). Ускорение?',
|
||
options: ['2 м/с²', '3 м/с²', '4 м/с²', '48 м/с²'],
|
||
correctIndex: 1,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Второй закон Ньютона', back: 'F⃗ = m·a⃗\nУскорение пропорционально силе и обратно пропорционально массе.' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_MECH, S_M3, 'Работа и энергия', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Работа и механическая энергия' });
|
||
block(L, 'formula', ++b, { label: 'Работа', tex: 'A = F \\cdot s \\cdot \\cos \\alpha' });
|
||
block(L, 'formula', ++b, { label: 'Кинетическая энергия', tex: 'E_k = \\dfrac{mv^2}{2}' });
|
||
block(L, 'formula', ++b, { label: 'Потенциальная энергия', tex: 'E_p = mgh' });
|
||
block(L, 'formula', ++b, { label: 'Закон сохранения', tex: 'E_k + E_p = \\text{const}' });
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'С трением: E_k1 + E_p1 = E_k2 + E_p2 + Q, где Q — тепловые потери.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Мяч 0.5 кг падает с h = 20 м (g = 10 м/с²). Eₖ у земли?',
|
||
options: ['50 Дж', '100 Дж', '200 Дж', '10 Дж'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_MECH, S_M3, 'Импульс и закон сохранения', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Импульс тела' });
|
||
block(L, 'formula', ++b, { tex: '\\vec{p} = m \\vec{v}' });
|
||
block(L, 'formula', ++b, { label: 'Импульс силы', tex: '\\vec{F} \\cdot \\Delta t = \\Delta \\vec{p}' });
|
||
block(L, 'formula', ++b, { label: 'Закон сохранения импульса', tex: 'm_1 \\vec{v}_1 + m_2 \\vec{v}_2 = m_1 \\vec{v}_1\\prime + m_2 \\vec{v}_2\\prime' });
|
||
block(L, 'columns', ++b, {
|
||
cols: [
|
||
{ content: '<strong>Упругий удар</strong><br>Сохраняется и импульс, и кинетическая энергия.' },
|
||
{ content: '<strong>Неупругий удар</strong><br>Тела слипаются, сохраняется только импульс.' },
|
||
],
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Вагон 30 т (v = 2 м/с) сцепляется с неподвижным 20 т. Скорость?',
|
||
options: ['1 м/с', '1.2 м/с', '1.5 м/с', '2 м/с'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Электричество и магнетизм [phys]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_EM = course('phys', 'Электричество и магнетизм',
|
||
'Закон Кулона, электрическое поле, постоянный ток, магнитное поле, индукция', '⚡', 2);
|
||
const S_E1 = section(C_EM, 'Электростатика', 1);
|
||
const S_E2 = section(C_EM, 'Постоянный ток', 2);
|
||
const S_E3 = section(C_EM, 'Магнетизм', 3);
|
||
|
||
{
|
||
const L = lesson(C_EM, S_E1, 'Закон Кулона и электрическое поле', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Электрический заряд' });
|
||
block(L, 'text', ++b, { text: 'Заряд квантуется: e ≈ 1.6·10⁻¹⁹ Кл. Закон сохранения: суммарный заряд замкнутой системы постоянен.' });
|
||
block(L, 'formula', ++b, { label: 'Закон Кулона', tex: 'F = k \\dfrac{|q_1 q_2|}{r^2}, \\quad k = 9 \\cdot 10^9 \\text{ Н·м}^2/\\text{Кл}^2' });
|
||
block(L, 'formula', ++b, { label: 'Напряжённость поля', tex: 'E = k \\dfrac{Q}{r^2}, \\quad E = \\dfrac{U}{d}' });
|
||
block(L, 'formula', ++b, { label: 'Потенциал', tex: '\\varphi = k \\dfrac{Q}{r}, \\quad U_{AB} = \\varphi_A - \\varphi_B' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'q₁ = q₂ = 10⁻⁶ Кл, r = 1 м. Сила?',
|
||
options: ['9·10⁻³ Н', '9·10⁻⁶ Н', '9·10⁻² Н', '9 Н'],
|
||
correctIndex: 0,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_EM, S_E2, 'Постоянный ток. Законы Ома', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Законы Ома' });
|
||
block(L, 'formula', ++b, { label: 'Закон Ома (участок)', tex: 'I = \\dfrac{U}{R}' });
|
||
block(L, 'formula', ++b, { label: 'Закон Ома (полная цепь)', tex: 'I = \\dfrac{\\mathcal{E}}{R + r}' });
|
||
block(L, 'columns', ++b, {
|
||
cols: [
|
||
{ content: '<strong>Последовательно:</strong><br>R = R₁ + R₂<br>I одинаковый<br>U = U₁ + U₂' },
|
||
{ content: '<strong>Параллельно:</strong><br>1/R = 1/R₁ + 1/R₂<br>U одинаковое<br>I = I₁ + I₂' },
|
||
],
|
||
});
|
||
block(L, 'formula', ++b, { label: 'Мощность', tex: 'P = UI = I^2R = U^2/R' });
|
||
block(L, 'formula', ++b, { label: 'Закон Джоуля–Ленца', tex: 'Q = I^2Rt' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'R₁ = 3 Ом и R₂ = 6 Ом параллельно. Общее сопротивление?',
|
||
options: ['9 Ом', '4.5 Ом', '2 Ом', '18 Ом'],
|
||
correctIndex: 2,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_EM, S_E3, 'Магнитное поле. Сила Лоренца', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Магнитное поле' });
|
||
block(L, 'formula', ++b, { label: 'Сила Лоренца', tex: 'F = qvB\\sin\\alpha' });
|
||
block(L, 'formula', ++b, { label: 'Сила Ампера', tex: 'F = BIl\\sin\\alpha' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Направление силы Лоренца — правило левой руки: пальцы по v (+), ладонь на B, большой палец — F.' });
|
||
block(L, 'formula', ++b, { label: 'Радиус кругового движения заряда', tex: 'r = \\dfrac{mv}{qB}' });
|
||
block(L, 'formula', ++b, { label: 'Закон Фарадея (ЭДС индукции)', tex: '\\mathcal{E} = -\\dfrac{\\Delta\\Phi}{\\Delta t}, \\quad \\Phi = BS\\cos\\alpha' });
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'Знак «−» — правило Ленца: индукционный ток противодействует изменению потока.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Протон (q=1.6·10⁻¹⁹, m=1.67·10⁻²⁷ кг) летит v=10⁶ м/с ⊥ полю B=0.1 Тл. Радиус?',
|
||
options: ['≈ 0.1 м', '≈ 0.01 м', '≈ 1 м', '≈ 10 м'],
|
||
correctIndex: 0,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_EM, S_E3, 'Колебательный контур и переменный ток', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Электромагнитные колебания' });
|
||
block(L, 'formula', ++b, { label: 'Формула Томсона', tex: 'T = 2\\pi\\sqrt{LC}' });
|
||
block(L, 'formula', ++b, { label: 'Действующее значение', tex: 'I_{\\text{rms}} = \\dfrac{I_m}{\\sqrt{2}} \\approx 0.707\\, I_m' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Приборы измеряют действующее значение. В розетке 220 В — это RMS, амплитуда ≈ 311 В.' });
|
||
block(L, 'formula', ++b, { label: 'Резонанс (X_L = X_C)', tex: '\\omega_0 = \\dfrac{1}{\\sqrt{LC}}, \\quad Z_{\\min} = R' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Контур L=0.1 Гн, C=10 мкФ. Частота?',
|
||
options: ['≈ 159 Гц', '≈ 16 Гц', '≈ 1590 Гц', '≈ 100 Гц'],
|
||
correctIndex: 0,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Формула Томсона', back: 'T = 2π√(LC) — период свободных колебаний контура' });
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Оптика и волны [phys]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_OPT = course('phys', 'Оптика и волны',
|
||
'Геометрическая оптика, линзы, волновые явления: интерференция, дифракция', '🔭', 3);
|
||
const S_O1 = section(C_OPT, 'Геометрическая оптика', 1);
|
||
const S_O2 = section(C_OPT, 'Волновая оптика', 2);
|
||
|
||
{
|
||
const L = lesson(C_OPT, S_O1, 'Отражение и преломление света', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Законы геометрической оптики' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'c ≈ 3·10⁸ м/с. В среде с n: v = c/n. Стекло: n ≈ 1.5, вода: n ≈ 1.33.' });
|
||
block(L, 'formula', ++b, { label: 'Закон отражения', tex: '\\alpha_1 = \\alpha_2' });
|
||
block(L, 'formula', ++b, { label: 'Закон Снеллиуса', tex: 'n_1 \\sin\\alpha_1 = n_2 \\sin\\alpha_2' });
|
||
block(L, 'formula', ++b, { label: 'Критический угол полного отражения', tex: '\\sin\\theta_c = \\dfrac{n_2}{n_1}' });
|
||
block(L, 'text', ++b, { text: 'При переходе из более плотной среды (n₁ > n₂) при угле > θc возникает полное внутреннее отражение. На этом работает оптоволокно.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Луч из воды (n=1.33) в воздух (n=1). Критический угол?',
|
||
options: ['sin⁻¹(0.75) ≈ 48.75°', '90°', '45°', '30°'],
|
||
correctIndex: 0,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_OPT, S_O1, 'Линзы', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Тонкая линза' });
|
||
block(L, 'formula', ++b, { label: 'Формула тонкой линзы', tex: '\\dfrac{1}{f} = \\dfrac{1}{d} + \\dfrac{1}{v}' });
|
||
block(L, 'formula', ++b, { label: 'Увеличение', tex: 'M = \\dfrac{|v|}{d}' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Положение предмета', 'Изображение', 'Применение'],
|
||
rows: [
|
||
['d > 2f', 'Действит., уменьш., перевёрн.', 'Объективы'],
|
||
['f < d < 2f', 'Действит., увелич., перевёрн.', 'Проекторы'],
|
||
['d < f', 'Мнимое, увелич., прямое', 'Лупа, очки'],
|
||
],
|
||
});
|
||
block(L, 'accordion', ++b, {
|
||
title: 'Пример: f = 10 см, d = 15 см → где изображение?',
|
||
content: '1/v = 1/10 − 1/15 = 1/30 → v = 30 см\nМ = 30/15 = 2 (увеличенное, перевёрнутое)',
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Линза f=10 см, предмет d=15 см. Изображение?',
|
||
options: ['30 см', '−30 см', '20 см', '15 см'],
|
||
correctIndex: 0,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Формула тонкой линзы', back: '1/f = 1/d + 1/v\nf > 0 — собирающая, f < 0 — рассеивающая' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_OPT, S_O2, 'Интерференция и дифракция', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Волновые свойства света' });
|
||
block(L, 'text', ++b, { text: 'Свет — поперечная ЭМ волна. Длина волны: фиолетовый ≈ 400 нм, красный ≈ 700 нм.' });
|
||
block(L, 'formula', ++b, { label: 'Максимум интерференции', tex: '\\Delta = m\\lambda, \\quad m = 0, \\pm1, \\pm2, \\ldots' });
|
||
block(L, 'formula', ++b, { label: 'Минимум интерференции', tex: '\\Delta = (2m+1)\\dfrac{\\lambda}{2}' });
|
||
block(L, 'formula', ++b, { label: 'Дифракционная решётка', tex: 'd \\sin\\theta = m\\lambda' });
|
||
block(L, 'formula', ++b, { label: 'Закон Малюса (поляризация)', tex: 'I = I_0 \\cos^2\\theta' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Решётка d=2 мкм, λ=600 нм, m=1. Угол θ?',
|
||
options: ['17.5°', '30°', '15°', '45°'],
|
||
correctIndex: 0,
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Строение вещества [chem]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_ATOM = course('chem', 'Строение вещества',
|
||
'Атом, электронные оболочки, периодический закон, химическая связь', '⚛️', 1);
|
||
const S_A1 = section(C_ATOM, 'Строение атома', 1);
|
||
const S_A2 = section(C_ATOM, 'Периодический закон', 2);
|
||
const S_A3 = section(C_ATOM, 'Химическая связь', 3);
|
||
|
||
{
|
||
const L = lesson(C_ATOM, S_A1, 'Модели атома', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Строение атома' });
|
||
block(L, 'text', ++b, { text: 'Атом = ядро (протоны p⁺ + нейтроны n⁰) + электронная оболочка (e⁻).' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Протон: заряд +1, масса ≈ 1 а.е.м.\nНейтрон: заряд 0, масса ≈ 1 а.е.м.\nЭлектрон: заряд −1, масса ≈ 1/1836 а.е.м.' });
|
||
block(L, 'formula', ++b, { label: 'Массовое число', tex: 'A = Z + N' });
|
||
block(L, 'text', ++b, { text: 'Z — число протонов (порядковый номер). N — число нейтронов. Изотопы — одинаковый Z, разный N.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Сколько нейтронов в ²⁷₁₃Al?',
|
||
options: ['13', '14', '27', '40'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ATOM, S_A1, 'Электронные оболочки', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Электронные оболочки атома' });
|
||
block(L, 'text', ++b, { text: 'Электроны занимают энергетические уровни n = 1, 2, 3… Максимум на уровне: 2n².' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Уровень n', 'Обозначение', 'Макс. e⁻', 'Подуровни'],
|
||
rows: [['1', 'K', '2', 's'], ['2', 'L', '8', 's, p'], ['3', 'M', '18', 's, p, d'], ['4', 'N', '32', 's, p, d, f']],
|
||
});
|
||
block(L, 'text', ++b, { text: 'Порядок заполнения: 1s→2s→2p→3s→3p→4s→3d→4p→…\n\nПринцип Паули: ≤ 2 e⁻ на орбитали.\nПравило Гунда: на одном подуровне сначала по одному.\nПравило Клечковского: порядок по (n+l).' });
|
||
block(L, 'callout', ++b, { style: 'success', text: 'Углерод (Z=6): 1s² 2s² 2p² — на 2p два электрона на разных орбиталях (правило Гунда).' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Электронная конфигурация Na (Z=11)?',
|
||
options: ['1s² 2s² 2p⁶ 3s¹', '1s² 2s² 2p⁷', '1s² 2s² 2p⁶ 3p¹', '1s² 2s² 3s² 3p⁵'],
|
||
correctIndex: 0,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Макс. e⁻ на 3-м уровне?', back: '2n² = 18\n3s²(2) + 3p⁶(6) + 3d¹⁰(10) = 18' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ATOM, S_A2, 'Периодический закон Менделеева', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Периодический закон' });
|
||
block(L, 'text', ++b, { text: 'Свойства элементов периодически зависят от заряда ядра.\n\nПериод — строка (номер = число уровней). Группа — столбец (сходные свойства).' });
|
||
block(L, 'columns', ++b, {
|
||
cols: [
|
||
{ content: '<strong>В периоде (→):</strong><br>Z растёт<br>Радиус ↓<br>ЭО ↑<br>Металл. свойства ↓' },
|
||
{ content: '<strong>В группе (↓):</strong><br>Радиус ↑<br>ЭО ↓<br>Металл. свойства ↑' },
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'success', text: 'Самый электроотрицательный элемент — F (правый верхний). Самый активный металл — Fr (левый нижний).' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Какой элемент имеет наибольший атомный радиус: Li, Na, K, Rb?',
|
||
options: ['Li', 'Na', 'K', 'Rb'],
|
||
correctIndex: 3,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ATOM, S_A3, 'Типы химической связи', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Химическая связь' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Тип', 'Механизм', 'Пример', 'Прочность'],
|
||
rows: [
|
||
['Ковалентная неполярная', 'Обобществление e⁻ (одинак. атомы)', 'H₂, Cl₂', 'Высокая'],
|
||
['Ковалентная полярная', 'Обобществление e⁻ (разные неМе)', 'HCl, H₂O', 'Высокая'],
|
||
['Ионная', 'Передача e⁻ (Ме+неМе)', 'NaCl, CaO', 'Высокая'],
|
||
['Металлическая', 'Электронный газ', 'Fe, Cu', 'Средняя'],
|
||
['Водородная', 'H···F/O/N', 'H₂O···H₂O', 'Низкая'],
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Водородная связь объясняет аномально высокую t° кипения воды (100°C против −80°C у H₂S).' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Тип связи в NaCl?',
|
||
options: ['Ковалентная неполярная', 'Ковалентная полярная', 'Ионная', 'Металлическая'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Чем отличается ковалентная полярная от неполярной?', back: 'Полярная: e⁻ пара смещена к более ЭО атому (разные эл-ты).\nНеполярная: e⁻ пара симметрична (одинаковые эл-ты).' });
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Органическая химия [chem]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_ORG = course('chem', 'Органическая химия',
|
||
'Углеводороды, кислородсодержащие соединения, биополимеры', '🧪', 2);
|
||
const S_G1 = section(C_ORG, 'Углеводороды', 1);
|
||
const S_G2 = section(C_ORG, 'Кислородсодержащие', 2);
|
||
const S_G3 = section(C_ORG, 'Биополимеры', 3);
|
||
|
||
{
|
||
const L = lesson(C_ORG, S_G1, 'Алканы — предельные углеводороды', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Алканы CₙH₂ₙ₊₂' });
|
||
block(L, 'formula', ++b, { tex: 'C_n H_{2n+2}' });
|
||
block(L, 'text', ++b, { text: 'Все связи C–C и C–H одинарные (σ-связи). Метан CH₄, этан C₂H₆, пропан C₃H₈, бутан C₄H₁₀…\n\nС C₄ возможна структурная изомерия.' });
|
||
block(L, 'text', ++b, { text: 'Химические свойства:\n1. Горение: CH₄ + 2O₂ → CO₂ + 2H₂O\n2. Замещение: CH₄ + Cl₂ → CH₃Cl + HCl (свет, SR)\n3. Крекинг: C₁₀H₂₂ → C₅H₁₂ + C₅H₁₀' });
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'Реакции присоединения (AE) для алканов НЕВОЗМОЖНЫ — нет π-связей. Это главное отличие от алкенов.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Общая формула алканов?',
|
||
options: ['CₙHₙ', 'CₙH₂ₙ', 'CₙH₂ₙ₊₂', 'CₙH₂ₙ₋₂'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Алканы / алкены / алкины — общие формулы?', back: 'Алканы: CₙH₂ₙ₊₂\nАлкены: CₙH₂ₙ\nАлкины: CₙH₂ₙ₋₂' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ORG, S_G1, 'Алкены и алкины', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Ненасыщенные углеводороды' });
|
||
block(L, 'formula', ++b, { label: 'Алкены', tex: 'C_n H_{2n}' });
|
||
block(L, 'formula', ++b, { label: 'Алкины', tex: 'C_n H_{2n-2}' });
|
||
block(L, 'text', ++b, { text: 'Реакции присоединения алкенов:\n1. Гидрирование: CH₂=CH₂ + H₂ → C₂H₆ (Ni, t°)\n2. Галогенирование: CH₂=CH₂ + Br₂ → CH₂BrCH₂Br (обесцвечивает бромную воду!)\n3. Гидратация: CH₂=CH₂ + H₂O → C₂H₅OH\n4. Полимеризация: nCH₂=CH₂ → (CH₂–CH₂)ₙ' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Правило Марковникова: H присоединяется к более гидрогенизированному углероду несимметричного алкена.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Какая реакция невозможна для алканов?',
|
||
options: ['Горение', 'Замещение', 'Присоединение', 'Крекинг'],
|
||
correctIndex: 2,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ORG, S_G2, 'Спирты и карбоновые кислоты', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Кислородсодержащие соединения' });
|
||
block(L, 'matching', ++b, {
|
||
question: 'Класс → функциональная группа:',
|
||
pairs: [
|
||
{ left: 'Спирт', right: '–OH' },
|
||
{ left: 'Альдегид', right: '–CHO' },
|
||
{ left: 'Кислота', right: '–COOH' },
|
||
{ left: 'Амин', right: '–NH₂' },
|
||
],
|
||
});
|
||
block(L, 'text', ++b, { text: 'Свойства спиртов:\n1. + Na: 2C₂H₅OH + 2Na → 2C₂H₅ONa + H₂↑\n2. Окисление: C₂H₅OH → CH₃CHO → CH₃COOH\n3. Дегидратация (140°): → эфир\n4. Дегидратация (170°): C₂H₅OH → CH₂=CH₂ + H₂O' });
|
||
block(L, 'text', ++b, { text: 'Этерификация (кислота + спирт):\nCH₃COOH + C₂H₅OH ⇌ CH₃COOC₂H₅ + H₂O (кат. H₂SO₄)\n\nРеакция обратима. Продукт — сложный эфир.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'При дегидратации C₂H₅OH при 170°C образуется:',
|
||
options: ['Эфир', 'CH₂=CH₂', 'Ацетальдегид', 'Ацетилен'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ORG, S_G3, 'Углеводы и белки', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Биополимеры' });
|
||
block(L, 'formula', ++b, { label: 'Углеводы', tex: 'C_n(H_2O)_m' });
|
||
block(L, 'text', ++b, { text: 'Моносахариды: глюкоза и фруктоза C₆H₁₂O₆ (изомеры).\nДисахариды: сахароза C₁₂H₂₂O₁₁.\nПолисахариды: крахмал, целлюлоза (C₆H₁₀O₅)ₙ.\n\nБрожение: C₆H₁₂O₆ → 2C₂H₅OH + 2CO₂' });
|
||
block(L, 'text', ++b, { text: 'Белки — полимеры из аминокислот, связанных пептидными связями (–CO–NH–).\n\nСтруктуры: первичная (последовательность), вторичная (α-спираль), третичная (3D-форма), четвертичная (несколько цепей).' });
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'Денатурация — разрушение вторичной и выше структур (не первичной). Причины: нагрев, кислоты, щёлочи. Необратима.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Какая связь соединяет аминокислоты в белке?',
|
||
options: ['Ионная', 'Водородная', 'Пептидная (–CO–NH–)', 'Дисульфидная'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'timeline', ++b, {
|
||
items: [
|
||
{ date: '1869', title: 'ДНК выделена Мишером', text: 'Из лейкоцитов выделено «нуклеиновое вещество».' },
|
||
{ date: '1944', title: 'ДНК — носитель наследственности', text: 'Эйвери показал: именно ДНК передаёт наследственную информацию.' },
|
||
{ date: '1953', title: 'Двойная спираль', text: 'Уотсон и Крик предложили модель двойной спирали ДНК.' },
|
||
],
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Клетка и генетика [bio]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_BIO = course('bio', 'Клетка и генетика',
|
||
'Строение клетки, фотосинтез, клеточное дыхание, законы Менделя, ДНК и синтез белка', '🧬', 1);
|
||
const S_B1 = section(C_BIO, 'Строение клетки', 1);
|
||
const S_B2 = section(C_BIO, 'Обмен веществ', 2);
|
||
const S_B3 = section(C_BIO, 'Генетика', 3);
|
||
|
||
{
|
||
const L = lesson(C_BIO, S_B1, 'Строение эукариотической клетки', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Строение клетки' });
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Прокариоты (бактерии) — нет ядра и мембранных органоидов. Эукариоты (животные, растения, грибы) — есть ядро.' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Органоид', 'Функция'],
|
||
rows: [
|
||
['Клеточная мембрана', 'Барьер, транспорт, рецепция'],
|
||
['Ядро', 'Хранение ДНК, регуляция'],
|
||
['Митохондрии', 'Синтез АТФ (дыхание)'],
|
||
['Рибосомы', 'Синтез белков'],
|
||
['ЭПС шероховатая', 'Синтез и транспорт белков'],
|
||
['Аппарат Гольджи', 'Модификация, упаковка белков'],
|
||
['Лизосомы', 'Переваривание (аутолиз)'],
|
||
['Хлоропласты', 'Фотосинтез (только у растений)'],
|
||
],
|
||
});
|
||
block(L, 'diagram', ++b, {
|
||
code: "graph LR\n A[Профаза] --> B[Метафаза] --> C[Анафаза] --> D[Телофаза]\n D --> E[2 дочерние клетки 2n]",
|
||
caption: 'Фазы митоза',
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Какой органоид синтезирует АТФ?',
|
||
options: ['Рибосома', 'Хлоропласт', 'Митохондрия', 'Лизосома'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'Митоз vs мейоз?', back: 'Митоз: 2n → 2n (соматические, идентичные)\nМейоз: 2n → n (гаметы, 2 деления, кроссинговер)' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_BIO, S_B2, 'Фотосинтез и клеточное дыхание', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Энергетический обмен клетки' });
|
||
block(L, 'formula', ++b, { label: 'Фотосинтез', tex: '6CO_2 + 6H_2O \\xrightarrow{h\\nu} C_6H_{12}O_6 + 6O_2' });
|
||
block(L, 'columns', ++b, {
|
||
cols: [
|
||
{ content: '<strong>Световые реакции</strong> (тилакоиды):<br>Фотолиз воды → O₂<br>АТФ (фотофосфорилирование)<br>НАДФН' },
|
||
{ content: '<strong>Тёмновые реакции</strong> (строма, цикл Калвина):<br>Фиксация CO₂<br>Синтез глюкозы<br>Расход АТФ и НАДФН' },
|
||
],
|
||
});
|
||
block(L, 'formula', ++b, { label: 'Клеточное дыхание', tex: 'C_6H_{12}O_6 + 6O_2 \\rightarrow 6CO_2 + 6H_2O + 38\\,\\text{АТФ}' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Этап', 'Место', 'АТФ'],
|
||
rows: [
|
||
['Гликолиз', 'Цитоплазма', '2'],
|
||
['Цикл Кребса', 'Матрикс митохондрий', '2'],
|
||
['Окислит. фосфорилирование', 'Внутр. мембрана митохондрий', '34'],
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'info', text: 'Без O₂: анаэробный гликолиз → 2 АТФ. Аэробное дыхание → 38 АТФ, в 19 раз эффективнее!' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'На каком этапе дыхания выделяется CO₂?',
|
||
options: ['Гликолиз', 'Цикл Кребса', 'Окислительное фосфорилирование', 'Все этапы'],
|
||
correctIndex: 1,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_BIO, S_B3, 'Законы Менделя', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Генетика наследственности' });
|
||
block(L, 'text', ++b, { text: 'Ген — участок ДНК, кодирующий признак. Аллели — варианты гена. Доминантный (A) подавляет рецессивный (a). Генотип — набор генов, фенотип — его проявление.' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Скрещивание', 'Расщепление F₂ по фенотипу'],
|
||
rows: [
|
||
['Aa × Aa (моногибридное)', '3:1'],
|
||
['AaBb × AaBb (дигибридное)', '9:3:3:1'],
|
||
['Aa × Aa (неполное доминирование)', '1:2:1'],
|
||
['Aa × aa (анализирующее)', '1:1'],
|
||
],
|
||
});
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'III закон (независимое наследование) нарушается при сцеплении генов — если они в одной хромосоме.' });
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Скрещивание Aa × Aa. Вероятность фенотипа aa?',
|
||
options: ['25%', '50%', '75%', '100%'],
|
||
correctIndex: 0,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'II закон Менделя (расщепление)', back: 'Aa × Aa:\nФенотип: 3 доминантных : 1 рецессивный\nГенотип: 1 AA : 2 Aa : 1 aa' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_BIO, S_B3, 'ДНК и синтез белка', 4);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Молекула ДНК и реализация наследственности' });
|
||
block(L, 'text', ++b, { text: 'ДНК — двойная спираль. Принцип комплементарности: A–T (2 связи), G–C (3 связи).\nПравило Чаргаффа: %A = %T, %G = %C.' });
|
||
block(L, 'columns', ++b, {
|
||
cols: [
|
||
{ content: '<strong>Транскрипция</strong> (ядро):<br>ДНК → мРНК<br>РНК-полимераза<br>T заменяется на U' },
|
||
{ content: '<strong>Трансляция</strong> (рибосомы):<br>мРНК → белок<br>Кодон = 3 нуклеотида<br>Код универсален' },
|
||
],
|
||
});
|
||
block(L, 'formula', ++b, { label: 'Нуклеотиды ↔ аминокислоты', tex: 'N_{\\text{нукл}} = 3 \\times N_{\\text{а.к.}}' });
|
||
block(L, 'diagram', ++b, {
|
||
code: "graph LR\n DNA[ДНК] -->|Репликация| DNA2[ДНК-копия]\n DNA -->|Транскрипция| mRNA[мРНК]\n mRNA -->|Трансляция| P[Белок]",
|
||
caption: 'Центральная догма молекулярной биологии',
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'В ДНК 30% аденина. Сколько % тимина?',
|
||
options: ['30%', '20%', '70%', '60%'],
|
||
correctIndex: 0,
|
||
});
|
||
block(L, 'alert', ++b, { style: 'exam', text: 'В мРНК нет тимина — только урацил. При транскрипции: A→U, T→A, G→C, C→G.' });
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════
|
||
КУРС: Алгоритмы и структуры данных [inf]
|
||
══════════════════════════════════════════════════════════════ */
|
||
const C_ALG = course('inf', 'Алгоритмы и структуры данных',
|
||
'Сложность, сортировки, деревья и графы', '💻', 1);
|
||
const S_L1 = section(C_ALG, 'Основы', 1);
|
||
const S_L2 = section(C_ALG, 'Сортировки', 2);
|
||
const S_L3 = section(C_ALG, 'Структуры данных', 3);
|
||
|
||
{
|
||
const L = lesson(C_ALG, S_L1, 'Сложность алгоритмов (O-нотация)', 1);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Big-O нотация' });
|
||
block(L, 'text', ++b, { text: 'O-нотация описывает верхнюю оценку роста времени выполнения при n→∞. Константы и малые слагаемые игнорируются: O(2n) = O(n).' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Сложность', 'Название', 'Пример'],
|
||
rows: [
|
||
['O(1)', 'Константная', 'Доступ к элементу массива'],
|
||
['O(log n)', 'Логарифмическая', 'Бинарный поиск'],
|
||
['O(n)', 'Линейная', 'Линейный поиск'],
|
||
['O(n log n)', 'Квазилинейная', 'Быстрая сортировка'],
|
||
['O(n²)', 'Квадратичная', 'Сортировка пузырьком'],
|
||
['O(2ⁿ)', 'Экспоненциальная', 'Перебор подмножеств'],
|
||
],
|
||
});
|
||
block(L, 'code', ++b, {
|
||
lang: 'python',
|
||
code: `# O(log n) — бинарный поиск
|
||
def binary_search(arr, target):
|
||
left, right = 0, len(arr) - 1
|
||
while left <= right:
|
||
mid = (left + right) // 2
|
||
if arr[mid] == target: return mid
|
||
elif arr[mid] < target: left = mid + 1
|
||
else: right = mid - 1
|
||
return -1`,
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Два вложенных цикла по n элементам — сложность?',
|
||
options: ['O(n)', 'O(n log n)', 'O(n²)', 'O(2ⁿ)'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'O(n log n) vs O(n²) при n=1000?', back: 'O(n log n) ≈ 10 000 оп.\nO(n²) = 1 000 000 оп.\nРазница в 100 раз!' });
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ALG, S_L2, 'Алгоритмы сортировки', 2);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Сравнение алгоритмов сортировки' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Алгоритм', 'Лучшее', 'Среднее', 'Худшее', 'Память'],
|
||
rows: [
|
||
['Пузырьковая', 'O(n)', 'O(n²)', 'O(n²)', 'O(1)'],
|
||
['Вставками', 'O(n)', 'O(n²)', 'O(n²)', 'O(1)'],
|
||
['Слиянием', 'O(n log n)', 'O(n log n)', 'O(n log n)', 'O(n)'],
|
||
['Быстрая', 'O(n log n)', 'O(n log n)', 'O(n²)', 'O(log n)'],
|
||
['Кучей', 'O(n log n)', 'O(n log n)', 'O(n log n)', 'O(1)'],
|
||
],
|
||
});
|
||
block(L, 'code', ++b, {
|
||
lang: 'python',
|
||
code: `def quicksort(arr):
|
||
if len(arr) <= 1: return arr
|
||
pivot = arr[len(arr) // 2]
|
||
left = [x for x in arr if x < pivot]
|
||
mid = [x for x in arr if x == pivot]
|
||
right = [x for x in arr if x > pivot]
|
||
return quicksort(left) + mid + quicksort(right)`,
|
||
});
|
||
block(L, 'callout', ++b, { style: 'warning', text: 'QuickSort в худшем случае (уже отсортированный + крайний pivot) деградирует до O(n²). Решение: случайный pivot.' });
|
||
block(L, 'ordering', ++b, {
|
||
question: 'Шаги сортировки слиянием в правильном порядке:',
|
||
items: [
|
||
'Разделить массив на две половины',
|
||
'Рекурсивно отсортировать левую',
|
||
'Рекурсивно отсортировать правую',
|
||
'Слить две отсортированные половины',
|
||
],
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Какая сортировка гарантирует O(n log n) в худшем случае без доп. памяти?',
|
||
options: ['Быстрая', 'Слиянием', 'Кучей (HeapSort)', 'Пузырьковая'],
|
||
correctIndex: 2,
|
||
});
|
||
}
|
||
|
||
{
|
||
const L = lesson(C_ALG, S_L3, 'Деревья и графы', 3);
|
||
let b = 0;
|
||
block(L, 'heading', ++b, { level: 2, text: 'Бинарное дерево поиска (BST)' });
|
||
block(L, 'text', ++b, { text: 'В BST: всё левое поддерево < узел < всё правое. Поиск/вставка/удаление: O(h). У сбалансированного дерева h = O(log n).' });
|
||
block(L, 'code', ++b, {
|
||
lang: 'python',
|
||
code: `class Node:
|
||
def __init__(self, val):
|
||
self.val = val
|
||
self.left = self.right = None
|
||
|
||
def insert(root, val):
|
||
if not root: return Node(val)
|
||
if val < root.val: root.left = insert(root.left, val)
|
||
else: root.right = insert(root.right, val)
|
||
return root
|
||
|
||
def inorder(root): # обход: лево → корень → право
|
||
if root:
|
||
inorder(root.left)
|
||
print(root.val, end=' ')
|
||
inorder(root.right)`,
|
||
});
|
||
block(L, 'heading', ++b, { level: 3, text: 'Графы' });
|
||
block(L, 'table', ++b, {
|
||
header: ['Алгоритм', 'Задача', 'Сложность'],
|
||
rows: [
|
||
['BFS', 'Кратчайший путь (без весов)', 'O(V+E)'],
|
||
['DFS', 'Обход, топосортировка', 'O(V+E)'],
|
||
['Dijkstra', 'Кратчайший путь (≥0 веса)', 'O((V+E) log V)'],
|
||
['Bellman–Ford', 'Кратчайший путь (любые веса)', 'O(V·E)'],
|
||
],
|
||
});
|
||
block(L, 'diagram', ++b, {
|
||
code: "graph TD\n A((A)) -- 4 --> B((B))\n A -- 2 --> C((C))\n B -- 1 --> D((D))\n C -- 5 --> D\n B -- 3 --> E((E))\n D -- 1 --> E",
|
||
caption: 'Взвешенный граф',
|
||
});
|
||
block(L, 'quiz', ++b, {
|
||
question: 'Какой алгоритм находит кратчайшие пути при отрицательных весах рёбер?',
|
||
options: ['Dijkstra', 'BFS', 'Bellman–Ford', 'DFS'],
|
||
correctIndex: 2,
|
||
});
|
||
block(L, 'flashcard', ++b, { front: 'BFS vs DFS?', back: 'BFS — очередь, обход по уровням, кратчайший путь (без весов).\nDFS — стек/рекурсия, идёт вглубь, топосортировка, компоненты связности.' });
|
||
}
|
||
|
||
// ── Итог ──────────────────────────────────────────────────────────────
|
||
console.log(`✓ Theory seed complete:
|
||
[math] Тригонометрия (id=${C_TRIG}) — 4 урока
|
||
[math] Производные и интегралы (id=${C_CALC}) — 4 урока
|
||
[math] Теория вероятностей (id=${C_PROB}) — 3 урока
|
||
[phys] Механика (id=${C_MECH}) — 4 урока
|
||
[phys] Электричество и магнетизм (id=${C_EM}) — 4 урока
|
||
[phys] Оптика и волны (id=${C_OPT}) — 3 урока
|
||
[chem] Строение вещества (id=${C_ATOM}) — 4 урока
|
||
[chem] Органическая химия (id=${C_ORG}) — 4 урока
|
||
[bio] Клетка и генетика (id=${C_BIO}) — 4 урока
|
||
[inf] Алгоритмы (id=${C_ALG}) — 3 урока
|
||
Итого: 37 уроков`);
|
||
|
||
})(); // end transaction
|