From 6c1e0033409f6e7e97beb6be4d321251793f0592 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 17:57:45 +0300 Subject: [PATCH 01/11] =?UTF-8?q?docs(textbooks):=20=D0=BF=D0=BB=D0=B0?= =?UTF-8?q?=D0=BD=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=83=D1=87=D0=B5=D0=B1=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=A5=D0=B8=D0=BC=D0=B8=D1=8F=207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Полный план учебника Химия 7 (Беларусь, Шиманович 2023): 26 §, 4 главы, 5 лаб. опытов, 4 практ. работы. Архитектура hub + 4 главы (как Химия 8), карта интерактивов по каждому §, химический стандарт качества, миграция 046, фазы 0-6, ачивки. Строго по программе 7 класса. Co-Authored-By: Claude Opus 4.8 (1M context) --- plans/textbooks-7/PLAN_CHEMISTRY_7.md | 425 ++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 plans/textbooks-7/PLAN_CHEMISTRY_7.md diff --git a/plans/textbooks-7/PLAN_CHEMISTRY_7.md b/plans/textbooks-7/PLAN_CHEMISTRY_7.md new file mode 100644 index 0000000..59529b5 --- /dev/null +++ b/plans/textbooks-7/PLAN_CHEMISTRY_7.md @@ -0,0 +1,425 @@ +# План реализации: Химия 7 (Беларусь) — интерактивный наглядный учебник + +> Цель: создать **с нуля** интерактивный наглядный учебник по **всей программе 7 класса** +> в **современной архитектуре hub + главы** (как Химия 8 / Физика 7–11 / Алгебра / Геометрия), +> на уровне их качества, с поправкой на содержание 7 класса. +> +> **7 класс — это ПЕРВЫЙ курс химии у школьника.** Поэтому всё качественно и наглядно, +> БЕЗ количественной химии 8 класса (нет моля, молярной массы, расчётов по уравнениям, +> периодического закона, строения атома, химической связи, ТЭД, ионных уравнений, +> степени окисления). Акцент — на **первоначальные понятия** (вещество, атом, элемент, +> молекула, формула, валентность, химическая реакция, уравнение) и **первое знакомство** +> с кислородом, водородом, водой и важнейшими классами соединений (оксиды, кислоты, соли, +> основания) на уровне состава, наглядных свойств и качественных реакций. +> +> **Архитектура (по образцу Химии 8):** `chemistry_7_hub.html` (хаб-каталог глав) + **4 файла глав**. +> В БД — родитель `chemistry-7` + 4 ребёнка с `parent_slug`. Каждая глава — самостоятельная +> страница со своими § (см. карту ниже), модульный CSS/JS на предмет. Единый стандарт с +> [[../textbooks-8/PLAN_CHEMISTRY_8.md]] и [[../textbooks-9/PLAN_CHEMISTRY_9.md]]. + +--- + +## 🎯 Источник + +| Параметр | Значение | +|----------|----------| +| Книга | `himiya_7kl_shimanovich_rus_2023 (1).pdf` | +| Авторы | Шиманович И. Е., Красицкий В. А., Сечко О. И., Хвалюк В. Н. | +| Изд. | Минск, «Народная асвета», 2023 (2-е издание, пересмотренное), 175 с., тираж 121 000 | +| Структура | **4 главы, 26 §, 5 лабораторных опытов, 4 практические работы, 7 приложений** | +| Справочные таблицы (приложения) | ряд активности металлов (Прил.4), расчёт массовой доли элемента (Прил.3), бытовые названия веществ (Прил.6), единицы массы/объёма (Прил.7), вода в природе (Прил.5) | + +PDF лежит в `G:\Dev\Тесты\Методички\тест_6 класс\Книги\`. Оглавление — стр. 3–4 PDF. +Авторов **в hub НЕ показываем** (политика проекта, см. [[../textbooks-7/PLAN_PHYSICS_7.md]]). + +> **Важные отличия от Химии 8:** +> 1. Это **первый** курс химии — объяснять с нуля, простым языком, максимум наглядности. +> 2. **Качественно, не количественно**: валентность (а не степень окисления), `M_r` как +> «во сколько раз тяжелее» (без моля и `M`), баланс уравнений подбором (без стехиометрии). +> 3. Классы соединений даются **в порядке знакомства по ходу курса** (оксиды — в теме «Кислород», +> кислоты и соли — в теме «Водород», основания — в теме «Вода»), а НЕ единой главой как в 8 кл. +> 4. Индикаторы (лакмус, метилоранж, фенолфталеин), ряд активности, признаки реакций — +> **ядро наглядности** курса. +> +> **Соответствие «глава книги → файл главы → slug»:** +> +> | Глава книги | § | Файл | slug | Цвет | +> |---|---|---|---|---| +> | Гл.I Первоначальные химические понятия | 1–12 | `chemistry_7_ch1.html` | `chemistry-7-ch1` | emerald | +> | Гл.II Кислород | 13–17 | `chemistry_7_ch2.html` | `chemistry-7-ch2` | sky/cyan | +> | Гл.III Водород | 18–22 | `chemistry_7_ch3.html` | `chemistry-7-ch3` | violet | +> | Гл.IV Вода | 23–26 | `chemistry_7_ch4.html` | `chemistry-7-ch4` | blue | +> +> Хаб: `chemistry_7_hub.html` / slug `chemistry-7` (родитель в каталоге). +> Палитра hub — **emerald/green** (первые шаги в химии, природа, вода), не пересекается с +> Химией 8 (amber) и Химией 9 (amber). + +--- + +## 📗 ПОЛНАЯ КАРТА СОДЕРЖАНИЯ (26 §) + +Колонка **«Интерактив»** — главный наглядный элемент сверх текста (минимум 1 «звёздный» +виджет на §; полный набор — в стандарте ниже). + +### ГЛАВА I. Первоначальные химические понятия (§§1–12) — *emerald* +| § | Тема | Ключ | Интерактив (звёздный виджет) | +|---|------|------|------------------------------| +| §1 | Химия — наука о веществах | вещество vs тело; свойства веществ; наблюдение/опыт; лаб. оборудование, ТБ | **Виртуальная лаборатория-знакомство** (наведи на оборудование → название/назначение + правило ТБ) + DnD «тело / вещество» | +| §2 | Чистые вещества и смеси | однородные/неоднородные смеси; способы разделения | **Симулятор разделения смесей** (фильтрование, выпаривание, отстаивание, дистилляция, магнит) + классификатор | +| §3 | Атомы. Химические элементы | атом; химический элемент; символы элементов | **Тренажёр символов элементов** (название↔символ) + мини-ПСХЭ как каталог (`miniPeriodic`) | +| §4 | Относительная атомная масса | `A_r`; а.е.м. = 1/12 массы атома C | **«Весы атомов»**: во сколько раз атом X тяжелее H/C; поиск `A_r` по элементу | +| §5 | Молекулы. Простые вещества | молекула; простое вещество; атомность O₂, O₃, H₂, металлы | **3D-модели простых веществ** (biochem-core: H₂, O₂, O₃, N₂) + классификатор «атом/молекула/простое» | +| §6 | Сложные вещества | атомы разных элементов: H₂O, CO₂, CH₄, NH₃ | **3D-модели сложных веществ** + DnD «простое / сложное» | +| §7 | Химическая формула | индекс, коэффициент; качественный/количественный состав; чтение формул | **Парсер/конструктор формулы** (формула → какие атомы и сколько) + «читалка» (аш-два-о) | +| §8 | Относительная молекулярная масса | `M_r = Σ A_r·index` | **Калькулятор `M_r`** (biochem-core) с пошаговым разбором + калькулятор массовой доли элемента `w(A)` (Прил.3) | +| §9 | Валентность | валентность по водороду; H–I, O–II; составление формул по валентности | **Конструктор формул по валентности** (2 элемента + валентности → НОК индексов + проверка) + «черточки валентности» | +| §10 | Явления физические и химические. Признаки химических реакций | признаки: цвет, осадок, газ, запах, тепло/свет | **Детектор признаков реакции** (малахит→CuO+H₂O+CO₂; CuSO₄+NaOH→синий осадок) `testTube` + DnD «физ./хим. явление» | +| §11 | Закон сохранения массы. Химические уравнения | m(реагентов)=m(продуктов); Ломоносов/Лавуазье | **«Весы сохранения массы»** (анимация: реагенты ⇄ продукты, баланс) + знакомство с уравнением | +| §12 | Составление уравнений химических реакций | подбор коэффициентов; баланс атомов | **Балансировщик уравнений** (`equationBalancer`: P+O₂→P₂O₅, Fe+O₂→Fe₃O₄, H₂+O₂→H₂O, CH₄+O₂→CO₂+H₂O) | + +**Лаб. опыт 1** (после §10): признаки протекания химических реакций. +**Практическая работа 1** (после §2): знакомство с химической лабораторией, разделение смесей. + +### ГЛАВА II. Кислород (§§13–17) — *sky/cyan* +| § | Тема | Ключ | Интерактив | +|---|------|------|------------| +| §13 | Воздух как смесь газов | состав воздуха: N₂ 78 %, O₂ 21 %, Ar, CO₂ | **Интерактивная диаграмма состава воздуха** (круговая, клик → доля газа) + связь со §2 (смесь) | +| §14 | Кислород как химический элемент и простое вещество | O (элемент) vs O₂ (вещество); озон O₃; нахождение в природе; физ. свойства | **Переключатель «элемент ↔ простое вещество»** + 3D O₂/O₃ + «паспорт» кислорода | +| §15 | Химические свойства кислорода | горение: C, S, P, Fe + O₂ → оксиды; окисление, медленное окисление | **Симулятор горения** (выбери вещество → реакция с O₂ → оксид + уравнение + пламя) `chemEq` | +| §16 | Оксиды | оксид = Э + O (бинарное); названия; CuO, CO₂, SO₂, P₂O₅, Fe₃O₄, H₂O | **Конструктор оксида** (элемент + валентность → формула) + классификатор «оксид / не оксид» | +| §17 | Получение кислорода | разложение KMnO₄, H₂O₂ (+кат. MnO₂); катализатор; реакция разложения | **Схема получения O₂** (разложение при нагреве; катализатор — ускоряет, не расходуется, анимация) | + +**Лаб. опыт 2** (после §13): сборка простейших приборов для получения и собирания газов. +**Практическая работа 2** (после §17): получение кислорода и изучение его свойств (тлеющая лучинка → вспыхивает). + +### ГЛАВА III. Водород (§§18–22) — *violet* +| § | Тема | Ключ | Интерактив | +|---|------|------|------------| +| §18 | Водород — химический элемент и простое вещество | H, H₂; самый лёгкий газ; нахождение; физ. свойства | **3D-модель H₂** + «паспорт» водорода + демонстрация «легче воздуха» | +| §19 | Химические свойства водорода | H₂+O₂→H₂O (гремучий газ); H₂+CuO→Cu+H₂O (восстановление); восстановитель | **Симулятор реакций H₂** (восстановление оксида: чёрный CuO → красная Cu) `chemEq` | +| §20 | Понятие о кислотах | кислота = H + кислотный остаток; HCl, H₂SO₄, HNO₃, H₂SO₃, H₂CO₃; индикаторы | **`indicatorScale`** (лакмус красный, метилоранж розовый в кислоте) + конструктор «кислота → остаток» | +| §21 | Взаимодействие кислот с металлами | Me + кислота → соль + H₂↑; ряд активности (Прил.4); Cu/Ag не реагируют | **Интерактивный ряд активности** (`activitySeries`: K…H₂…Au, клик металл → реагирует/нет, пузырьки H₂) + Zn+HCl (`testTube`) | +| §22 | Соли — продукты замещения H на металл | соль = Me + кислотный остаток; реакция замещения; хлориды/сульфаты/нитраты | **Конструктор солей** (металл + кислотный остаток → формула по валентности) + анимация замещения | + +**Лаб. опыт 3** (после §20): действие кислот на индикаторы. +**Лаб. опыт 4** (после §21): взаимодействие серной и соляной кислот с металлами. +**Практическая работа 3** (после §22): получение водорода и изучение его свойств (Zn+HCl, «гремучий газ»). + +### ГЛАВА IV. Вода (§§23–26) — *blue* +| § | Тема | Ключ | Интерактив | +|---|------|------|------------| +| §23 | Состав, физические и химические свойства воды | H₂O; разложение эл. током → H₂+O₂ (2:1); Na+H₂O→щёлочь+H₂; оксид+H₂O→кислота/основание; круговорот | **Разложение воды** (анимация 2:1 H₂:O₂) + реакции воды (Na, CaO, CO₂) + круговорот воды (Прил.5) | +| §24 | Основания как сложные вещества | основание = Me + OH; NaOH, KOH, Ca(OH)₂; щёлочи vs нерастворимые; индикаторы | **Конструктор оснований** Me(OH)ₙ + **`indicatorScale`** (фенолфталеин малиновый, лакмус синий) + классификатор | +| §25 | Реакция нейтрализации | кислота + основание → соль + вода; HCl+NaOH→NaCl+H₂O; экзотермическая | **Анимация нейтрализации** (фенолфталеин малиновый → бесцветный; sim `titration`) `chemEq` | +| §26 | Охрана окружающей среды | загрязнение воды/воздуха; кислотные дожди; очистка воды; бережное отношение | **Инфографика-исследование** (источники загрязнения, очистка воды — Прил.5) | + +**Лаб. опыт 5** (после §24): действие щелочей на индикаторы. +**Практическая работа 4** (после §25): реакция нейтрализации. + +**Итого**: 26 §, 4 главы, **5 лаб. опытов** (§10, §13, §20, §21, §24), **4 практические работы** (§2, §17, §22, §25), 7 приложений. + +--- + +## ⚗️ ХИМИЧЕСКИЙ СТАНДАРТ КАЧЕСТВА + +### A. Движки и переиспользуемые активы (всё уже есть в проекте) + +| Что нужно | Берём из | Файл / id | +|-----------|----------|-----------| +| Парсинг формул, `M_r`/`A_r`, состав вещества | biochem-core | `frontend/js/biochem-core.js` ✅ | +| 2D/3D шаростержневые модели молекул | biochem-core | `frontend/js/biochem-core.js` ✅ | +| Рендер формул/ионов/уравнений (`formula`, `ionLabel`, `chemEq`) | chem8_svg | `frontend/js/chem8_svg.js` (`window.Chem8`) ✅ | +| Индикаторы, ряд активности, классификаторы, `testTube` | chem8_svg (виджеты разд. B Химии 8) | переиспользовать / доработать ✅ | +| Интерактивная ПСХЭ (как каталог элементов) | sim `periodic` | реестр `_register-all.js` ✅ | +| Песочница реакций | sim `chemsandbox` | реестр ✅ | +| Титрование / нейтрализация | sim `titration` | реестр ✅ | +| Качественный анализ / индикаторы | sim `qualanalysis` | реестр ✅ | + +Монтаж sim: контейнер `
` + `openSim('')` (или прямой mount через +`window.LabRegistry`), как на остальных страницах. Глоссарий — по образцу `chem8_glossary.js`. + +### B. Хелпер `/js/chem7_svg.js` (тонкая надстройка над `chem8_svg.js`) + +> **Рекомендация:** НЕ дублировать химические примитивы. `formula`, `ionLabel`, `chemEq`, +> `indicatorScale`, `activitySeries`, `testTube`, `classifier` уже есть/планируются в +> `chem8_svg.js` (`window.Chem8`). `chem7_svg.js` должен **переиспользовать** их (через +> `window.Chem8.*`) и добавить только то, что специфично для 7 класса. План Химии 9 уже +> рекомендует свести химические примитивы в общий `chem_svg.js` — при совместной разработке +> довести до единого shared-модуля. Молекулы — **только через `biochem-core.js`**. + +```js +// 1. Конструктор формулы по валентности (звёздный виджет §9): +// выбор 2 элементов + их валентностей → НОК → индексы → формула + структурная схема +const valenceBuilder = (mount, {elements}) => { /* H-I, O-II, Cl-I, ...; НОК(v1,v2) */ }; + +// 2. Тренажёр символов элементов (§3): название ↔ символ (карточки/квикфайр) +const elementSymbolDrill = (mount, {set}) => { /* H,O,C,N,Na,Cl,Fe,Cu,Ca,Al,Zn,S,P,Mg,K */ }; + +// 3. «Весы атомов» (§4): сравнение масс атомов в а.е.м., во сколько раз тяжелее +const atomBalance = (mount, {a, b}) => { /* визуальные весы + A_r */ }; + +// 4. Разделитель смесей (§2): смесь → выбор метода (фильтр/выпаривание/магнит/дистилляция) +const mixtureSeparator = (mount, {mixtures}) => { /* анимация разделения, проверка метода */ }; + +// 5. Детектор признаков реакции (§10): анимация опыта + чек-лист признаков (цвет/осадок/газ/запах/тепло) +const reactionSigns = (mount, {demo}) => { /* малахит↘, CuSO4+NaOH↘синий, S+O2 запах */ }; + +// 6. «Весы сохранения массы» (§11): слева реагенты, справа продукты, стрелка баланса m=m +const massConservation = (mount, {eq}) => { /* m(реаг)=m(прод), визуальные весы */ }; + +// 7. Балансировщик уравнений подбором (§12): матрица атомов, подсветка дисбаланса, без стехиометрии +const equationBalancer = (mount, {skeleton}) => { /* коэффициенты, проверка баланса атомов */ }; + +// 8. Симулятор горения (§15): вещество (C/S/P/Fe) + O2 → оксид + пламя/искры + уравнение +const combustionSim = (mount, {fuel}) => { /* SVG-пламя + chemEq продукта */ }; + +// 9. Конструктор оксида/соли/основания (§16,22,24): Me/Э + валентность/остаток → формула +const compoundBuilder = (mount, {kind}) => { /* kind: 'oxide'|'salt'|'base'; по валентности */ }; + +// 10. Диаграмма состава воздуха (§13): интерактивная круговая (N2 78, O2 21, Ar, CO2) +const airComposition = (mount) => { /* клик сектор → газ + доля */ }; + +// 11. Разложение воды (§23): анимация электролиза, объёмы H2:O2 = 2:1 +const waterDecomp = (mount) => { /* две пробирки над электродами, 2:1 */ }; + +// 12. Калькулятор массовой доли элемента (Прил.3, §8): w(A) = A_r·x / M_r, пошагово +const massFraction = (mount, {formula}) => { /* w(O) в H3PO4 = 65,3 % */ }; +``` + +`indicatorScale`, `activitySeries`, `testTube`, `classifier`, `miniPeriodic` — **из `chem8_svg.js`** +(если там пока заглушки — реализовать там же и переиспользовать в обоих учебниках). + +### C. Правила рендера химии (обязательны с §1) + +1. **Формулы веществ** — нижние индексы для атомов (`H₂O`, `CaCO₃`) через `Chem8.formula`/ + `chemEq`, не «сырой» текст. Зарядов ионов и степеней окисления в 7 классе НЕТ. +2. **Уравнения реакций** — всегда сбалансированы; стрелки `=`/`→`, `↑` (газ), `↓` (осадок), + условия над стрелкой (`t`, кат., эл. ток). Только реакции из программы 7 класса. +3. **Валентность**, а не степень окисления: схемы «черточек» (H–Cl, H–O–H), составление формул + по валентности (НОК индексов). +4. **Признак реакции** — для каждой качественной/наглядной реакции показывать видимый признак: + цвет осадка, пузырьки газа, изменение окраски индикатора, пламя (через `testTube`/`indicatorScale`/`combustionSim`). +5. **Молекулярные модели** — структурная формула + 3D (biochem-core) для изучаемых веществ + (H₂, O₂, O₃, N₂, H₂O, CO₂, CH₄, NH₃, HCl). +6. **Цвета — химически достоверные**: осадок Cu(OH)₂ голубой, малахит зелёный→CuO чёрный, + медь красная, сера жёлтая; индикаторы: лакмус (кислота — красный, щёлочь — синий), + метилоранж (кислота — розовый), фенолфталеин (щёлочь — малиновый). +7. **Безопасность** — где уместно (кислоты, щёлочи, Na+вода, гремучий газ) — заметка-«скрепка»; + в практических работах — блок «Правила ТБ». +8. **KaTeX-эскейпы** — в JS-шаблонах двойной backslash (`\\to`, `\\downarrow`, `\\uparrow`). +9. **Drag/слайдеры** — `window`-listeners + `{passive:false}` + state ВЫШЕ `redraw()` + (стандарт геометрии), `touch-action:none` на draggable SVG/canvas, **без `setPointerCapture`**. +10. **Без эмоджи** — только inline SVG `.ic` (правило проекта [[feedback_no_emoji]]). +11. **Простой язык** — это первый курс химии; определения короткие, на каждое понятие — наглядность. + +### D. Типы интерактивов по темам 7 класса + +| Тип темы | Интерактив | +|----------|------------| +| Вещества/смеси (§1,2,13) | `mixtureSeparator`, классификатор, `airComposition`, виртуальная лаборатория | +| Атомы/элементы (§3,4) | `elementSymbolDrill`, `miniPeriodic`, `atomBalance` | +| Молекулы/вещества (§5,6) | biochem-core 3D, классификатор «простое/сложное» | +| Формулы/`M_r`/валентность (§7,8,9) | парсер формулы, калькулятор `M_r`, `valenceBuilder`, `massFraction` | +| Реакции/уравнения (§10,11,12) | `reactionSigns`, `massConservation`, `equationBalancer` | +| Кислород/горение/оксиды (§14,15,16,17) | `combustionSim`, `compoundBuilder('oxide')`, схема получения, sim `chemsandbox` | +| Водород/кислоты/соли (§18,19,20,21,22) | 3D H₂, `indicatorScale`, `activitySeries`, `testTube`, `compoundBuilder('salt')` | +| Вода/основания/нейтрализация (§23,24,25) | `waterDecomp`, `compoundBuilder('base')`, `indicatorScale`, sim `titration` | +| Прикладное/экология (§26, Прил.) | инфографика-исследование, бытовые названия (Прил.6), единицы (Прил.7) | + +--- + +## 📦 СТРУКТУРА КАЖДОГО § (стандарт наполнения) + +**Теория (3 карточки):** +- `theory` — основное определение/понятие + наглядная SVG/модель +- `rule` — ключевое правило/закономерность/формула (рамка) +- `example` — разобранный пример (реакция / составление формулы / опыт) с пошаговым рендером +- (для прикладных §) `apply` — применение/значение (инфографика) + +**Интерактивы (3–5 на §):** +1. **Звёздный виджет** темы (из карты содержания) +2. **Конструктор/симулятор** (drag / slider / sim из реестра / biochem-core 3D) +3. **DnD-классификатор** (вещество/смесь, простое/сложное, физ./хим. явление, оксид/кислота/соль/основание) +4. **Тренажёр** — 5 задач с inline-наглядностью (формула / уравнение / модель / признак в условии) +5. **Босс §** — 3–4 интеграционные задачи (+5 XP каждая) + +**Дополнительно:** пополнение глоссария (термины §, `[[ссылки]]`), «Вопросы и задания» из +учебника (адаптированные, с проверкой), проходящий jsdom-тест страницы. + +**Финал главы:** итоговая шпаргалка (mini-cards), карта связей (SVG-граф понятий главы), +5–7 интегрированных боссов (+10 XP), achievement «Мастер главы N» (+50 XP, confetti), +кнопка перехода к следующей главе. + +--- + +## 🚀 ПОРЯДОК РЕАЛИЗАЦИИ (по фазам) + +### Phase 0 — Фундамент (hub + каркасы глав) +- **`chemistry_7_hub.html`** — хаб-каталог 4 глав по образцу `chemistry_8_hub.html`: палитра + **emerald** (`--pri:#059669`/`#047857`, `--pri-soft:#d1fae5`; header gradient + `linear-gradient(110deg,#065f46 0%,#059669 55%,#6ee7b7 100%)`), водяной знак «ХИМИЯ», + карточки глав с прогрессом (грузятся из `/api/textbooks/chemistry-7/children`), + блок «Финал курса» (шпаргалка + боссы — наполняется в Phase 5), achievement-strip + «Химик 7 класса», тема (localStorage `chemistry7_theme`), KaTeX CDN, `/js/api.js`+`/js/xp.js`. +- **4 файла глав** `chemistry_7_ch1..ch4.html` — на Phase 0 валидные каркасы-заглушки + (header с водяным знаком, hero, sidebar-оглавление §, контейнер параграфов, XP/tracker-интеграция), + наполнение § — в Phase 1–4. Подключить `?v=YYYYMMDD` (cache-busting) на все JS; + sidebar-фикс `@media(min-width:981px){#sidebar-btn{display:none}}`. +- **`/js/chem7_svg.js`** (хелперы B — заглушки → реализация по фазам); подключить + `chem8_svg.js`, `biochem-core.js` и нужные симуляторы на страницах глав; `chem7_glossary.js`. +- **Миграция `046_chemistry7_hub.sql`** (следующий номер после `045_bio_pathways.sql`): + **INSERT** родителя `chemistry-7` (`html_path='chemistry_7_hub.html'`, `para_count=26`, + `color='emerald'`, `parent_slug=NULL`, `subject='chemistry'`, `grade=7`) + **4 детей** + `chemistry-7-ch1..ch4` (`parent_slug='chemistry-7'`, свои `html_path`/`para_count`/`color`/`sort_order`) + — по образцу `041_chemistry8_hub.sql`. Применить `npm run migrate` + рестарт dev-сервера. +- jsdom-тест-каркас: хаб строится, все 5 файлов парсятся, ссылки глав ведут на существующие slug. + +### Phase 1 — Глава I «Первоначальные химические понятия» (§§1–12) + ЛО1 + ПР1 +Самая фундаментальная: закладываем движки, от которых зависит весь курс — `elementSymbolDrill`, +парсер формулы + калькулятор `M_r` (biochem-core), `valenceBuilder`, `equationBalancer`, +`reactionSigns`, `massConservation`, `mixtureSeparator`. По 2–3 § за волну. + +### Phase 2 — Глава II «Кислород» (§§13–17) + ЛО2 + ПР2 +`airComposition`, `combustionSim`, `compoundBuilder('oxide')`, схема получения O₂ (катализатор), +sim `chemsandbox`. Первые реакции горения и понятие оксида. + +### Phase 3 — Глава III «Водород» (§§18–22) + ЛО3 + ЛО4 + ПР3 +3D H₂, восстановление CuO, `indicatorScale` (лакмус/метилоранж), `activitySeries` (ряд активности, +Прил.4), `testTube` (Zn+HCl, пузырьки H₂), `compoundBuilder('salt')`. Понятия «кислота» и «соль». + +### Phase 4 — Глава IV «Вода» (§§23–26) + ЛО5 + ПР4 +`waterDecomp` (разложение 2:1), реакции воды, `compoundBuilder('base')`, `indicatorScale` +(фенолфталеин), нейтрализация (sim `titration`), экология/круговорот воды (Прил.5). + +### Phase 5 — Финалы глав + общий финал учебника +Шпаргалки и карты связей по каждой главе; интегрированные боссы + achievements; +**большой финал**: «химический паспорт вещества» (тело/вещество → состав → формула → реакции), +итоговый босс-квест, ачивка «Химик 7 класса»; глоссарий собран и связан `[[ссылками]]`; +страница приложений-справочников (Прил.3,4,6,7 как интерактивные виджеты). + +### Phase 6 — Качество и админка +Полный прогон jsdom-тестов (каждый § — builder не stub); аудит баланса всех уравнений и +KaTeX/`chemEq`-эскейпов; синхронизация с админкой (если новые sim в `lab.html` → +обновить `ADMIN_SIMS` в `admin.html` — [[feedback_sims_admin_sync]]); проверка доступа +по классам/ученикам ([[project_content_access]], `/api/access`); появление в каталоге +`textbooks.html` в секции 7 класса (рядом с Физикой 7, Алгеброй 7, Геометрией 7). + +> Рекомендуемый темп: внутри фазы — по 2–3 § за «волну», каждая волна = commit + +> проходящий jsdom-тест (правило CLAUDE.md: commit изменённых файлов + push сразу). + +--- + +## 🗄️ ИНТЕГРАЦИЯ С ПРОЕКТОМ + +| Точка | Действие | +|-------|----------| +| **БД каталог** | `chemistry-7` в `textbooks` **отсутствует** → миграция `046_chemistry7_hub.sql`: INSERT родитель + 4 ребёнка (образец — `041_chemistry8_hub.sql`). Каталог `/api/textbooks` показывает только `parent_slug IS NULL`; хаб тянет детей через `/api/textbooks/chemistry-7/children`. | +| **Прогресс/XP** | Автоматически: `textbook-xp-widget.js` (+5 XP/§), `textbook-tracker.js`, `LS.xp`. Доп. XP за боссов — по образцу `phys7_ch1_widgets.js`. localStorage-ключи прогресса — `chem7-*`. | +| **Симуляторы** | Реестр `frontend/js/labs/_register-all.js`. Нужные химические sim уже зарегистрированы: `periodic`, `chemsandbox`, `titration`, `qualanalysis`. | +| **Молекулы** | `biochem-core.js` (парсинг, `M_r`, 2D/3D-модели). | +| **Хим. примитивы** | `chem8_svg.js` (`window.Chem8`: `formula`, `ionLabel`, `chemEq` + виджеты) — переиспользовать; `chem7_svg.js` — тонкая надстройка (валентность, горение, смеси, весы массы). | +| **Бэкенд** | Роуты готовы: `backend/src/routes/textbooks.js` (catalog/children/progress/bookmarks). Доступ: `backend/src/services/contentAccess.js`. | +| **Глоссарий** | `chem7_glossary.js` по образцу `chem8_glossary.js` (всплывающие определения терминов). | +| **Тесты** | `cd backend && npm test` (jsdom). На каждый § — тест: страница строится, builder не stub, уравнения сбалансированы. Учитывать 3 pre-existing baseline-фейла (`BASELINE_FAILS=3`). | +| **Админка** | Новые sim в `lab.html` → синхронно `ADMIN_SIMS` в `admin.html`. | +| **Каталог-страница** | `frontend/textbooks.html` — карточка появляется автоматически из каталога (секция 7 класса). | + +--- + +## ⚠️ КРИТИЧЕСКИЕ ПРАВИЛА + +### ❌ НЕ делать +- Контент 8 класса: моль, молярная масса/объём, расчёты по уравнениям, периодический закон, + строение атома, химическая связь, ТЭД, ионные уравнения, степень окисления — **в 7 классе НЕТ**. +- «Сырые» формулы текстом — только `Chem8.formula`/`chemEq`/KaTeX. +- Несбалансированные уравнения (аудит баланса перед commit). Реакции — только из программы 7 кл. +- Дублировать молекулярный движок или химические примитивы — переиспользовать `biochem-core.js` и `chem8_svg.js`. +- `setPointerCapture` (теряется после `innerHTML`-replace) → `window`-listeners + state-flag. +- `\to`, `\uparrow`, `\downarrow` без удвоения backslash в JS-шаблонах. +- Эмодзи — запрещены; только inline SVG `.ic` ([[feedback_no_emoji]]). +- Авторов учебника в hub/footer — НЕ упоминаем. +- **Grep tool — запрещён**; поиск только `ast-index` (правила проекта, [[reference_sqlite_node]]). + +### ✅ Обязательно +- Каждый commit → jsdom-тест 100 % pass (сверх baseline). Push сразу после коммита, файлы поимённо. +- Аудит баланса уравнений + KaTeX-эскейпов после каждой волны. +- Каждая наглядная/качественная реакция = уравнение **+ видимый признак** (цвет/газ/осадок/индикатор/пламя). +- Цвета осадков/индикаторов/пламени — химически достоверные. +- Все builder-функции в конце финальной волны главы — НЕ stub'ы. +- Простой, дружелюбный язык первого курса; на каждое понятие — наглядность. +- Перед работой по ветке `feature/lab-content-engine` — `git fetch` (параллельные сессии, + [[project_concurrent_sessions_branch]]). + +--- + +## 📊 Оценка объёма + +| Глава | § | Лаб/ПР | Ожидаемый LOC | +|--------|---|--------|---------------| +| Гл.I Первоначальные понятия | 12 | ЛО1 + ПР1 | ~10 000 (+`valenceBuilder`, `equationBalancer`, `reactionSigns`, `massConservation`, `mixtureSeparator`) | +| Гл.II Кислород | 5 | ЛО2 + ПР2 | ~5 000 (+`combustionSim`, `compoundBuilder('oxide')`, `airComposition`) | +| Гл.III Водород | 5 | ЛО3,4 + ПР3 | ~6 000 (+`indicatorScale`, `activitySeries`, `testTube`, `compoundBuilder('salt')`) | +| Гл.IV Вода | 4 | ЛО5 + ПР4 | ~5 000 (+`waterDecomp`, `compoundBuilder('base')`, sim `titration`) | +| Финалы глав + общий финал + приложения | — | — | ~4 000 | +| `/js/chem7_svg.js` хелперы | — | — | ~2 500 | +| Хаб + 4 каркаса глав (Phase 0) | — | — | ~2 500 | +| **Итого** | **26** | **5 ЛО + 4 ПР** | **~35 000 LOC** | + +Меньше Химии 8 (~64 000) — курс короче (26 § против 52) и качественный (без тяжёлых +количественных движков). По плотности интерактива — на уровне Химии 8 / Физики 7. + +--- + +## 🏆 Достижения и XP (оценка) + +| Slug | Название | Условие | XP | +|------|----------|---------|-----| +| `chemistry7_ch1_master` | Первооткрыватель | Все боссы финала главы I | 50 | +| `chemistry7_ch2_master` | Повелитель кислорода | Все боссы финала главы II | 50 | +| `chemistry7_ch3_master` | Знаток водорода | Все боссы финала главы III | 50 | +| `chemistry7_ch4_master` | Хранитель воды | Все боссы финала главы IV | 50 | +| `chemistry7_course_master` | Химик 7 класса | Все 4 ачивки глав + финальный босс-квест | 150 | + +XP-оценка полного прохождения: 26 § × ~50 XP + 4 ач × 50 + финал 150 ≈ **1 700 XP**. + +--- + +## 🧠 Чем Химия 7 отличается от Химии 8 + +| Аспект | Химия 7 (первый курс) | Химия 8 | +|--------|----------------------|---------| +| Количество вещества (моль) | **Нет** | Есть (вводный раздел) | +| `M_r` / `M` | `M_r` как «во сколько раз тяжелее» (без моля) | Молярная масса `M`, расчёты | +| Состав по формуле | Качественный/количественный; массовая доля элемента (Прил.3) | + расчёты по уравнениям | +| Валентность vs степень окисления | **Валентность** | Степень окисления, ОВР | +| Классы соединений | Знакомство по ходу курса (оксиды, кислоты, соли, основания) | Системно, единой главой + генетическая связь | +| Периодический закон, строение атома, связь | **Нет** | Есть (главы 2–4) | +| ТЭД, ионные уравнения, растворы (`w`,`c`) | **Нет** | Есть (глава 6) | +| Кислоты/основания | Понятие, индикаторы, реакции с Me, нейтрализация | + классификация, свойства, получение | +| Главный визуал курса | Балансировщик уравнений + ряд активности + индикаторы | Количественные расчёты + ПСХЭ | + +Курсы дополняют друг друга: Химия 7 закладывает язык химии (вещество, формула, валентность, +уравнение) и первое знакомство с веществами; Химия 8 переводит это в количественную плоскость. + +--- + +## 📚 Связанные планы +- [PLAN_CHEMISTRY_8.md](../textbooks-8/PLAN_CHEMISTRY_8.md) — образец архитектуры (hub + главы), химический стандарт качества, `chem8_svg.js`, миграция-шаблон. +- [PLAN_CHEMISTRY_9.md](../textbooks-9/PLAN_CHEMISTRY_9.md) — химический стандарт, рекомендация о shared `chem_svg.js`. +- [PLAN_PHYSICS_7.md](PLAN_PHYSICS_7.md) — образец полного курса 7 класса (фазы, ачивки, финал курса, политика «без авторов», cache-busting, sidebar-фикс). + +--- + +## 🎬 Запуск + +**Phase 0**: `chemistry_7_hub.html` (по образцу `chemistry_8_hub.html`, палитра emerald) + +4 каркаса глав (`chemistry_7_ch1..ch4.html`) + `/js/chem7_svg.js` (скелет) + подключение +`chem8_svg.js`/`biochem-core.js`/симуляторов + миграция `046_chemistry7_hub.sql` (родитель + 4 ребёнка) ++ `npm run migrate` + рестарт + jsdom-каркас. +**Phase 1**: Глава I (§§1–12) — закладываем движки (`valenceBuilder`, `equationBalancer`, +парсер формулы, `M_r`, `reactionSigns`), от которых зависят все главы. + +Дальше — последовательно по главам (Phase 2 → 4), затем финалы (Phase 5) и качество (Phase 6). + +После завершения **Химия 7 → первый учебник, полностью покрывающий химию с нуля** для самого +младшего класса в линейке (7→8→9), закрывает «нижнюю» ступень химии. From 5381679c68108ce457940acdc59f1b162f04e870 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:12:55 +0300 Subject: [PATCH 02/11] =?UTF-8?q?chore:=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE?= =?UTF-8?q?=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BD=D0=B5=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=87=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20(=D0=B1?= =?UTF-8?q?=D0=B8=D0=BE=D1=85=D0=B8=D0=BC=D0=B8=D1=8F=20+=20System=20Healt?= =?UTF-8?q?h=20+=20lab/textbooks)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зафиксирована накопленная незакоммиченная работа рабочего дерева, КРОМЕ файлов учебника «Химия 7» (migration 046, chemistry_7_*.html, chem7_svg.js, тест — оставлены незакоммиченными по запросу). Включает: модуль биохимии (ядро BIO, 3D VSEPR, химдвижок, баланс, challenges, пути из БД), System Health Level 1 (вердикт/мониторинг), а также frontend- страницы и lab/textbooks-правки параллельной сессии. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/settings.json | 16 +- backend/scripts/audit_chem8.js | 110 + backend/scripts/biochem_pathways_data.js | 1290 +++++++++++ backend/scripts/gen_phys10_ch.js | 1128 +++++++++ backend/scripts/gen_phys10_hub.js | 307 +++ backend/scripts/patch_html.py | 94 + backend/scripts/patch_interfsim.js | 409 ++++ backend/scripts/patch_interfsim2.js | 235 ++ backend/scripts/patch_interfsim3.js | 19 + backend/scripts/patch_interfsim_html.js | 187 ++ backend/scripts/patch_optics.py | 204 ++ backend/scripts/patch_optics2.py | 44 + backend/scripts/patch_optics3.py | 91 + backend/scripts/review_geom10.js | 85 + backend/scripts/review_geom10_2.js | 92 + backend/scripts/review_geom10_3.js | 73 + backend/scripts/review_geom11.js | 227 ++ backend/scripts/seed_biochem_challenges.js | 19 + backend/scripts/seed_biochem_pathways.js | 42 + backend/scripts/seed_math.js | 569 +++++ backend/scripts/seed_math2.js | 305 +++ backend/scripts/seed_phys.js | 594 +++++ backend/scripts/seed_phys2.js | 288 +++ backend/src/controllers/adminController.js | 118 +- backend/src/controllers/biochemController.js | 109 +- .../src/controllers/gamification/service.js | 13 + .../db/migrations/044_bio_user_pathway.sql | 16 + .../src/db/migrations/045_bio_pathways.sql | 15 + backend/src/routes/biochem.js | 3 + backend/src/sse.js | 12 +- frontend/analytics.html | 3 +- frontend/biochem-pathways.html | 288 +-- frontend/biochem.html | 2 + frontend/classroom.html | 3 +- frontend/collection-rb.html | 3 +- frontend/collection.html | 3 +- frontend/course.html | 3 +- frontend/crossword.html | 3 +- frontend/gradebook.html | 3 +- frontend/lesson.html | 3 +- frontend/live-quiz.html | 3 +- frontend/mocks-redesign.html | 2064 +++++++++++++++++ frontend/my-students.html | 4 +- frontend/pet.html | 3 +- frontend/profile.html | 2 +- frontend/question-bank.html | 3 +- frontend/red-book-biomes.html | 2 +- frontend/red-book-ecosystem.html | 2 +- frontend/red-book-games.html | 2 +- frontend/red-book.html | 3 +- js/api.js | 5 + plans/textbooks-10/PLAN_PHYSICS_10.md | 424 ++++ plans/textbooks-11/PLAN_ALGEBRA_11.md | 251 ++ plans/textbooks-11/PLAN_GEOMETRY_11.md | 349 +++ plans/textbooks-9/PLAN_CHEMISTRY_9.md | 363 +++ 55 files changed, 10203 insertions(+), 305 deletions(-) create mode 100644 backend/scripts/audit_chem8.js create mode 100644 backend/scripts/biochem_pathways_data.js create mode 100644 backend/scripts/gen_phys10_ch.js create mode 100644 backend/scripts/gen_phys10_hub.js create mode 100644 backend/scripts/patch_html.py create mode 100644 backend/scripts/patch_interfsim.js create mode 100644 backend/scripts/patch_interfsim2.js create mode 100644 backend/scripts/patch_interfsim3.js create mode 100644 backend/scripts/patch_interfsim_html.js create mode 100644 backend/scripts/patch_optics.py create mode 100644 backend/scripts/patch_optics2.py create mode 100644 backend/scripts/patch_optics3.py create mode 100644 backend/scripts/review_geom10.js create mode 100644 backend/scripts/review_geom10_2.js create mode 100644 backend/scripts/review_geom10_3.js create mode 100644 backend/scripts/review_geom11.js create mode 100644 backend/scripts/seed_biochem_pathways.js create mode 100644 backend/scripts/seed_math.js create mode 100644 backend/scripts/seed_math2.js create mode 100644 backend/scripts/seed_phys.js create mode 100644 backend/scripts/seed_phys2.js create mode 100644 backend/src/db/migrations/044_bio_user_pathway.sql create mode 100644 backend/src/db/migrations/045_bio_pathways.sql create mode 100644 frontend/mocks-redesign.html create mode 100644 plans/textbooks-10/PLAN_PHYSICS_10.md create mode 100644 plans/textbooks-11/PLAN_ALGEBRA_11.md create mode 100644 plans/textbooks-11/PLAN_GEOMETRY_11.md create mode 100644 plans/textbooks-9/PLAN_CHEMISTRY_9.md diff --git a/.claude/settings.json b/.claude/settings.json index 2d45084..c151850 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -178,7 +178,21 @@ "Bash(cmd /c \"taskkill /PID 60564 /F\")", "Bash(cmd /c \"taskkill /F /PID 60564 2>&1\")", "Bash(kill -9 60564)", - "Bash(kill -9 9313)" + "Bash(kill -9 9313)", + "Read(//f/!Рабочие/ЦТ/Математика/**)", + "Read(//f/!Рабочие/ЦТ/Физика/**)", + "Read(//f/!����稥/��/��⥬�⨪�/**)", + "PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Математика\\\\Математика\\\\ЦТ-ЦЭ\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)", + "Read(//f/!����稥/��/**)", + "PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦЭ,ЦТ-20260116T125835Z-3-001\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)", + "PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦТ-20260116T130104Z-3-001\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)", + "PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦЭ,ЦТ-20260116T125835Z-3-001\\\\Сборники ЦЭ,ЦТ\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)", + "PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦТ-20260116T130104Z-3-001\\\\Сборники ЦТ\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)", + "Bash(git commit -m ' *)", + "Bash(git push *)", + "Bash(curl -s http://localhost:3000/api/subjects)", + "PowerShell(\\(Get-Content \"g:\\\\Dev\\\\Тесты\\\\BQ-System\\\\frontend\\\\question-bank.html\"\\).Count)", + "Bash(curl -s \"http://localhost:3000/api/subjects/math/topics\" -H \"Authorization: Bearer test\")" ], "additionalDirectories": [ "\\tmp" diff --git a/backend/scripts/audit_chem8.js b/backend/scripts/audit_chem8.js new file mode 100644 index 0000000..8b40bff --- /dev/null +++ b/backend/scripts/audit_chem8.js @@ -0,0 +1,110 @@ +/* audit_chem8.js — аудит KaTeX и оформления учебника «Химия 8». + * Загружает каждую страницу в jsdom (renderMathInElement застаблен → $…$ остаются + * литералами с уже раскрытыми JS-эскейпами), строит все §, извлекает формулы и + * проверяет: баланс $, баланс {}, отсутствие управляющих символов (следы \t/\n), + * пустые формулы, «сырые» $…$ вне рендера. Запуск: node backend/scripts/audit_chem8.js + */ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const { JSDOM, VirtualConsole } = require('jsdom'); + +const ROOT = path.join(__dirname, '..', '..'); +const readF = p => fs.readFileSync(path.join(ROOT, p), 'utf8'); +const wait = ms => new Promise(r => setTimeout(r, ms)); + +const PAGES = [ + ['chemistry_8_intro.html', 'chem8_intro_widgets'], + ['chemistry_8_ch1.html', 'chem8_ch1_widgets'], + ['chemistry_8_ch2.html', 'chem8_ch2_widgets'], + ['chemistry_8_ch3.html', 'chem8_ch3_widgets'], + ['chemistry_8_ch4.html', 'chem8_ch4_widgets'], + ['chemistry_8_ch5.html', 'chem8_ch5_widgets'], + ['chemistry_8_ch6.html', 'chem8_ch6_widgets'] +]; + +function buildPage(file, widgets) { + let html = readF('frontend/textbooks/' + file); + const inl = { + '/js/biochem-core.js': readF('frontend/js/biochem-core.js'), + '/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'), + '/js/chem8_mol.js': readF('frontend/js/chem8_mol.js'), + ['/js/' + widgets + '.js']: readF('frontend/js/' + widgets + '.js'), + '/js/chem8_engine.js': readF('frontend/js/chem8_engine.js') + }; + html = html + .replace(/') + .replace(/'); + }); + return html; +} + +function extractMath(s) { + const out = []; + // $$...$$ затем $...$ + let re = /\$\$([\s\S]+?)\$\$/g, m; + let masked = s; + while ((m = re.exec(s)) !== null) out.push({ disp: true, body: m[1] }); + masked = s.replace(/\$\$[\s\S]+?\$\$/g, ''); + re = /\$([^$]*)\$/g; + while ((m = re.exec(masked)) !== null) out.push({ disp: false, body: m[1] }); + return out; +} + +function checkBraces(b) { let d = 0; for (const c of b) { if (c === '{') d++; else if (c === '}') d--; if (d < 0) return false; } return d === 0; } +function hasCtrl(b) { return /[\t\n\r\f\v\b]/.test(b); } + +async function auditPage(file, widgets) { + const issues = []; + const vc = new VirtualConsole(); const errs = []; + vc.on('jsdomError', e => errs.push(e.message)); + const dom = new JSDOM(buildPage(file, widgets), { + runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/', + beforeParse(w) { w.scrollTo = function () {}; } + }); + await wait(120); + const doc = dom.window.document; + const paras = (dom.window.PARAS || []).map(p => p.id); + for (const id of paras) { try { dom.window.goTo(id); } catch (e) {} } + await wait(120); + + if (errs.length) issues.push('script errors: ' + errs.join(' | ')); + + // собрать все § тела + sidebar + let html = ''; + doc.querySelectorAll('[id$="-body"]').forEach(el => { html += el.innerHTML + '\n'; }); + const sidebar = doc.getElementById('sidebar-content'); if (sidebar) html += sidebar.innerHTML; + + // баланс $ (нечётное число одиночных $ вне $$) + const noDisp = html.replace(/\$\$[\s\S]+?\$\$/g, ''); + const singles = (noDisp.match(/\$/g) || []).length; + if (singles % 2 !== 0) issues.push('нечётное число одиночных $ (' + singles + ')'); + + const maths = extractMath(html); + let bad = 0; + for (const m of maths) { + const b = m.body; + if (!b.trim()) { issues.push('пустая формула $' + (m.disp ? '$' : '') + '$'); bad++; continue; } + if (!checkBraces(b)) { issues.push('несбалансированные {} в: ' + b.slice(0, 50)); bad++; } + if (hasCtrl(b)) { issues.push('управляющий символ (след \\t/\\n?) в: ' + JSON.stringify(b.slice(0, 50))); bad++; } + // одиночный backslash перед буквой, не часть известной команды? — грубая эвристика: \ в конце + if (/\\$/.test(b)) { issues.push('формула заканчивается на \\: ' + b.slice(-20)); bad++; } + } + return { file, mathCount: maths.length, badCount: bad, issues }; +} + +(async () => { + let total = 0, totalBad = 0; + for (const [file, w] of PAGES) { + const r = await auditPage(file, w); + total += r.mathCount; totalBad += r.badCount; + console.log('\n=== ' + file + ' — формул: ' + r.mathCount + ', проблем: ' + r.issues.length + ' ==='); + if (r.issues.length) r.issues.slice(0, 25).forEach(i => console.log(' ! ' + i)); + else console.log(' OK'); + } + console.log('\nИТОГО формул: ' + total + ', проблемных: ' + totalBad); + process.exit(0); +})(); diff --git a/backend/scripts/biochem_pathways_data.js b/backend/scripts/biochem_pathways_data.js new file mode 100644 index 0000000..576b8c5 --- /dev/null +++ b/backend/scripts/biochem_pathways_data.js @@ -0,0 +1,1290 @@ +'use strict'; +// Данные метаболических путей (источник для seed_biochem_pathways.js). +// Извлечены из инлайн-объекта PATHWAYS biochem-pathways.html — теперь самодостаточный источник. +module.exports = { + "glycolysis": { + "name": "Гликолиз", + "color": "#f59e0b", + "colorRgb": "245,158,11", + "desc": "10 реакций расщепления глюкозы до пирувата. Происходит в цитоплазме. Выход: 2 АТФ (нетто), 2 НАДН, 2 пируват.", + "stats": [ + { + "label": "−2 АТФ +4 АТФ", + "cls": "atp" + }, + { + "label": "2 НАДН", + "cls": "nadh" + } + ], + "legend": [ + { + "color": "#f59e0b", + "type": "circle", + "label": "Метаболит" + }, + { + "color": "#f59e0b", + "type": "line", + "label": "Реакция" + }, + { + "color": "#f59e0b88", + "type": "circle-sm", + "label": "Кофактор (АТФ/НАД)" + } + ], + "nodes": [ + { + "id": "glc", + "label": "Глюкоза", + "formula": "C₆H₁₂O₆", + "x": 400, + "y": 60, + "role": "substrate", + "desc": "Исходный субстрат гликолиза. 6-углеродный сахар, главный источник энергии клетки.", + "props": [] + }, + { + "id": "g6p", + "label": "Глюкозо-6-Ф", + "formula": "C₆H₁₃O₉P", + "x": 400, + "y": 145, + "role": "inter", + "desc": "Глюкозо-6-фосфат. Образуется при фосфорилировании глюкозы за счёт АТФ. Удерживает молекулу в клетке.", + "props": [ + "−1 АТФ" + ] + }, + { + "id": "f6p", + "label": "Фруктозо-6-Ф", + "formula": "C₆H₁₃O₉P", + "x": 400, + "y": 225, + "role": "inter", + "desc": "Изомер глюкозо-6-фосфата. Образуется при изомеризации ферментом фосфоглюкоизомеразой.", + "props": [] + }, + { + "id": "f16bp", + "label": "Фруктозо-1,6-бФ", + "formula": "C₆H₁₄O₁₂P₂", + "x": 400, + "y": 310, + "role": "key", + "desc": "Фруктозо-1,6-бисфосфат — ключевой регуляторный метаболит. Образование катализирует фосфофруктокиназа-1 (ФФК-1).", + "props": [ + "−1 АТФ", + "Контроль скорости" + ] + }, + { + "id": "dhap", + "label": "ДГАФ", + "formula": "C₃H₇O₆P", + "x": 260, + "y": 395, + "role": "inter", + "desc": "Дигидроксиацетонфосфат — один из двух триозофосфатов при расщеплении фруктозо-1,6-бисфосфата. Быстро конвертируется в ГАФ.", + "props": [] + }, + { + "id": "gap", + "label": "ГАФ", + "formula": "C₃H₇O₆P", + "x": 540, + "y": 395, + "role": "inter", + "desc": "Глицеральдегид-3-фосфат (ГАФ) — непосредственный субстрат следующих реакций. Оба триозофосфата канализируются через ГАФ.", + "props": [] + }, + { + "id": "bpg", + "label": "1,3-бФГ", + "formula": "C₃H₈O₁₀P₂", + "x": 540, + "y": 480, + "role": "inter", + "desc": "1,3-бисфосфоглицерат. Образуется при окислении ГАФ, сопряжённом с восстановлением НАД⁺ в НАДН.", + "props": [ + "2 НАДН" + ] + }, + { + "id": "pg3", + "label": "3-ФГК", + "formula": "C₃H₇O₇P", + "x": 540, + "y": 560, + "role": "inter", + "desc": "3-фосфоглицерат. Образуется при субстратном фосфорилировании АДФ АТФ ферментом фосфоглицераткиназой.", + "props": [ + "+2 АТФ" + ] + }, + { + "id": "pg2", + "label": "2-ФГК", + "formula": "C₃H₇O₇P", + "x": 540, + "y": 635, + "role": "inter", + "desc": "2-фосфоглицерат. Образуется при перемещении фосфатной группы с 3 на 2 положение.", + "props": [] + }, + { + "id": "pep", + "label": "ФЕП", + "formula": "C₃H₅O₆P", + "x": 540, + "y": 710, + "role": "inter", + "desc": "Фосфоенолпируват (ФЕП) — высокоэнергетический промежуточный продукт. Образуется при дегидратации 2-ФГК.", + "props": [] + }, + { + "id": "pyr", + "label": "Пируват", + "formula": "C₃H₄O₃", + "x": 400, + "y": 795, + "role": "product", + "desc": "Конечный продукт гликолиза. В аэробных условиях переходит в ацетил-КоА (цикл Кребса). В анаэробных лактат или этанол.", + "props": [ + "+2 АТФ", + "2 молекулы" + ] + } + ], + "edges": [ + { + "from": "glc", + "to": "g6p", + "enzyme": "Гексокиназа", + "co": "-АТФ", + "curveX": 0 + }, + { + "from": "g6p", + "to": "f6p", + "enzyme": "ФГИ", + "curveX": 0 + }, + { + "from": "f6p", + "to": "f16bp", + "enzyme": "ФФК-1", + "co": "-АТФ", + "curveX": 0 + }, + { + "from": "f16bp", + "to": "dhap", + "enzyme": "Альдолаза", + "curveX": 0 + }, + { + "from": "f16bp", + "to": "gap", + "enzyme": "Альдолаза", + "curveX": 0 + }, + { + "from": "dhap", + "to": "gap", + "enzyme": "ТФИ", + "curveX": 0 + }, + { + "from": "gap", + "to": "bpg", + "enzyme": "ГАФДГ", + "co": "+НАДН", + "curveX": 0 + }, + { + "from": "bpg", + "to": "pg3", + "enzyme": "ФГК", + "co": "+АТФ", + "curveX": 0 + }, + { + "from": "pg3", + "to": "pg2", + "enzyme": "Фосфоглицератмутаза", + "curveX": 0 + }, + { + "from": "pg2", + "to": "pep", + "enzyme": "Енолаза", + "curveX": 0 + }, + { + "from": "pep", + "to": "pyr", + "enzyme": "Пируваткиназа", + "co": "+АТФ", + "curveX": 0 + } + ], + "steps": [ + { + "title": "Фосфорилирование глюкозы", + "mol": "g6p", + "desc": "Гексокиназа катализирует перенос фосфатной группы с АТФ на глюкозу, образуя глюкозо-6-фосфат (Г6Ф). Реакция необратима и «ловит» глюкозу в клетке.", + "energy": [ + { + "label": "-1 АТФ", + "cls": "atp-used" + } + ], + "quiz": { + "q": "Зачем глюкозу фосфорилируют в первой реакции?", + "opts": [ + "Для выхода из клетки", + "Чтобы удержать глюкозу в клетке", + "Для образования НАДН", + "Для расщепления кольца" + ], + "ans": 1 + } + }, + { + "title": "Изомеризация", + "mol": "f6p", + "desc": "Фосфоглюкоизомераза превращает Г6Ф в фруктозо-6-фосфат (Ф6Ф). Реакция обратима и перестраивает альдозный сахар в кетозный.", + "energy": [], + "quiz": { + "q": "Какой фермент катализирует изомеризацию Г6Ф Ф6Ф?", + "opts": [ + "Гексокиназа", + "Альдолаза", + "Фосфоглюкоизомераза", + "Пируваткиназа" + ], + "ans": 2 + } + }, + { + "title": "Ключевой контрольный шаг", + "mol": "f16bp", + "desc": "Фосфофруктокиназа-1 (ФФК-1) фосфорилирует Ф6Ф фруктозо-1,6-бисфосфат. Это необратимая реакция — главный регуляторный пункт гликолиза. АТФ ингибирует, АМФ/АДФ активирует.", + "energy": [ + { + "label": "-1 АТФ", + "cls": "atp-used" + } + ], + "quiz": { + "q": "Что является главным аллостерическим активатором ФФК-1?", + "opts": [ + "АТФ", + "АМФ", + "НАДН", + "Пируват" + ], + "ans": 1 + } + }, + { + "title": "Расщепление на триозы", + "mol": "gap", + "desc": "Альдолаза расщепляет фруктозо-1,6-бисфосфат на два триозофосфата: ДГАФ и ГАФ (глицеральдегид-3-фосфат). Триозофосфатизомераза быстро конвертирует ДГАФ ГАФ.", + "energy": [], + "quiz": { + "q": "Сколько молекул ГАФ образуется из одной глюкозы?", + "opts": [ + "1", + "2", + "3", + "4" + ], + "ans": 1 + } + }, + { + "title": "Окислительное фосфорилирование", + "mol": "bpg", + "desc": "ГАФДГ окисляет ГАФ и присоединяет неорганический фосфат 1,3-бисфосфоглицерат. Сопряжено с восстановлением НАД⁺ НАДН. Реакция субстратного фосфорилирования.", + "energy": [ + { + "label": "+2 НАДН", + "cls": "nadh" + } + ], + "quiz": { + "q": "Чем восстанавливается НАД⁺ в этой реакции?", + "opts": [ + "ГАФ", + "Пируват", + "ДГАФ", + "АТФ" + ], + "ans": 0 + } + }, + { + "title": "Первая выработка АТФ", + "mol": "pg3", + "desc": "Фосфоглицераткиназа переносит фосфат с 1,3-бФГ на АДФ АТФ. Это субстратное фосфорилирование — первый синтез АТФ в гликолизе. С каждой глюкозы получаем 2 АТФ.", + "energy": [ + { + "label": "+2 АТФ", + "cls": "atp-prod" + } + ], + "quiz": { + "q": "Как называется тип синтеза АТФ в этой реакции?", + "opts": [ + "Окислительное фосфорилирование", + "Субстратное фосфорилирование", + "Фотофосфорилирование", + "Трансфосфорилирование" + ], + "ans": 1 + } + }, + { + "title": "Мутация фосфатной группы", + "mol": "pg2", + "desc": "Фосфоглицератмутаза перемещает фосфатную группу с 3-го на 2-е углеродное положение, подготавливая молекулу к дегидратации.", + "energy": [], + "quiz": { + "q": "Какой продукт образуется из 3-ФГК под действием мутазы?", + "opts": [ + "ФЕП", + "2-ФГК", + "Пируват", + "1,3-бФГ" + ], + "ans": 1 + } + }, + { + "title": "Образование ФЕП", + "mol": "pep", + "desc": "Енолаза катализирует дегидратацию 2-фосфоглицерата фосфоенолпируват (ФЕП). ФЕП — высокоэнергетический соединение с большой отрицательной ΔG° гидролиза фосфата.", + "energy": [], + "quiz": { + "q": "Почему ФЕП называют «высокоэнергетическим»?", + "opts": [ + "Содержит много атомов С", + "Большая ΔG° гидролиза фосфатной связи", + "Растворяется в жирах", + "Содержит двойную связь" + ], + "ans": 1 + } + }, + { + "title": "Финальная реакция — пируват", + "mol": "pyr", + "desc": "Пируваткиназа переносит фосфат с ФЕП на АДФ АТФ + пируват. Необратимая реакция. Итог: из 1 глюкозы 2 пирувата, 2 НАДН, +2 АТФ нетто.", + "energy": [ + { + "label": "+2 АТФ", + "cls": "atp-prod" + }, + { + "label": "2 пируват", + "cls": "co2" + } + ], + "quiz": { + "q": "Каков нетто-выход АТФ на 1 молекулу глюкозы в гликолизе?", + "opts": [ + "1", + "2", + "4", + "36" + ], + "ans": 1 + } + } + ] + }, + "krebs": { + "name": "Цикл Кребса", + "color": "#06b6d4", + "colorRgb": "6,182,212", + "desc": "8 реакций окисления ацетил-КоА. Происходит в матриксе митохондрий. Выход на 1 оборот: 3 НАДН, 1 ФАДН₂, 1 ГТФ, 2 СО₂.", + "stats": [ + { + "label": "3 НАДН / оборот", + "cls": "nadh" + }, + { + "label": "2 CO₂", + "cls": "co2" + }, + { + "label": "1 ГТФ", + "cls": "atp" + } + ], + "legend": [ + { + "color": "#06b6d4", + "type": "circle", + "label": "Промежуточный метаболит" + }, + { + "color": "#06b6d4", + "type": "line", + "label": "Реакция цикла" + } + ], + "nodes": [ + { + "id": "acetcoa", + "label": "Ацетил-КоА", + "formula": "CH₃CO-SCoA", + "x": 440, + "y": 80, + "role": "substrate", + "desc": "Активированный ацетат. Образуется из пирувата (гликолиз), жирных кислот (β-окисление) и аминокислот.", + "props": [ + "Входит в цикл" + ] + }, + { + "id": "oaa", + "label": "ОАА", + "formula": "C₄H₄O₅", + "x": 260, + "y": 140, + "role": "key", + "desc": "Оксалоацетат — акцептор ацетил-КоА. Регенерируется в каждом обороте цикла. Ключевой анаплеротический метаболит.", + "props": [ + "Акцептор" + ] + }, + { + "id": "cit", + "label": "Цитрат", + "formula": "C₆H₈O₇", + "x": 160, + "y": 260, + "role": "inter", + "desc": "Цитрат — первый продукт цикла. Синтезируется цитратсинтазой из ацетил-КоА и ОАА.", + "props": [] + }, + { + "id": "isocit", + "label": "Изоцитрат", + "formula": "C₆H₈O₇", + "x": 120, + "y": 390, + "role": "inter", + "desc": "Изоцитрат — изомер цитрата. Субстрат изоцитратдегидрогеназы — ключевого регуляторного фермента.", + "props": [] + }, + { + "id": "akg", + "label": "α-КГ", + "formula": "C₅H₆O₅", + "x": 160, + "y": 520, + "role": "inter", + "desc": "α-кетоглутарат (α-КГ). Образуется при окислительном декарбоксилировании изоцитрата. Выделяется CO₂.", + "props": [ + "−CO₂", + "+НАДН" + ] + }, + { + "id": "succoa", + "label": "Сукцинил-КоА", + "formula": "C₅H₆O₃S", + "x": 300, + "y": 620, + "role": "inter", + "desc": "Сукцинил-КоА — высокоэнергетический тиоэфир. Образуется при окислительном декарбоксилировании α-КГ.", + "props": [ + "+НАДН", + "+ГТФ", + "-CO₂" + ] + }, + { + "id": "succ", + "label": "Сукцинат", + "formula": "C₄H₆O₄", + "x": 480, + "y": 620, + "role": "inter", + "desc": "Сукцинат. Окисляется сукцинатдегидрогеназой (СДГ) — единственным мембранным ферментом цикла.", + "props": [ + "+ФАДН₂" + ] + }, + { + "id": "fum", + "label": "Фумарат", + "formula": "C₄H₄O₄", + "x": 620, + "y": 520, + "role": "inter", + "desc": "Фумарат — транс-изомер. Образуется при окислении сукцината. Гидратируется фумаразой.", + "props": [] + }, + { + "id": "mal", + "label": "Малат", + "formula": "C₄H₆O₅", + "x": 660, + "y": 390, + "role": "inter", + "desc": "Малат (яблочная кислота). Образуется при гидратации фумарата. Окисляется малатдегидрогеназой.", + "props": [ + "+НАДН" + ] + } + ], + "edges": [ + { + "from": "acetcoa", + "to": "cit", + "enzyme": "Цитратсинтаза", + "co": "+ОАА", + "curveX": 0 + }, + { + "from": "oaa", + "to": "cit", + "enzyme": "", + "curveX": 0 + }, + { + "from": "cit", + "to": "isocit", + "enzyme": "Аконитаза", + "curveX": 0 + }, + { + "from": "isocit", + "to": "akg", + "enzyme": "ИзоцитратДГ", + "co": "+НАДН,-CO₂", + "curveX": 0 + }, + { + "from": "akg", + "to": "succoa", + "enzyme": "α-КГДГ-комплекс", + "co": "+НАДН,-CO₂", + "curveX": 0 + }, + { + "from": "succoa", + "to": "succ", + "enzyme": "Сукцинил-КоА-синтетаза", + "co": "+ГТФ", + "curveX": 0 + }, + { + "from": "succ", + "to": "fum", + "enzyme": "Сукцинатдегидрогеназа", + "co": "+ФАДН₂", + "curveX": 0 + }, + { + "from": "fum", + "to": "mal", + "enzyme": "Фумараза", + "curveX": 0 + }, + { + "from": "mal", + "to": "oaa", + "enzyme": "МалатДГ", + "co": "+НАДН", + "curveX": 0 + } + ], + "steps": [ + { + "title": "Конденсация с ОАА", + "mol": "cit", + "desc": "Цитратсинтаза присоединяет ацетил-КоА (2C) к оксалоацетату (4C) цитрат (6C). Это необратимая реакция, запускающая цикл.", + "energy": [], + "quiz": { + "q": "Сколько углеродов в цитрате?", + "opts": [ + "2", + "4", + "6", + "8" + ], + "ans": 2 + } + }, + { + "title": "Изомеризация цитрата", + "mol": "isocit", + "desc": "Аконитаза через промежуточный цис-аконитат превращает цитрат в изоцитрат. Реакция обратима, равновесие сдвинуто в сторону цитрата.", + "energy": [], + "quiz": { + "q": "Какой фермент изомеризует цитрат?", + "opts": [ + "Фумараза", + "Аконитаза", + "Малатдегидрогеназа", + "Цитратсинтаза" + ], + "ans": 1 + } + }, + { + "title": "Первое окислительное декарбоксилирование", + "mol": "akg", + "desc": "Изоцитратдегидрогеназа окисляет изоцитрат α-кетоглутарат с выделением CO₂ и НАДН. Ключевой регуляторный шаг — активируется изоцитратом, ингибируется НАДН.", + "energy": [ + { + "label": "+НАДН", + "cls": "nadh" + }, + { + "label": "-CO₂", + "cls": "co2" + } + ], + "quiz": { + "q": "Сколько углеродов в α-кетоглутарате?", + "opts": [ + "2", + "4", + "5", + "6" + ], + "ans": 2 + } + }, + { + "title": "Второе окислительное декарбоксилирование", + "mol": "succoa", + "desc": "α-кетоглутаратдегидрогеназный комплекс (аналог ПДК) окисляет α-КГ сукцинил-КоА. Выделяется ещё одна CO₂ и НАДН.", + "energy": [ + { + "label": "+НАДН", + "cls": "nadh" + }, + { + "label": "-CO₂", + "cls": "co2" + } + ], + "quiz": { + "q": "Чем структурно похож α-КГДК на пируватдегидрогеназный комплекс?", + "opts": [ + "Использует ФАДН₂", + "Механизм окислительного декарбоксилирования с КоА", + "Находится в цитоплазме", + "Требует витамин К" + ], + "ans": 1 + } + }, + { + "title": "Субстратное фосфорилирование", + "mol": "succ", + "desc": "Сукцинил-КоА-синтетаза расщепляет тиоэфирную связь сукцинил-КоА, сопрягая это с синтезом ГТФ (или АТФ). Единственная реакция субстратного фосфорилирования в цикле.", + "energy": [ + { + "label": "+ГТФ", + "cls": "atp-prod" + } + ], + "quiz": { + "q": "Что синтезируется при реакции сукцинил-КоА-синтетазы?", + "opts": [ + "НАДН", + "ФАДН₂", + "ГТФ", + "CO₂" + ], + "ans": 2 + } + }, + { + "title": "Окисление сукцината", + "mol": "fum", + "desc": "Сукцинатдегидрогеназа (комплекс II дыхательной цепи) окисляет сукцинат фумарат, восстанавливая ФАД ФАДН₂.", + "energy": [ + { + "label": "+ФАДН₂", + "cls": "fadh2" + } + ], + "quiz": { + "q": "К какому комплексу дыхательной цепи относится СДГ?", + "opts": [ + "Комплекс I", + "Комплекс II", + "Комплекс III", + "АТФ-синтаза" + ], + "ans": 1 + } + }, + { + "title": "Гидратация фумарата", + "mol": "mal", + "desc": "Фумараза присоединяет воду к фумарату L-малат. Реакция стереоспецифична — образуется только L-изомер.", + "energy": [], + "quiz": { + "q": "Что присоединяется к фумарату в этой реакции?", + "opts": [ + "CO₂", + "АТФ", + "H₂O", + "НАДН" + ], + "ans": 2 + } + }, + { + "title": "Регенерация ОАА", + "mol": "oaa", + "desc": "Малатдегидрогеназа окисляет малат оксалоацетат с образованием НАДН. Регенерируется акцептор для следующего оборота цикла.", + "energy": [ + { + "label": "+НАДН", + "cls": "nadh" + } + ], + "quiz": { + "q": "Сколько оборотов цикла Кребса нужно на 1 молекулу глюкозы?", + "opts": [ + "1", + "2", + "4", + "10" + ], + "ans": 1 + } + } + ] + }, + "oxidation": { + "name": "β-Окисление", + "color": "#fb923c", + "colorRgb": "251,146,60", + "desc": "Повторяющиеся циклы окисления жирных кислот в митохондриях. Каждый цикл отщепляет 2C в виде ацетил-КоА и выделяет 1 НАДН + 1 ФАДН₂.", + "stats": [ + { + "label": "+НАДН / цикл", + "cls": "nadh" + }, + { + "label": "+ФАДН₂", + "cls": "nadh" + }, + { + "label": "+Ацетил-КоА", + "cls": "atp" + } + ], + "legend": [ + { + "color": "#fb923c", + "type": "circle", + "label": "Промежуточный продукт" + }, + { + "color": "#fb923c", + "type": "line", + "label": "Реакция β-окисления" + } + ], + "nodes": [ + { + "id": "fac", + "label": "Жирная к-та", + "formula": "R-COOH", + "x": 400, + "y": 60, + "role": "substrate", + "desc": "Свободная жирная кислота (напр. пальмитиновая C₁₆). Активируется в ацил-КоА перед входом в митохондрии.", + "props": [] + }, + { + "id": "acylcoa", + "label": "Ацил-КоА", + "formula": "R-CO-SCoA", + "x": 400, + "y": 150, + "role": "key", + "desc": "Активированная жирная кислота. Образуется при участии ацил-КоА-синтетазы за счёт АТФ (АМФ+PPi). Не проходит через мембрану — транспортируется как карнитиновый эфир.", + "props": [ + "-АТФ (АМФ)" + ] + }, + { + "id": "enoylcoa", + "label": "Транс-еноил-КоА", + "formula": "R-CH=CH-CO-SCoA", + "x": 400, + "y": 250, + "role": "inter", + "desc": "Транс-Δ²-еноил-КоА. Образуется при ФАД-зависимом окислении ацил-КоА ацил-КоА-дегидрогеназой.", + "props": [ + "+ФАДН₂" + ] + }, + { + "id": "hydroxy", + "label": "L-β-гидрокси-КоА", + "formula": "R-CHOH-CH₂-CO-SCoA", + "x": 400, + "y": 345, + "role": "inter", + "desc": "L-β-гидроксиацил-КоА. Образуется при гидратации двойной связи еноил-КоА гидратазой.", + "props": [] + }, + { + "id": "ketoacoa", + "label": "β-кето-КоА", + "formula": "R-CO-CH₂-CO-SCoA", + "x": 400, + "y": 440, + "role": "inter", + "desc": "β-кетоацил-КоА. Образуется при НАД⁺-зависимом окислении L-β-гидроксиацил-КоА.", + "props": [ + "+НАДН" + ] + }, + { + "id": "newacyl", + "label": "Ацил-КоА (−2C)", + "formula": "R'—CO-SCoA", + "x": 240, + "y": 540, + "role": "inter", + "desc": "Укороченный на 2 углерода ацил-КоА. Возвращается на начало цикла β-окисления.", + "props": [ + "Следующий цикл" + ] + }, + { + "id": "acetcoa2", + "label": "Ацетил-КоА", + "formula": "CH₃CO-SCoA", + "x": 560, + "y": 540, + "role": "product", + "desc": "Ацетил-КоА — входит в цикл Кребса. Из пальмитиновой кислоты (C₁₆) образуется 8 ацетил-КоА за 7 циклов β-окисления.", + "props": [ + " Цикл Кребса" + ] + } + ], + "edges": [ + { + "from": "fac", + "to": "acylcoa", + "enzyme": "Ацил-КоА-синтетаза", + "co": "-АТФ", + "curveX": 0 + }, + { + "from": "acylcoa", + "to": "enoylcoa", + "enzyme": "Ацил-КоА-ДГ", + "co": "+ФАДН₂", + "curveX": 0 + }, + { + "from": "enoylcoa", + "to": "hydroxy", + "enzyme": "Еноил-КоА-гидратаза", + "curveX": 0 + }, + { + "from": "hydroxy", + "to": "ketoacoa", + "enzyme": "L-3-гидроксиацил-КоА-ДГ", + "co": "+НАДН", + "curveX": 0 + }, + { + "from": "ketoacoa", + "to": "newacyl", + "enzyme": "Тиолаза", + "curveX": 0 + }, + { + "from": "ketoacoa", + "to": "acetcoa2", + "enzyme": "Тиолаза", + "curveX": 0 + }, + { + "from": "newacyl", + "to": "acylcoa", + "enzyme": "Повтор цикла", + "curveX": -60 + } + ], + "steps": [ + { + "title": "Активация жирной кислоты", + "mol": "acylcoa", + "desc": "Ацил-КоА-синтетаза присоединяет КоА к жирной кислоте, образуя ацил-КоА. Расходуется АТФ (АМФ+PPi, что эквивалентно 2 АТФ). Это происходит в цитоплазме.", + "energy": [ + { + "label": "-2 АТФ", + "cls": "atp-used" + } + ], + "quiz": { + "q": "Где происходит активация жирной кислоты в ацил-КоА?", + "opts": [ + "В митохондриях", + "В ядре", + "В цитоплазме", + "В рибосомах" + ], + "ans": 2 + } + }, + { + "title": "ФАД-зависимое окисление", + "mol": "enoylcoa", + "desc": "Ацил-КоА-дегидрогеназа окисляет ацил-КоА, вводя двойную связь между α и β углеродами транс-Δ²-еноил-КоА. ФАД восстанавливается до ФАДН₂.", + "energy": [ + { + "label": "+ФАДН₂", + "cls": "fadh2" + } + ], + "quiz": { + "q": "Какой кофактор восстанавливается в первой реакции β-окисления?", + "opts": [ + "НАД⁺", + "ФАД", + "ГТФ", + "КоА" + ], + "ans": 1 + } + }, + { + "title": "Гидратация двойной связи", + "mol": "hydroxy", + "desc": "Еноил-КоА-гидратаза присоединяет воду по двойной связи L-β-гидроксиацил-КоА. Реакция стереоспецифична.", + "energy": [], + "quiz": { + "q": "Что присоединяется в реакции гидратации еноил-КоА?", + "opts": [ + "CO₂", + "O₂", + "H₂O", + "НАД⁺" + ], + "ans": 2 + } + }, + { + "title": "НАД⁺-зависимое окисление", + "mol": "ketoacoa", + "desc": "L-3-гидроксиацил-КоА-дегидрогеназа окисляет гидроксильную группу кетогруппу, восстанавливая НАД⁺ НАДН.", + "energy": [ + { + "label": "+НАДН", + "cls": "nadh" + } + ], + "quiz": { + "q": "Какая группа окисляется в этой реакции?", + "opts": [ + "Карбоксильная", + "Аминогруппа", + "Гидроксильная", + "Метильная" + ], + "ans": 2 + } + }, + { + "title": "Тиолитическое расщепление", + "mol": "acetcoa2", + "desc": "Тиолаза расщепляет β-кетоацил-КоА присоединением КоА ацетил-КоА (2C) + укороченный ацил-КоА. Цикл повторяется.", + "energy": [ + { + "label": "+Ацетил-КоА", + "cls": "atp-prod" + } + ], + "quiz": { + "q": "Сколько ацетил-КоА образуется из пальмитиновой кислоты (C16)?", + "opts": [ + "4", + "6", + "7", + "8" + ], + "ans": 3 + } + } + ] + }, + "synthesis": { + "name": "Синтез белка", + "color": "#a78bfa", + "colorRgb": "167,139,250", + "desc": "Трансляция — считывание мРНК рибосомой и полимеризация аминокислот в полипептидную цепь.", + "stats": [ + { + "label": "~2 ГТФ / аминокислота", + "cls": "atp" + }, + { + "label": "мРНК белок", + "cls": "nadh" + } + ], + "legend": [ + { + "color": "#a78bfa", + "type": "circle", + "label": "Участник трансляции" + }, + { + "color": "#a78bfa", + "type": "line", + "label": "Этап синтеза" + } + ], + "nodes": [ + { + "id": "mrna", + "label": "мРНК", + "formula": "5′-AUG…-3′", + "x": 400, + "y": 60, + "role": "substrate", + "desc": "Матричная РНК — несёт генетическую информацию от ДНК к рибосоме в виде кодонов (триплетов нуклеотидов).", + "props": [ + "Матрица" + ] + }, + { + "id": "ribosome", + "label": "Рибосома", + "formula": "60S+40S", + "x": 400, + "y": 160, + "role": "key", + "desc": "Эукариотическая рибосома (80S). Состоит из малой (40S) и большой (60S) субъединиц. Имеет 3 сайта: A (аминоацильный), P (пептидильный), E (выход).", + "props": [ + "A-P-E сайты" + ] + }, + { + "id": "trna", + "label": "аминоацил-тРНК", + "formula": "aa-tRNA", + "x": 230, + "y": 260, + "role": "inter", + "desc": "тРНК с присоединённой аминокислотой. Распознаёт кодон мРНК через антикодон. Доставляется в A-сайт в комплексе с EF-Tu·ГТФ.", + "props": [ + "-2 ГТФ" + ] + }, + { + "id": "peptide", + "label": "Растущая цепь", + "formula": "...aa-aa-aa", + "x": 560, + "y": 260, + "role": "inter", + "desc": "Нарастающая полипептидная цепь в P-сайте. Пептидилтрансфераза (23S rRNA) катализирует образование пептидной связи.", + "props": [ + "P-сайт" + ] + }, + { + "id": "peptbond", + "label": "Пептидная связь", + "formula": "—CO—NH—", + "x": 400, + "y": 360, + "role": "inter", + "desc": "Образование пептидной связи катализируется рибозимом (23S rRNA) — пептидилтрансферазой. Выделяется тРНК из P-сайта.", + "props": [] + }, + { + "id": "translo", + "label": "Транслокация", + "formula": "EF-G·ГТФ", + "x": 400, + "y": 460, + "role": "inter", + "desc": "Фактор EF-G (с ГТФ) сдвигает рибосому на 1 кодон (3 нт) в направлении 5′3′. Освобождается Е-сайт. Расходуется ГТФ.", + "props": [ + "-1 ГТФ" + ] + }, + { + "id": "protein", + "label": "Белок", + "formula": "[полипептид]", + "x": 400, + "y": 560, + "role": "product", + "desc": "Готовый полипептид. Освобождается при встрече со стоп-кодоном (UAA, UAG, UGA) при участии факторов высвобождения RF1/RF2.", + "props": [ + "Готовый продукт" + ] + } + ], + "edges": [ + { + "from": "mrna", + "to": "ribosome", + "enzyme": "Инициация (eIF)", + "curveX": 0 + }, + { + "from": "ribosome", + "to": "trna", + "enzyme": "Декодирование", + "curveX": 0 + }, + { + "from": "trna", + "to": "peptbond", + "enzyme": "Пептидилтрансфераза", + "curveX": 0 + }, + { + "from": "peptide", + "to": "peptbond", + "enzyme": "", + "curveX": 0 + }, + { + "from": "peptbond", + "to": "translo", + "enzyme": "EF-G·ГТФ", + "curveX": 0 + }, + { + "from": "translo", + "to": "protein", + "enzyme": "Терминация (RF)", + "curveX": 0 + }, + { + "from": "translo", + "to": "ribosome", + "enzyme": "Следующий кодон", + "curveX": -70 + } + ], + "steps": [ + { + "title": "Инициация", + "mol": "ribosome", + "desc": "Малая субъединица рибосомы распознаёт 5′-кэп мРНК при помощи факторов инициации (eIF4E/4G). Инициаторная Met-тРНК занимает P-сайт. Присоединяется большая субъединица.", + "energy": [ + { + "label": "-3 ГТФ", + "cls": "atp-used" + } + ], + "quiz": { + "q": "Какой сайт занимает инициаторная Met-тРНК?", + "opts": [ + "A-сайт", + "P-сайт", + "E-сайт", + "Все три" + ], + "ans": 1 + } + }, + { + "title": "Элонгация — доставка аа-тРНК", + "mol": "trna", + "desc": "EF-Tu·ГТФ доставляет аминоацил-тРНК в A-сайт. При правильном спаривании кодон–антикодон ГТФ гидролизуется, EF-Tu·ГДФ уходит.", + "energy": [ + { + "label": "-1 ГТФ", + "cls": "atp-used" + } + ], + "quiz": { + "q": "Какой фактор доставляет аа-тРНК в А-сайт?", + "opts": [ + "EF-G", + "EF-Tu", + "eIF2", + "RF1" + ], + "ans": 1 + } + }, + { + "title": "Пептидная связь", + "mol": "peptbond", + "desc": "Пептидилтрансфераза переносит пептидильную группу с P-сайта на аминогруппу в A-сайте, образуя пептидную связь. Энергия — из гидролиза аминоацильной связи тРНК.", + "energy": [], + "quiz": { + "q": "Что катализирует образование пептидной связи?", + "opts": [ + "Белковый фермент", + "23S rRNA (рибозим)", + "ДНК-полимераза", + "АТФ-синтаза" + ], + "ans": 1 + } + }, + { + "title": "Транслокация", + "mol": "translo", + "desc": "EF-G·ГТФ сдвигает рибосому на 3 нуклеотида по мРНК. Цепь с тРНК перемещается из A P, пустая тРНК из P E и уходит. Расходуется ГТФ.", + "energy": [ + { + "label": "-1 ГТФ", + "cls": "atp-used" + } + ], + "quiz": { + "q": "На сколько нуклеотидов сдвигается рибосома при транслокации?", + "opts": [ + "1", + "2", + "3", + "4" + ], + "ans": 2 + } + }, + { + "title": "Терминация и высвобождение", + "mol": "protein", + "desc": "Стоп-кодон (UAA/UAG/UGA) распознаётся факторами высвобождения RF1/RF2. Пептидилтрансфераза гидролизует связь пептид-тРНК белок освобождается. Рибосома диссоциирует.", + "energy": [], + "quiz": { + "q": "Сколько стоп-кодонов существует?", + "opts": [ + "1", + "2", + "3", + "4" + ], + "ans": 2 + } + } + ] + } +}; diff --git a/backend/scripts/gen_phys10_ch.js b/backend/scripts/gen_phys10_ch.js new file mode 100644 index 0000000..026feb6 --- /dev/null +++ b/backend/scripts/gen_phys10_ch.js @@ -0,0 +1,1128 @@ +// Генератор physics_10_ch{1..6}.html — Phase 0 skeleton со STUB-builder'ами. +// Берём алгебру 11 ch1 как базу, заменяем только инфраструктуру: +// - title, theme keys, slug, hero, sec-nav names, PARAS list +// - STUB-builder для каждого § (37) + final{1..6} +// - SIDEBARS / TIPS / ACH_LABELS +// CSS POLISH + ICONS + 2D-хелперы оставляем 1:1 (нужны Phase 1+). +'use strict'; +const fs = require('fs'); +const path = require('path'); + +const TBOOKS = path.join(__dirname, '..', '..', 'frontend', 'textbooks'); +const SRC = path.join(TBOOKS, 'algebra_11_ch1.html'); + +// === Данные глав === +const PARA_NAMES = { + p1:'Основные положения МКТ', + p2:'Масса и размеры молекул. Количество вещества', + p3:'Идеальный газ. Основное уравнение МКТ', + p4:'Температура. Тепловое равновесие', + p5:'Уравнение состояния идеального газа', + p6:'Изопроцессы', + p7:'Строение и свойства твёрдых тел', + p8:'Строение и свойства жидкостей', + p9:'Испарение и конденсация. Насыщенный пар', + p10:'Влажность воздуха', + p11:'Внутренняя энергия', + p12:'Работа в термодинамике', + p13:'Количество теплоты', + p14:'Первый закон термодинамики', + p15:'Тепловые двигатели. Цикл Карно', + p16:'Электрический заряд', + p17:'Закон Кулона', + p18:'Электростатическое поле', + p19:'Напряжённость поля. Принцип суперпозиции', + p20:'Линии напряжённости', + p21:'Работа поля. Потенциал', + p22:'Разность потенциалов. Напряжение', + p23:'Конденсаторы', + p24:'Энергия поля конденсатора', + p25:'ЭДС источника тока', + p26:'Закон Ома для полной цепи', + p27:'Магнитное поле тока', + p28:'Индукция магнитного поля', + p29:'Сила Ампера', + p30:'Сила Лоренца', + p31:'Магнитный поток. Электромагнитная индукция', + p32:'Правило Ленца. Закон Фарадея', + p33:'Самоиндукция', + p34:'Ток в металлах. Сверхпроводимость', + p35:'Ток в электролитах', + p36:'Ток в газах. Плазма', + p37:'Ток в полупроводниках', +}; + +// Sub-формулы (с двойным backslash для JS string literals) +const PARA_SUBS = { + p1:'Положения МКТ', + p2:'$N_A = 6{,}022 \\\\cdot 10^{23}$', + p3:'$p = \\\\dfrac{1}{3}nm\\\\overline{v^2}$', + p4:'$\\\\overline{E_k} = \\\\dfrac{3}{2}kT$', + p5:'$pV = \\\\nu RT$', + p6:'$pV/T = \\\\text{const}$', + p7:'Кристаллы', + p8:'Поверхностное натяжение', + p9:'Насыщенный пар', + p10:'$\\\\varphi = p/p_н$', + p11:'$U = \\\\dfrac{3}{2}\\\\nu RT$', + p12:'$A = p\\\\Delta V$', + p13:'$Q = cm\\\\Delta T$', + p14:'$Q = \\\\Delta U + A$', + p15:'$\\\\eta = (T_1-T_2)/T_1$', + p16:'$q = ne$', + p17:'$F = k\\\\dfrac{q_1 q_2}{r^2}$', + p18:'$\\\\vec{E}$', + p19:'$\\\\vec{E} = \\\\sum \\\\vec{E_i}$', + p20:'Силовые линии', + p21:'$A = qU$', + p22:'$U = E \\\\cdot d$', + p23:'$C = q/U$', + p24:'$W = CU^2/2$', + p25:'$\\\\mathcal{E}$', + p26:'$I = \\\\mathcal{E}/(R+r)$', + p27:'Опыт Эрстеда', + p28:'$\\\\vec{B}$', + p29:'$F = BIL\\\\sin\\\\alpha$', + p30:'$F = qvB$', + p31:'$\\\\Phi = BS\\\\cos\\\\alpha$', + p32:'$\\\\mathcal{E}_i = -d\\\\Phi/dt$', + p33:'$L$, $W_L = LI^2/2$', + p34:'$\\\\rho(T)$', + p35:'$m = kIt$', + p36:'Виды разрядов', + p37:'n-/p-тип', +}; + +// Watermarks для секций (короткие) +const PARA_WM = { + p1:'МКТ', p2:'N_A', p3:'pV', p4:'T', p5:'pV=νRT', p6:'iso', p7:'cryst', p8:'σ', p9:'пар', p10:'φ', + p11:'U', p12:'A', p13:'Q', p14:'1-й', p15:'Карно', + p16:'q', p17:'Кулон', p18:'&vec;E', p19:'E', p20:'→', p21:'A=qU', p22:'U', p23:'C', p24:'CU²', + p25:'ε', p26:'Ом', + p27:'Эрстед', p28:'B', p29:'Ампер', p30:'Лоренц', p31:'Φ', p32:'Фарадей', p33:'L', + p34:'ρ(T)', p35:'m=kIt', p36:'plasma', p37:'n/p', + final1:'★', final2:'★', final3:'★', final4:'★', final5:'★', final6:'★', +}; + +const CHAPTERS = { + ch1: { + paras: ['p1','p2','p3','p4','p5','p6','p7','p8','p9','p10'], final: 'final1', + title: 'Основы МКТ', + headerSub: 'Молекулярно-кинетическая теория · идеальный газ · изопроцессы · влажность', + hero: { h:'Молекулярная физика — почему вещество ведёт себя так', p:'Молекулярно-кинетическая теория объясняет свойства вещества движением и взаимодействием молекул. Изучаем газы, твёрдые тела, жидкости, температуру, давление и влажность.' }, + pri:'#2563eb', priD:'#1d4ed8', priSoft:'#dbeafe', priLight:'#60a5fa', + headerGrad:'linear-gradient(110deg,#1e3a8a 0%,#2563eb 55%,#60a5fa 100%)', + chNum:1, watermarkHero:'T', + }, + ch2: { + paras: ['p11','p12','p13','p14','p15'], final: 'final2', + title: 'Термодинамика', + headerSub: 'Внутренняя энергия · работа · теплота · 1-й закон · тепловые двигатели', + hero: { h:'Термодинамика — превращения энергии', p:'Термодинамика — наука о превращении энергии. Внутренняя энергия, работа газа, количество теплоты, первый закон и тепловые двигатели.' }, + pri:'#059669', priD:'#047857', priSoft:'#d1fae5', priLight:'#34d399', + headerGrad:'linear-gradient(110deg,#064e3b 0%,#059669 55%,#34d399 100%)', + chNum:2, watermarkHero:'ΔU', + }, + ch3: { + paras: ['p16','p17','p18','p19','p20','p21','p22','p23','p24'], final: 'final3', + title: 'Электростатика', + headerSub: 'Заряд · Кулон · поле · потенциал · напряжение · конденсаторы', + hero: { h:'Электростатика — поле неподвижных зарядов', p:'Электрический заряд создаёт поле. Изучаем закон Кулона, напряжённость и потенциал поля, конденсаторы и их энергию.' }, + pri:'#7c3aed', priD:'#6d28d9', priSoft:'#ede9fe', priLight:'#a78bfa', + headerGrad:'linear-gradient(110deg,#3b0764 0%,#7c3aed 55%,#a78bfa 100%)', + chNum:3, watermarkHero:'+q', + }, + ch4: { + paras: ['p25','p26'], final: 'final4', + title: 'Постоянный ток', + headerSub: 'ЭДС источника · закон Ома для полной цепи · КПД', + hero: { h:'Постоянный ток в полной цепи', p:'Постоянный ток в полной цепи: ЭДС источника, закон Ома, КПД источника.' }, + pri:'#db2777', priD:'#be185d', priSoft:'#fce7f3', priLight:'#f472b6', + headerGrad:'linear-gradient(110deg,#831843 0%,#db2777 55%,#f472b6 100%)', + chNum:4, watermarkHero:'I', + }, + ch5: { + paras: ['p27','p28','p29','p30','p31','p32','p33'], final: 'final5', + title: 'Магнитное поле и ЭМИ', + headerSub: 'Магнитное поле · Ампер · Лоренц · поток · индукция · Ленц · Фарадей · самоиндукция', + hero: { h:'Магнитное поле и электромагнитная индукция', p:'Магнитное поле тока, сила Ампера, сила Лоренца, явление электромагнитной индукции и закон Фарадея.' }, + pri:'#0891b2', priD:'#0e7490', priSoft:'#cffafe', priLight:'#22d3ee', + headerGrad:'linear-gradient(110deg,#164e63 0%,#0891b2 55%,#22d3ee 100%)', + chNum:5, watermarkHero:'B', + }, + ch6: { + paras: ['p34','p35','p36','p37'], final: 'final6', + title: 'Ток в различных средах', + headerSub: 'Металлы · сверхпроводимость · электролиз · газы · плазма · полупроводники', + hero: { h:'Электрический ток в разных средах', p:'Электрический ток ведёт себя по-разному в металлах, электролитах, газах и полупроводниках. Сверхпроводимость, электролиз, плазма, p-n переход.' }, + pri:'#10b981', priD:'#059669', priSoft:'#d1fae5', priLight:'#6ee7b7', + headerGrad:'linear-gradient(110deg,#064e3b 0%,#10b981 55%,#6ee7b7 100%)', + chNum:6, watermarkHero:'n/p', + }, +}; + +// === Sidebar rows: краткие подсказки для каждого § === +const SIDEBAR_ROWS = { + p1: [['Положения','3 положения МКТ: все вещества из частиц, частицы движутся, взаимодействуют'],['Опыты','Броуновское движение, диффузия'],['Размер','$d \\\\sim 10^{-10}$ м']], + p2: [['Авогадро','$N_A = 6{,}022 \\\\cdot 10^{23}$ 1/моль'],['Количество в-ва','$\\\\nu = N/N_A = m/M$'],['Молярная масса','$M$ — кг/моль']], + p3: [['Идеальный газ','точечные частицы, упругие столкновения'],['Осн. ур-ие МКТ','$p = \\\\tfrac{1}{3}nm\\\\overline{v^2}$'],['Концентрация','$n = N/V$']], + p4: [['Темпер.','$T$ — мера ср. кин. энергии'],['$\\\\overline{E_k}$','$\\\\overline{E_k} = \\\\tfrac{3}{2}kT$'],['Шкалы','$T_K = t + 273{,}15$']], + p5: [['Менделеев-Клапейрон','$pV = \\\\nu RT$'],['Клапейрон','$\\\\frac{pV}{T} = \\\\text{const}$'],['$R$','$R = 8{,}314$ Дж/(моль·К)']], + p6: [['Изотерма','$T = \\\\text{const}$: $pV = \\\\text{const}$ (Бойль-Мариотт)'],['Изобара','$p = \\\\text{const}$: $V/T = \\\\text{const}$ (Гей-Люссак)'],['Изохора','$V = \\\\text{const}$: $p/T = \\\\text{const}$ (Шарль)']], + p7: [['Крист.','дальний порядок'],['Аморф.','ближний порядок, нет $T_{пл}$'],['Деформ.','упругая, пластическая']], + p8: [['Жидкость','ближний порядок, текучесть'],['Поверх. натяж.','$\\\\sigma$ — Н/м'],['Капилляр','смачивание']], + p9: [['Испар.','с поверхности'],['Кипение','$p_{нас} = p_{внеш}$'],['Нас. пар','динам. равновесие']], + p10: [['Абс. вл.','$\\\\rho_{пара}$ — кг/м³'],['Отн. вл.','$\\\\varphi = p/p_{нас} \\\\cdot 100\\\\%$'],['Точка росы','$T$, при которой $\\\\varphi = 100\\\\%$']], + p11: [['$U$','$U = \\\\tfrac{3}{2}\\\\nu RT$ — для одноат. идеал. газа'],['$\\\\Delta U$','зависит только от $T$ для идеал. газа'],['Способы','теплопередача, работа']], + p12: [['$A_{газ}$','$A = p\\\\Delta V$ при $p = \\\\text{const}$'],['Геометрия','площадь под графиком $p(V)$'],['Знак','газ расш. — $A > 0$']], + p13: [['$Q = cm\\\\Delta T$','нагрев/охлаждение'],['$Q = \\\\lambda m$','плавление'],['$Q = rm$','парообразование'],['$Q = qm$','сгорание']], + p14: [['1-й закон','$Q = \\\\Delta U + A$'],['Изопроц.','частные случаи'],['Адиабат.','$Q = 0 \\\\Rightarrow A = -\\\\Delta U$']], + p15: [['КПД','$\\\\eta = A_{пол}/Q_1$'],['Карно','$\\\\eta_{max} = (T_1 - T_2)/T_1$'],['Циклы','Отто, Дизель']], + p16: [['Заряд','$q$ — Кл'],['Электрон','$e = 1{,}6 \\\\cdot 10^{-19}$ Кл'],['Закон сохр.','$\\\\sum q = \\\\text{const}$ в замкн. системе'],['$q = ne$','дискретность']], + p17: [['Закон','$F = k\\\\dfrac{|q_1 q_2|}{r^2}$'],['$k$','$k = 9 \\\\cdot 10^9$ Н·м²/Кл²'],['$\\\\varepsilon_0$','$\\\\varepsilon_0 = 8{,}85 \\\\cdot 10^{-12}$ Ф/м']], + p18: [['Поле','посредник взаимодействия'],['Свойства','действует на заряд силой $\\\\vec{F}$'],['Источник','$+q$ или $-q$']], + p19: [['$\\\\vec{E}$','$\\\\vec{E} = \\\\vec{F}/q_0$'],['Точ. заряд','$E = k|q|/r^2$'],['Суперпоз.','$\\\\vec{E} = \\\\sum \\\\vec{E_i}$']], + p20: [['Линии','касат. — $\\\\vec{E}$'],['Густота','$E$ велико — линии чаще'],['$+ \\\\to -$','начало на $+$, конец на $-$ или $\\\\infty$']], + p21: [['Работа поля','$A_{поля} = qU$ — не зависит от пути'],['Потенц.','$\\\\varphi = W_p/q$'],['Знак','от $+$ к $-$ ток. зар. — $A > 0$']], + p22: [['Разность','$U = \\\\varphi_1 - \\\\varphi_2$'],['Однор. поле','$U = Ed$'],['Эквипот.','$\\\\bot$ линиям $\\\\vec{E}$']], + p23: [['$C = q/U$','Ф (фарад)'],['Плоский','$C = \\\\varepsilon\\\\varepsilon_0 S/d$'],['Парал.','$C = \\\\sum C_i$'],['Послед.','$1/C = \\\\sum 1/C_i$']], + p24: [['Энергия','$W = \\\\tfrac{CU^2}{2} = \\\\tfrac{q^2}{2C} = \\\\tfrac{qU}{2}$'],['Плотн.','$w = \\\\tfrac{\\\\varepsilon\\\\varepsilon_0 E^2}{2}$']], + p25: [['$\\\\mathcal{E}$','ЭДС — В'],['Сторон. силы','внутри источника'],['$\\\\mathcal{E} = A_{стор}/q$','']], + p26: [['$I = \\\\mathcal{E}/(R+r)$','полная цепь'],['КЗ','$I_{кз} = \\\\mathcal{E}/r$'],['КПД','$\\\\eta = R/(R+r)$'],['$U = \\\\mathcal{E} - Ir$','напр. на полюсах']], + p27: [['Эрстед','ток отклоняет стрелку'],['Поле тока','вихревое, $\\\\vec{B}$'],['Правая рука','для проводника']], + p28: [['$\\\\vec{B}$','индукция магн. поля — Тл'],['Линии','замкнутые, без начала/конца'],['Опр.','$B = F_{max}/(IL)$']], + p29: [['$F_A = BIL\\\\sin\\\\alpha$','сила Ампера'],['Левая рука','для напр-я силы'],['$F_A \\\\bot \\\\vec{B}, \\\\vec{I}$','']], + p30: [['$F_л = qvB\\\\sin\\\\alpha$','сила Лоренца'],['Радиус','$r = mv/(qB)$'],['$F_л \\\\bot \\\\vec{v}$','траектория — окружность/спираль']], + p31: [['$\\\\Phi = BS\\\\cos\\\\alpha$','магн. поток — Вб'],['ЭМИ','при $\\\\Delta\\\\Phi \\\\ne 0$ возникает $\\\\mathcal{E}_i$'],['Опыт Фарадея','']], + p32: [['Ленц','$I_{инд}$ противодействует причине'],['$\\\\mathcal{E}_i = -d\\\\Phi/dt$','закон Фарадея'],['Знак','определяет Ленц']], + p33: [['$\\\\mathcal{E}_{si} = -L\\\\dfrac{dI}{dt}$','самоиндукция'],['$L$','индуктивность — Гн'],['$W_L = LI^2/2$','энергия магн. поля']], + p34: [['Носители','свободные электроны'],['$\\\\rho(T) = \\\\rho_0(1 + \\\\alpha t)$','зависимость от $T$'],['Сверхпров.','$T < T_c$, $\\\\rho = 0$']], + p35: [['Электролит','раствор/расплав ионных в-в'],['$m = kIt$','1-й закон Фарадея'],['$k = M/(zF)$','эл.-хим. эквивалент'],['$F$','$F = 96485$ Кл/моль']], + p36: [['Несам.','требует ионизатора'],['Самост.','тлеющий, искровой, дуговой, коронный'],['Плазма','газ из ионов и электронов']], + p37: [['n-тип','примесь-донор, носители — электроны'],['p-тип','примесь-акцептор, носители — дырки'],['p-n','одностор. проводимость, диод']], +}; + +// Tips на каждый параграф — краткая подсказка +const TIPS_HTML = { + p1: 'Положения МКТ: все вещества из частиц, частицы движутся, взаимодействуют. Доказательства — диффузия, броуновское движение.', + p2: '$N_A = 6{,}022 \\\\cdot 10^{23}$ — число частиц в 1 моле. $\\\\nu = m/M = N/N_A$.', + p3: 'Идеальный газ: точечные молекулы, упругие столкновения. Основное ур-ие МКТ: $p = \\\\tfrac{1}{3}nm\\\\overline{v^2}$.', + p4: '$T$ — мера ср. кин. энергии: $\\\\overline{E_k} = \\\\tfrac{3}{2}kT$. Абс. ноль $T = 0$ К $= -273{,}15$°C.', + p5: 'Уравнение Менделеева-Клапейрона: $pV = \\\\nu RT$, где $R = 8{,}314$ Дж/(моль·К).', + p6: 'Изопроцессы: при фиксации одного параметра ($T$, $p$ или $V$). Бойль-Мариотт, Гей-Люссак, Шарль.', + p7: 'Кристалл — дальний порядок, $T_{пл}$ определена. Аморфные — ближний порядок, плавятся плавно.', + p8: 'Жидкость имеет $V$, но не имеет форму. Поверхностное натяжение $\\\\sigma$ создаёт «плёнку» на поверхности.', + p9: 'Насыщенный пар — пар в динамическом равновесии с жидкостью. $p_{нас}$ зависит только от $T$.', + p10: 'Отн. влажность: $\\\\varphi = p_{пара}/p_{нас} \\\\cdot 100\\\\%$. При $\\\\varphi = 100\\\\%$ — точка росы.', + p11: 'Внутр. энергия идеал. одноат. газа: $U = \\\\tfrac{3}{2}\\\\nu RT$. Зависит только от $T$.', + p12: 'Работа газа: $A = p\\\\Delta V$ при $p = \\\\text{const}$. Геометрически — площадь под графиком $p(V)$.', + p13: '$Q = cm\\\\Delta T$ — нагрев. $Q = \\\\lambda m$ — плавление. $Q = rm$ — парообразование. $Q = qm$ — сгорание.', + p14: '1-й закон термодинамики: $Q = \\\\Delta U + A$ — теплота идёт на изменение внутр. энергии и работу газа.', + p15: 'КПД цикла Карно: $\\\\eta_{max} = (T_1 - T_2)/T_1$ — максимально возможный при данных $T_1, T_2$.', + p16: 'Заряд квантуется: $q = ne$, где $e = 1{,}6 \\\\cdot 10^{-19}$ Кл. Закон сохранения заряда — фундаментальный.', + p17: '$F = k\\\\dfrac{|q_1 q_2|}{r^2}$, $k = 9 \\\\cdot 10^9$ Н·м²/Кл². Аналог закона всемирного тяготения.', + p18: 'Поле — посредник взаимодействия. Действует на заряд силой $\\\\vec{F} = q\\\\vec{E}$.', + p19: '$\\\\vec{E} = \\\\vec{F}/q_0$ — векторная характеристика поля. Принцип суперпозиции: $\\\\vec{E} = \\\\sum \\\\vec{E_i}$.', + p20: 'Линии напряжённости — касательные к $\\\\vec{E}$. Начинаются на «+» зарядах, заканчиваются на «−» или в $\\\\infty$.', + p21: 'Работа поля не зависит от пути: $A_{поля} = qU = q(\\\\varphi_1 - \\\\varphi_2)$. Поле потенциально.', + p22: 'В однородном поле: $U = E \\\\cdot d$. Эквипотенциальные поверхности перпендикулярны линиям $\\\\vec{E}$.', + p23: '$C = q/U$ — Ф. Плоский: $C = \\\\dfrac{\\\\varepsilon\\\\varepsilon_0 S}{d}$. Параллельно — $C_\\\\Sigma = \\\\sum C_i$.', + p24: '$W = \\\\dfrac{CU^2}{2} = \\\\dfrac{q^2}{2C} = \\\\dfrac{qU}{2}$ — три эквивалентные формулы.', + p25: 'ЭДС — работа сторонних сил по перемещению единичного заряда: $\\\\mathcal{E} = A_{стор}/q$. Измер. в В.', + p26: 'Закон Ома для полной цепи: $I = \\\\dfrac{\\\\mathcal{E}}{R + r}$. КПД источника: $\\\\eta = R/(R + r)$.', + p27: 'Опыт Эрстеда показал: ток создаёт магн. поле. Линии $\\\\vec{B}$ вокруг тока — концентр. окружности.', + p28: '$\\\\vec{B}$ — индукция магн. поля, измер. в Тл. Линии замкнуты (магн. поле — вихревое).', + p29: 'Сила Ампера: $F_A = BIL\\\\sin\\\\alpha$. Направление — по правилу левой руки.', + p30: 'Сила Лоренца: $F_л = qvB\\\\sin\\\\alpha$. Заряд движется по окружности с $r = mv/(qB)$.', + p31: 'Магн. поток: $\\\\Phi = BS\\\\cos\\\\alpha$. Измеряется в Вб. ЭМИ возникает при $\\\\Delta\\\\Phi \\\\ne 0$.', + p32: 'Закон Фарадея: $\\\\mathcal{E}_i = -\\\\dfrac{d\\\\Phi}{dt}$. Правило Ленца: $I_{инд}$ противодействует $\\\\Delta\\\\Phi$.', + p33: 'Самоиндукция: $\\\\mathcal{E}_{si} = -L\\\\dfrac{dI}{dt}$. Энергия магн. поля катушки: $W_L = LI^2/2$.', + p34: 'В металлах носители — свободные электроны. $\\\\rho(T) = \\\\rho_0(1 + \\\\alpha t)$. Сверхпров.: $\\\\rho = 0$.', + p35: 'Электролиз: $m = kIt$ — 1-й закон Фарадея. $k = M/(zF)$, $F = 96485$ Кл/моль.', + p36: 'Самостоятельный разряд: тлеющий, искровой, дуговой, коронный. Плазма — ионизованный газ.', + p37: 'Полупроводники: n-тип (донор, электроны) и p-тип (акцептор, дырки). p-n переход — диод.', + final1: 'Финал главы 1 — интегрированные задачи по §§1–10. В разработке (Phase 1+).', + final2: 'Финал главы 2 — интегрированные задачи по §§11–15. В разработке (Phase 2+).', + final3: 'Финал главы 3 — интегрированные задачи по §§16–24. В разработке (Phase 3+).', + final4: 'Финал главы 4 — интегрированные задачи по §§25–26. В разработке (Phase 4+).', + final5: 'Финал главы 5 — интегрированные задачи по §§27–33. В разработке (Phase 5+).', + final6: 'Финал главы 6 — интегрированные задачи по §§34–37. В разработке (Phase 6+).', +}; + +// === Билд одного ch === +function buildCh(chKey) { + const C = CHAPTERS[chKey]; + const slug = 'physics-10-' + chKey; + const lsPrefix = 'physics10_' + chKey; + const xpKey = 'physics10_xp'; + const allParas = [...C.paras, C.final]; + const paraNum = (pid) => { + if (pid.startsWith('final')) return '★'; + return '§\xa0' + pid.slice(1); + }; + // PARAS JS literal + const parasArr = allParas.map(pid => { + if (pid.startsWith('final')) { + return ` { id:'${pid}', num:'\\u2605', name:'Финал главы', sub:'Итоги \\u00b7 боссы главы ${C.chNum}', final:true }`; + } + const sub = PARA_SUBS[pid] || ''; + return ` { id:'${pid}', num:'\\u00a7 ${pid.slice(1)}', name:${JSON.stringify(PARA_NAMES[pid])}, sub:'${sub}' }`; + }).join(',\n'); + + // TOTAL_PARAS + const total = allParas.length; + + // ACH_LABELS — мини + const achLabels = [ + ` start:"Начало главы ${C.chNum}!"`, + ...C.paras.map(pid => ` ${pid}_done:${JSON.stringify(PARA_NAMES[pid] + ' освоен!')}`), + ` ${chKey}_done:"Глава ${C.chNum} пройдена!"`, + ].join(',\n'); + + // SIDEBARS JS — для каждой п в главе + final + const sidebarObj = allParas.map(pid => { + const rows = pid.startsWith('final') + ? [[`§§${C.paras[0].slice(1)}–${C.paras[C.paras.length-1].slice(1)}`, `теория главы ${C.chNum}`],['Награда','+50 XP']] + : (SIDEBAR_ROWS[pid] || [['В разработке',`шпаргалка ${pid}`]]); + const titleStr = pid.startsWith('final') ? `Финал главы ${C.chNum}` : `Шпаргалка ${pid.startsWith('p') ? '§' + pid.slice(1) : pid}`; + const rowsLit = rows.map(([k,v]) => `["${k}","${v}"]`).join(','); + return ` ${pid}:{title:"${titleStr}",rows:[${rowsLit}]}`; + }).join(',\n'); + + // TIPS JS + const tipsArr = allParas.map(pid => { + const html = TIPS_HTML[pid] || `Подсказка к ${pid} — в разработке.`; + return ` {sec:'${pid}',html:"${html.replace(/"/g, '\\"')}"}`; + }).join(',\n'); + + // STUB-builder для каждого п + const builders = allParas.map(pid => { + const isFinal = pid.startsWith('final'); + const name = isFinal ? `Финал главы ${C.chNum}` : PARA_NAMES[pid]; + const num = isFinal ? '★' : '§' + pid.slice(1); + const next = (() => { + const idx = allParas.indexOf(pid); + return idx < allParas.length - 1 ? allParas[idx+1] : null; + })(); + const prev = (() => { + const idx = allParas.indexOf(pid); + return idx > 0 ? allParas[idx-1] : null; + })(); + const prevStr = prev ? `'${prev}'` : 'null'; + const nextStr = next ? `'${next}'` : 'null'; + + return `function build_${pid}(){ + const box = document.getElementById('${pid}-body'); + let html = ''; + html += makeCard('theory', ${JSON.stringify(name)}, ${JSON.stringify(num)}, \` +

${name} — этот параграф в разработке (Phase 1+).

+

Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.

+

+ Phase 0: создан скелет учебника. Phase ${C.chNum}+: наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019). +

+ \`); + html += secNav(${prevStr}, ${nextStr}); + html += readButton('${pid}'); + box.innerHTML = html; + renderMath(box); + wireReadBtn('${pid}'); +}`; + }).join('\n\n'); + + // BUILDERS map + const buildersMap = allParas.map(pid => `${pid}:()=>build_${pid}()`).join(', '); + + // sec node HTML + const secNodes = allParas.map(pid => { + const isFinal = pid.startsWith('final'); + const num = isFinal ? '★' : '§ ' + pid.slice(1); + const titleHtml = isFinal ? 'Финал главы' : PARA_NAMES[pid]; + const wm = PARA_WM[pid] || '?'; + const numHtml = isFinal + ? `` + : `${num}`; + return `
${numHtml}

${titleHtml}

`; + }).join('\n'); + + // sec accent CSS — все секции одного цвета главы + const secCss = allParas.map(pid => + `.sec[id="sec-${pid}"]{ --sec-acc:${C.pri}; --sec-acc-d:${C.priD}; --sec-acc-soft:${C.priSoft}; }` + ).join('\n'); + + // Search NAMES map for secNav titles + const namesObj = allParas.map(pid => { + const v = pid.startsWith('final') ? 'Финал' : '\\xA7' + pid.slice(1); + return `${pid}:'${v}'`; + }).join(','); + + // === Финальный HTML === + const html = ` + + + + + + +Физика 10 · Глава ${C.chNum} · «${C.title}» + + + + + + + + + + + + + +
+
+
+

Физика 10 · Глава ${C.chNum}

+
${C.headerSub}
+
+
+ К физике 10 + + + +
+
+
+ +
+
+ +
+

${C.hero.h}

+

${C.hero.p}

+
+ +
+ Прогресс по главе +
+ 0% +
+
+
+
+ +
+
Параграфы главы
+
+
+ +${secNodes} + +
+ +
+
+ +
Интерактивный учебник «Физика 10» · Глава ${C.chNum} · «${C.title}» · LearnSpace
+ +
Достижение!
+ + + + + + +`; + + return html; +} + +// === Run === +for (const chKey of ['ch1','ch2','ch3','ch4','ch5','ch6']) { + const dst = path.join(TBOOKS, `physics_10_${chKey}.html`); + const html = buildCh(chKey); + fs.writeFileSync(dst, html); + + // Parse-check the inline ') + .replace(/'); + }); + return html; +} + +async function loadDom(file) { + const errors = []; + const vc = new VirtualConsole(); + vc.on('jsdomError', e => errors.push(e.message)); + const dom = new JSDOM(buildPage(file), { + runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/', + beforeParse(w) { w.scrollTo = function () {}; } + }); + await wait(160); + return { dom, errors, doc: dom.window.document }; +} + +const CHAPTERS = [ + { file: 'chemistry_7_ch1.html', cards: 15, first: 'sec-p1' }, + { file: 'chemistry_7_ch2.html', cards: 8, first: 'sec-p13' }, + { file: 'chemistry_7_ch3.html', cards: 9, first: 'sec-p18' }, + { file: 'chemistry_7_ch4.html', cards: 7, first: 'sec-p23' } +]; + +for (const ch of CHAPTERS) { + test(`${ch.file}: SPA без ошибок, ${ch.cards} карточек, активен ${ch.first}`, async () => { + const { doc, errors } = await loadDom(ch.file); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); + assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, ch.cards, ch.cards + ' карточек'); + const active = doc.querySelector('.sec.active'); + assert.ok(active && active.id === ch.first, 'активен ' + ch.first); + const firstId = ch.first.replace('sec-', ''); + assert.ok(doc.querySelector('#' + firstId + '-body .para-hero'), 'para-hero первого §'); + assert.ok(doc.querySelector('#' + firstId + '-body .read-wrap'), 'кнопка прочтения первого §'); + }); +} + +test('ch1: переход к §9 и финалу строит заглушку без ошибок', async () => { + const { doc, errors } = await loadDom('chemistry_7_ch1.html'); + doc.defaultView.goTo('p9'); await wait(80); + assert.ok(doc.querySelector('#p9-body .para-hero'), 'para-hero §9'); + doc.defaultView.goTo('final1'); await wait(80); + assert.ok(doc.querySelector('#final1-body .para-hero'), 'para-hero финала'); + assert.equal(doc.querySelector('#final1-body .read-wrap'), null, 'у финала нет кнопки прочтения'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + +/* ── Хаб: каталог глав + финал курса ── */ +function buildHub() { + let html = readF('frontend/textbooks/chemistry_7_hub.html'); + return html + .replace(/') + .replace(/ + + + + + + + + + + +
+
+
+

Химия 7 · Глава 1

+
Первоначальные химические понятия: вещество, атом, элемент, молекула, формула, валентность, реакция и уравнение
+
+
+ К главам + +
+
+
+ +
+
+ +
+

С чего начинается химия

+

Химия изучает вещества, их свойства и превращения. В этой главе вы научитесь главному «языку» химии: узнаете об атомах и химических элементах, научитесь записывать состав веществ формулами, определять валентность и составлять уравнения химических реакций.

+
+ +
+ Прогресс главы +
+ 0% +
+
+
+
+ +
+
Параграфы главы
+
+
+ +
§ 1

Химия — наука о веществах

+
§ 2

Чистые вещества и смеси

+
ПР 1

Практическая работа: знакомство с лабораторией. Разделение смесей

+
§ 3

Атомы. Химические элементы

+
§ 4

Относительная атомная масса химических элементов

+
§ 5

Молекулы. Простые вещества

+
§ 6

Сложные вещества

+
§ 7

Химическая формула

+
§ 8

Относительная молекулярная масса

+
§ 9

Валентность

+
§ 10

Явления физические и химические. Признаки химических реакций

+
Лаб. 1

Лабораторный опыт: признаки протекания химических реакций

+
§ 11

Закон сохранения массы веществ. Химические уравнения

+
§ 12

Составление уравнений химических реакций

+

Финал главы

+ +
+ +
+ +
Интерактивный учебник «Химия — 7 класс» · Глава 1 · «Первоначальные химические понятия» · LearnSpace
+
Достижение!
+ + + + + diff --git a/frontend/textbooks/chemistry_7_ch2.html b/frontend/textbooks/chemistry_7_ch2.html new file mode 100644 index 0000000..e58c89f --- /dev/null +++ b/frontend/textbooks/chemistry_7_ch2.html @@ -0,0 +1,118 @@ + + + + + + + + +Химия 7 · Глава 2 · «Кислород» + + + + + + + + + + + + + + +
+
+
+

Химия 7 · Глава 2

+
Кислород: воздух, горение, оксиды, получение кислорода
+
+
+ К главам + +
+
+
+ +
+
+ +
+

Кислород — газ, который даёт жизнь и огонь

+

Кислород — самый распространённый элемент на Земле. Без него невозможны дыхание и горение. В этой главе вы узнаете состав воздуха, познакомитесь с кислородом как простым веществом, изучите горение и оксиды, научитесь получать кислород в лаборатории.

+
+ +
+ Прогресс главы +
+ 0% +
+
+
+
+ +
+
Параграфы главы
+
+
+ +
§ 13

Воздух как смесь газов

+
Лаб. 2

Лабораторный опыт: сборка приборов для получения и собирания газов

+
§ 14

Кислород как химический элемент и простое вещество

+
§ 15

Химические свойства кислорода

+
§ 16

Оксиды

+
§ 17

Получение кислорода

+
ПР 2

Практическая работа: получение кислорода и изучение его свойств

+

Финал главы

+ +
+ +
+ +
Интерактивный учебник «Химия — 7 класс» · Глава 2 · «Кислород» · LearnSpace
+
Достижение!
+ + + + + diff --git a/frontend/textbooks/chemistry_7_ch3.html b/frontend/textbooks/chemistry_7_ch3.html new file mode 100644 index 0000000..da9ee68 --- /dev/null +++ b/frontend/textbooks/chemistry_7_ch3.html @@ -0,0 +1,120 @@ + + + + + + + + +Химия 7 · Глава 3 · «Водород» + + + + + + + + + + + + + + +
+
+
+

Химия 7 · Глава 3

+
Водород: свойства, кислоты и индикаторы, ряд активности, соли
+
+
+ К главам + +
+
+
+ +
+
+ +
+

Водород — самый лёгкий элемент Вселенной

+

Водород — первый элемент периодической системы и самый распространённый во Вселенной. В этой главе вы изучите его свойства, познакомитесь с кислотами и индикаторами, узнаете, как металлы вытесняют водород из кислот, и что такое соли.

+
+ +
+ Прогресс главы +
+ 0% +
+
+
+
+ +
+
Параграфы главы
+
+
+ +
§ 18

Водород — химический элемент и простое вещество

+
§ 19

Химические свойства водорода

+
§ 20

Понятие о кислотах

+
Лаб. 3

Лабораторный опыт: действие кислот на индикаторы

+
§ 21

Взаимодействие кислот с металлами

+
Лаб. 4

Лабораторный опыт: взаимодействие серной и соляной кислот с металлами

+
§ 22

Соли — продукты замещения атомов водорода в кислотах на металлы

+
ПР 3

Практическая работа: получение водорода и изучение его свойств

+

Финал главы

+ +
+ +
+ +
Интерактивный учебник «Химия — 7 класс» · Глава 3 · «Водород» · LearnSpace
+
Достижение!
+ + + + + diff --git a/frontend/textbooks/chemistry_7_ch4.html b/frontend/textbooks/chemistry_7_ch4.html new file mode 100644 index 0000000..8dc7ba9 --- /dev/null +++ b/frontend/textbooks/chemistry_7_ch4.html @@ -0,0 +1,116 @@ + + + + + + + + +Химия 7 · Глава 4 · «Вода» + + + + + + + + + + + + + + +
+
+
+

Химия 7 · Глава 4

+
Вода: состав и свойства, основания и индикаторы, нейтрализация, охрана природы
+
+
+ К главам + +
+
+
+ +
+
+ +
+

Вода — самое важное вещество на Земле

+

Вода знакома каждому, но в химии она удивительна. В этой главе вы изучите состав и свойства воды, познакомитесь с основаниями и индикаторами, узнаете о реакции нейтрализации и о том, как беречь воду и окружающую среду.

+
+ +
+ Прогресс главы +
+ 0% +
+
+
+
+ +
+
Параграфы главы
+
+
+ +
§ 23

Состав, физические и химические свойства воды

+
§ 24

Основания как сложные вещества

+
Лаб. 5

Лабораторный опыт: действие щелочей на индикаторы

+
§ 25

Реакция нейтрализации

+
ПР 4

Практическая работа: реакция нейтрализации

+
§ 26

Охрана окружающей среды

+

Финал главы

+ +
+ +
+ +
Интерактивный учебник «Химия — 7 класс» · Глава 4 · «Вода» · LearnSpace
+
Достижение!
+ + + + + diff --git a/frontend/textbooks/chemistry_7_hub.html b/frontend/textbooks/chemistry_7_hub.html new file mode 100644 index 0000000..60d4dd3 --- /dev/null +++ b/frontend/textbooks/chemistry_7_hub.html @@ -0,0 +1,540 @@ + + + + + + + + +Химия 7 класс — учебник + + + + + + + + + + +
+
+ +
+

Химия — 7 класс

+
Первый курс химии: вещества и реакции, кислород, водород, вода. 4 главы, 26 параграфов, 5 лабораторных опытов, 4 практические работы.
+
+
+ +
+
+
+ +
+ +
+
Х
+
+
Общий прогресс по курсу
+
Загрузка...
+
+
+ +
+ +
+ + +
+
Aₕ
+
Глава 1
+
Первоначальные химические понятия
+
§1–§12 · ЛО 1 · ПР 1
+
+
+
Вещества и смеси, атомы и химические элементы, относительная атомная масса, молекулы, простые и сложные вещества, химическая формула и $M_r$, валентность, признаки реакций, закон сохранения массы и химические уравнения.
+
+
Прогресс0%
+
+
+
Открыть главу
+
+
+ + +
+
O₂
+
Глава 2
+
Кислород
+
§13–§17 · ЛО 2 · ПР 2
+
+
+
Воздух как смесь газов, кислород — элемент и простое вещество, химические свойства кислорода и горение, оксиды, получение кислорода и катализаторы.
+
+
Прогресс0%
+
+
+
Открыть главу
+
+
+ + +
+
H₂
+
Глава 3
+
Водород
+
§18–§22 · ЛО 3,4 · ПР 3
+
+
+
Водород — элемент и простое вещество, химические свойства водорода, понятие о кислотах и индикаторах, взаимодействие кислот с металлами и ряд активности, соли как продукты замещения.
+
+
Прогресс0%
+
+
+
Открыть главу
+
+
+ + +
+
H₂O
+
Глава 4
+
Вода
+
§23–§26 · ЛО 5 · ПР 4
+
+
+
Состав, физические и химические свойства воды, основания как сложные вещества и индикаторы, реакция нейтрализации, охрана окружающей среды.
+
+
Прогресс0%
+
+
+
Открыть главу
+
+
+ +
+ +
+ +
+ +
Шпаргалка курса
+
+
Гл. 1Первоначальные понятия
  • $M_r=\sum A_r$ всех атомов
  • Валентность по водороду: H — I, O — II
  • Простое — один элемент, сложное — разные
  • В реакции масса сохраняется → коэффициенты
+
Гл. 2Кислород
  • Воздух: $N_2$ 78 %, $O_2$ 21 %
  • Горение: вещество $+ O_2 \to$ оксид
  • Оксид — соединение элемента с O
  • $O_2$ из разложения (катализатор $MnO_2$)
+
Гл. 3Водород
  • $H_2$ — самый лёгкий газ
  • Кислота = H + кислотный остаток
  • Ряд активности: до $H_2$ — вытесняют газ
  • Соль = металл + кислотный остаток
+
Гл. 4Вода
  • Разложение воды: $H_2 : O_2 = 2 : 1$
  • Основание = металл + OH
  • Индикаторы: лакмус, метилоранж, фенолфталеин
  • Нейтрализация: кислота + основание → соль + вода
+
+ +
8 интегрированных боссов
+
Боссов побеждено: 0 / 8
+
+ +
+
+
Курс «Химия 7» пройден!
Вы прошли итоговую проверку по всем 4 главам. +150 XP, ачивка «Химик 7 класса» получена.
+ К каталогу +
+ +
+
+ +
+
+ +
+
+
Химик 7 класса
+
Изучите все 26 параграфов курса, чтобы получить достижение.
+
+
+ +
+ +
+ Интерактивный учебник «Химия — 7 класс» · LearnSpace +
+ + + + + From 185ce2b640e8adf85c4daa7237a545c4adfde5cd Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:22:36 +0300 Subject: [PATCH 04/11] =?UTF-8?q?feat(chemistry7):=20Phase=201=20=D0=92?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B0=201=20=E2=80=94=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=201,=20=C2=A7=C2=A71=E2=80=933=20+=20=D0=9F=D0=A01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §1 Химия — наука о веществах (классификатор тело/вещество), §2 Чистые вещества и смеси (разделитель смесей: фильтр/выпаривание/ магнит/отстаивание/перегонка), ПР1 разделение смеси соль+песок, §3 Атомы и химические элементы (каталог элементов + тренажёр символов). Теория, тренажёры задач (POOLS), глоссарные шпаргалки. chem7_ch1_widgets.js. Тест: 7/7 pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/chemistry7-page.test.js | 16 +++ frontend/js/chem7_ch1_widgets.js | 161 ++++++++++++++++++++++++ frontend/textbooks/chemistry_7_ch1.html | 127 ++++++++++++++++++- 3 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 frontend/js/chem7_ch1_widgets.js diff --git a/backend/tests/chemistry7-page.test.js b/backend/tests/chemistry7-page.test.js index 5a1e23d..c34fb1d 100644 --- a/backend/tests/chemistry7-page.test.js +++ b/backend/tests/chemistry7-page.test.js @@ -23,6 +23,7 @@ function buildPage(file) { '/js/biochem-core.js': readF('frontend/js/biochem-core.js'), '/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'), '/js/chem7_svg.js': readF('frontend/js/chem7_svg.js'), + '/js/chem7_ch1_widgets.js': readF('frontend/js/chem7_ch1_widgets.js'), '/js/chem8_engine.js': readF('frontend/js/chem8_engine.js') }; html = html @@ -67,6 +68,21 @@ for (const ch of CHAPTERS) { }); } +test('ch1 Волна 1: интерактивы §1–§3 + ПР1 монтируются без ошибок', async () => { + const { doc, errors } = await loadDom('chemistry_7_ch1.html'); + // §1 строится при загрузке (первый §) — классификатор «тело/вещество» + assert.ok(doc.querySelector('#p1-cls .c7-chip'), 'классификатор §1'); + doc.defaultView.goTo('p2'); await wait(100); + assert.ok(doc.querySelector('#p2-sep .c7-m'), 'разделитель смесей §2'); + doc.defaultView.goTo('pr1'); await wait(100); + assert.ok(doc.querySelector('#pr1-sep .c7-m'), 'разделитель смесей ПР1'); + doc.defaultView.goTo('p3'); await wait(100); + assert.ok(doc.querySelectorAll('#p3-el .el-cell').length > 10, 'каталог элементов §3'); + assert.ok(doc.querySelector('#p3-drill .c7-d'), 'тренажёр символов §3'); + assert.ok(doc.querySelectorAll('#navDotsp3 .nav-dot').length >= 4, 'тренажёр задач §3'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + test('ch1: переход к §9 и финалу строит заглушку без ошибок', async () => { const { doc, errors } = await loadDom('chemistry_7_ch1.html'); doc.defaultView.goTo('p9'); await wait(80); diff --git a/frontend/js/chem7_ch1_widgets.js b/frontend/js/chem7_ch1_widgets.js new file mode 100644 index 0000000..b435934 --- /dev/null +++ b/frontend/js/chem7_ch1_widgets.js @@ -0,0 +1,161 @@ +/* chem7_ch1_widgets.js — интерактивы главы 1 «Первоначальные химические понятия» (Химия 7). + * Монтируются движком chem8_engine.js: window.CHEM8_WIDGETS[id] / window.FLAG_MOUNTS[id]. + * Используют window.Chem8 (chem8_svg.js): molarMass, elementCounts, arOf, fmt, equationBalancer. + * Без эмоджи; KaTeX-рендер — через window.chem8RenderMath. + */ +(function (W) { + 'use strict'; + function C() { return W.Chem8 || {}; } + function $(id) { return document.getElementById(id); } + function esc(s){ return String(s).replace(/&/g,'&').replace(//g,'>'); } + + /* ── общий мини-классификатор: чипы → 2 корзины ──────────────────── */ + function classifier(mount, opts) { + if (!mount || mount._built) return; mount._built = 1; + var items = opts.items.slice(); // {t, b} t=подпись, b=индекс корзины + var buckets = opts.buckets; // [name0, name1] + var placed = {}; // idx -> bucket + var pool = items.map(function (it, i) { return i; }); + function colorOf(ok){ return ok ? '#059669' : '#dc2626'; } + function render() { + var chips = pool.map(function (i) { + return ''; + }).join('') || 'Все карточки распределены.'; + var cols = buckets.map(function (name, bi) { + var inb = Object.keys(placed).filter(function (k) { return placed[k] === bi; }); + var cells = inb.map(function (k) { + var ok = items[k].b === bi; + return '
' + esc(items[k].t) + (ok ? ' ✓' : ' ✗') + '
'; + }).join('') || '
перетащите сюда…
'; + return '
' + esc(name) + '
' + cells + '
'; + }).join(''); + mount.innerHTML = '
' + chips + '
' + cols + '
' + + '
Кликни карточку, затем — корзину. Зелёный — верно, красный — ошибка.
'; + bind(); + } + var sel = null; + function bind() { + mount.querySelectorAll('.c7-chip').forEach(function (b) { + b.addEventListener('click', function () { + mount.querySelectorAll('.c7-chip').forEach(function (x) { x.style.outline = ''; }); + sel = +b.dataset.i; b.style.outline = '2px solid var(--pri)'; + }); + }); + mount.querySelectorAll('.c7-bucket').forEach(function (col) { + col.addEventListener('click', function () { + if (sel == null) return; + placed[sel] = +col.dataset.b; + pool = pool.filter(function (x) { return x !== sel; }); + sel = null; render(); + }); + }); + } + render(); + } + + /* §1 — классификатор «тело / вещество» */ + function mount_p1() { + var m = $('p1-cls'); if (!m) return; + classifier(m, { + buckets: ['Физическое тело', 'Вещество'], + items: [ + { t: 'стакан', b: 0 }, { t: 'вода', b: 1 }, { t: 'гвоздь', b: 0 }, { t: 'железо', b: 1 }, + { t: 'ложка', b: 0 }, { t: 'сахар', b: 1 }, { t: 'медь', b: 1 }, { t: 'линейка', b: 0 } + ] + }); + } + + /* §2 / ПР1 — разделитель смесей: выбери метод для смеси */ + var MIX = [ + { mix: 'Песок и вода', method: 'Фильтрование', why: 'Песок не растворяется — задерживается фильтром, вода проходит.' }, + { mix: 'Соль и вода', method: 'Выпаривание', why: 'Вода испаряется, соль остаётся на дне.' }, + { mix: 'Железные опилки и сера', method: 'Магнит', why: 'Железо притягивается магнитом, сера — нет.' }, + { mix: 'Вода и растительное масло', method: 'Отстаивание (делительная воронка)', why: 'Масло легче воды и не смешивается — слои разделяют.' }, + { mix: 'Спирт и вода', method: 'Перегонка (дистилляция)', why: 'У спирта и воды разные температуры кипения.' } + ]; + var METHODS = ['Фильтрование', 'Выпаривание', 'Магнит', 'Отстаивание (делительная воронка)', 'Перегонка (дистилляция)']; + function mount_sep(mountId) { + var m = $(mountId); if (!m || m._built) return; m._built = 1; + var idx = 0; + function render() { + var cur = MIX[idx]; + m.innerHTML = '
' + + '
Каким способом разделить смесь «' + esc(cur.mix) + '»?
' + + '
' + METHODS.map(function (mt) { + return ''; + }).join('') + '
' + + '
Выбери способ разделения.
'; + $(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; m._built = 0; render(); }); + var out = $(mountId + '-out'); + m.querySelectorAll('.c7-m').forEach(function (b) { + b.addEventListener('click', function () { + var ok = b.dataset.m === cur.method; + out.className = 'out ' + (ok ? 'ok' : 'bad'); + out.innerHTML = ok + ? 'Верно! ' + esc(cur.method) + '. ' + esc(cur.why) + : 'Не подходит. Подумай, чем различаются вещества в смеси (растворимость, магнитные свойства, температура кипения, плотность).'; + }); + }); + } + render(); + } + function mount_p2() { mount_sep('p2-sep'); } + function mount_pr1() { mount_sep('pr1-sep'); } + + /* §3 — каталог элементов + тренажёр «символ ↔ название» */ + var EL = { + H: [1, 'Водород'], He: [2, 'Гелий'], Li: [3, 'Литий'], C: [6, 'Углерод'], N: [7, 'Азот'], + O: [8, 'Кислород'], F: [9, 'Фтор'], Na: [11, 'Натрий'], Mg: [12, 'Магний'], Al: [13, 'Алюминий'], + Si: [14, 'Кремний'], P: [15, 'Фосфор'], S: [16, 'Сера'], Cl: [17, 'Хлор'], K: [19, 'Калий'], + Ca: [20, 'Кальций'], Fe: [26, 'Железо'], Cu: [29, 'Медь'], Zn: [30, 'Цинк'], Ag: [47, 'Серебро'] + }; + function mount_p3() { + var grid = $('p3-el'), info = $('p3-elinfo'); + if (grid && !grid._built) { + grid._built = 1; + Object.keys(EL).forEach(function (s) { + var ar = C().arOf ? C().arOf(s) : ''; + var c = document.createElement('div'); c.className = 'el-cell'; + c.innerHTML = '' + EL[s][0] + '' + s + '' + ar + ''; + c.addEventListener('click', function () { + grid.querySelectorAll('.el-cell').forEach(function (x) { x.classList.remove('on'); }); c.classList.add('on'); + if (info) info.innerHTML = '' + EL[s][1] + ' (' + s + ') · порядковый номер Z = ' + EL[s][0] + ' · A_r = ' + ar; + }); + grid.appendChild(c); + }); + } + /* drill: дан символ → выбери название */ + var d = $('p3-drill'); if (d && !d._built) { + d._built = 1; + var keys = Object.keys(EL), order = keys.slice(), qi = 0, score = 0, total = 0; + function nextQ() { + var sym = order[qi % order.length]; + var correct = EL[sym][1]; + var opts = [correct]; + while (opts.length < 4) { var r = EL[keys[(qi * 7 + opts.length * 13 + 3) % keys.length]][1]; if (opts.indexOf(r) < 0) opts.push(r); } + // детерминированная перестановка + opts = opts.sort(function (a, b) { return ((a.length + qi) % 3) - ((b.length + qi) % 3); }); + d.innerHTML = '
Какому элементу соответствует символ ' + sym + '?
' + + '
' + opts.map(function (o) { return ''; }).join('') + '
' + + '
Счёт: ' + score + ' из ' + total + '
'; + var out = $('p3-drill-out'); + d.querySelectorAll('.c7-d').forEach(function (b) { + b.addEventListener('click', function () { + total++; var ok = b.dataset.o === correct; if (ok) score++; + out.className = 'out ' + (ok ? 'ok' : 'bad'); + out.innerHTML = (ok ? 'Верно! ' : 'Нет. ') + sym + ' — это ' + correct + '. Счёт: ' + score + ' из ' + total; + qi++; + setTimeout(nextQ, 850); + }); + }); + } + nextQ(); + } + } + + W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, { + p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3 + }); + W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {}); +})(window); diff --git a/frontend/textbooks/chemistry_7_ch1.html b/frontend/textbooks/chemistry_7_ch1.html index 3bbf118..baa8e3e 100644 --- a/frontend/textbooks/chemistry_7_ch1.html +++ b/frontend/textbooks/chemistry_7_ch1.html @@ -17,6 +17,7 @@ + @@ -102,11 +103,121 @@ window.PARAS = [ {id:'final1', num:'★', name:'Финал главы', sub:'босс · ачивка', final:true} ]; -window.ACH_LABELS = { start:'Начало главы 1!', final1_tasks:'Глава 1 пройдена!' }; -window.SIDEBARS = { p1:{ title:'Глава 1 · Химия 7', rows:[['Раздел','Первоначальные понятия'],['§§','1–12'],['Лаб/ПР','ЛО 1 · ПР 1']] } }; -window.TIPS = [{ sec:'p1', html:'Глава наполняется содержанием по фазам. Сейчас доступны навигация по параграфам и отметка о прочтении (+10 XP).' }]; +window.ACH_LABELS = { start:'Начало главы 1!', p1_done:'§1 изучен!', p2_done:'§2 изучен!', + pr1_done:'Практическая работа 1 выполнена!', p3_done:'§3 изучен!', final1_tasks:'Глава 1 пройдена!' }; +window.SIDEBARS = { + p1:{ title:'Шпаргалка §1', rows:[['Вещество','то, из чего состоит тело'],['Тело','предмет из вещества'],['Свойства','цвет, запах, плотность, $t_{пл}$…']] }, + p2:{ title:'Шпаргалка §2', rows:[['Чистое','постоянный состав'],['Смесь','2+ вещества'],['Разделение','по различию свойств']] }, + pr1:{ title:'Практическая 1', rows:[['Цель','разделить смесь'],['Соль+песок','раствор → фильтр → выпаривание'],['ТБ','аккуратно с нагревом']] }, + p3:{ title:'Шпаргалка §3', rows:[['Атом','мельчайшая частица'],['Элемент','атомы с одинаковым $Z$'],['Символ','H, O, Fe, Cu…']] } +}; +window.TIPS = [ + { sec:'p1', html:'Тело — это предмет (гвоздь, стакан), а вещество — то, из чего он сделан (железо, стекло). Из одного вещества можно сделать много тел.' }, + { sec:'p2', html:'Способ разделения подбирают по тому, чем различаются вещества смеси: растворимостью, магнитными свойствами, температурой кипения, плотностью.' }, + { sec:'pr1', html:'Соль растворяется в воде, а песок — нет. Сначала отфильтруй песок, потом выпари воду — на дне останется соль.' }, + { sec:'p3', html:'Химический элемент определяется зарядом ядра (числом протонов) — это и есть порядковый номер $Z$.' } +]; -/* Phase 0: заглушки-builder'ы из PARAS (теория и интерактивы добавляются в фазах 1–4). */ +/* ── задачи (тренажёр) ── */ +window.POOLS = { + p1:[ + {q:'Что изучает химия?',opts:['Только движение тел','Вещества, их свойства и превращения','Только живые организмы','Звёзды и планеты'],a:1,ex:'Химия — наука о веществах, их свойствах и превращениях.'}, + {q:'Что из перечисленного — физическое тело (а не вещество)?',opts:['Вода','Медь','Гвоздь','Кислород'],a:2,ex:'Гвоздь — предмет (тело); медь, вода, кислород — вещества.'}, + {q:'«Железо» — это…',opts:['Физическое тело','Вещество','Смесь','Явление'],a:1,ex:'Железо — вещество, из которого можно сделать разные тела.'}, + {q:'Какое из свойств НЕ является свойством вещества?',opts:['Цвет','Плотность','Растворимость','Номер дома'],a:3,ex:'Цвет, плотность, растворимость — свойства вещества; номер дома — нет.'} + ], + p2:[ + {q:'Смесь песка и воды — какая?',opts:['Однородная','Неоднородная','Чистое вещество','Раствор'],a:1,ex:'Песчинки видны и не растворяются — смесь неоднородная.'}, + {q:'Как разделить раствор соли в воде, чтобы получить соль?',opts:['Фильтрованием','Выпариванием','Магнитом','Никак'],a:1,ex:'Вода испаряется при нагревании, соль остаётся.'}, + {q:'Раствор сахара в воде — это…',opts:['Чистое вещество','Однородная смесь','Неоднородная смесь','Простое вещество'],a:1,ex:'Сахар растворён равномерно — однородная смесь (раствор).'}, + {q:'Чем удобно разделить смесь железных опилок и серы?',opts:['Магнитом','Выпариванием','Перегонкой','Фильтрованием'],a:0,ex:'Железо притягивается магнитом, сера — нет.'} + ], + p3:[ + {q:'Атом — это…',opts:['Самое крупное тело','Мельчайшая химически неделимая частица','Смесь веществ','Молекула воды'],a:1,ex:'Атом — мельчайшая химически неделимая частица вещества.'}, + {q:'Химический элемент — это…',opts:['Любая частица','Вид атомов с одинаковым зарядом ядра','Молекула','Смесь атомов'],a:1,ex:'Элемент определяется зарядом ядра (числом протонов).'}, + {q:'Каков порядковый номер $Z$ кислорода?',hint:'число протонов в ядре',unit:'',a:8,ex:'У кислорода Z = 8.'}, + {q:'Какой символ у железа?',opts:['Fe','Ir','F','Zn'],a:0,ex:'Железо — Fe (от лат. ferrum).'} + ] +}; + +/* ── вспомогательные конструкторы контента ── */ +function rememberBox(items){ + return '
' + +' Запомни!
    ' + +items.map(function(t){return '
  • '+t+'
  • ';}).join('')+'
'; +} +function qList(items){ + return '
Вопросы и задания
    ' + +items.map(function(t){return '
  1. '+t+'
  2. ';}).join('')+'
'; +} +function wgt(title, inner){ + return '
'+title+'
'+inner+'
'; +} + +/* ── BUILDERS: реальные (Волна 1) + заглушки для остальных ── */ +function build_p1(){ + document.getElementById('p1-body').innerHTML = + '
§ 1 · Химия 7

Химия — наука о веществах

' + +'
С чего начинается химия: чем тело отличается от вещества и как химики изучают вещества.
' + +'
веществотелосвойства
' + +makeCard('theory','Вещества и тела','§1','

Химия — наука о веществах, их свойствах и превращениях. Физическое тело — это предмет (гвоздь, стакан, ложка). Вещество — то, из чего состоит тело (железо, стекло, вода).

' + +'
Из одного вещества можно изготовить много разных тел: из стекла — стакан, колбу, линзу. И наоборот, одно тело может состоять из нескольких веществ.
') + +makeCard('theory','Свойства веществ','§1','

Каждое вещество узнают по его свойствам: цвет, запах, агрегатное состояние, плотность, температуры плавления и кипения, растворимость в воде, тепло- и электропроводность.

' + +'

По набору свойств одно вещество отличают от другого: например, медь — красная и проводит ток, а сера — жёлтая и ток не проводит.

') + +makeCard('rule','Наблюдение и эксперимент','§1','

Химия — экспериментальная наука. Главные методы — наблюдение и опыт (эксперимент). Опыты проводят в химической лаборатории, используя оборудование: пробирки, колбы, стаканы, спиртовку, штатив. Работать нужно по правилам техники безопасности.

') + +wgt('Распредели: физическое тело или вещество?','
') + +rememberBox(['Тело — предмет; вещество — то, из чего он сделан.','Вещество узнают по свойствам (цвет, плотность, растворимость…).','Химия изучает вещества опытным путём в лаборатории.']) + +qList(['Чем тело отличается от вещества? Приведи примеры.','Назови 3 свойства, по которым можно отличить медь от серы.','Почему в лаборатории важно соблюдать правила безопасности?']) + +secNav(null,'p2')+readButton('p1'); + wireReadBtn('p1'); +} + +function build_p2(){ + document.getElementById('p2-body').innerHTML = + '
§ 2 · Химия 7

Чистые вещества и смеси

' + +'
Чем чистое вещество отличается от смеси и как смеси разделяют на составные части.
' + +'
смесьоднороднаяразделение
' + +makeCard('theory','Чистое вещество и смесь','§2','

Чистое вещество имеет постоянный состав и постоянные свойства. Смесь состоит из двух и более веществ, и её свойства зависят от состава.

' + +'
Однородные смеси: состав одинаков во всём объёме, частицы не видны (раствор соли или сахара в воде, воздух). Неоднородные: составные части видны (песок в воде, смесь железа и серы).
') + +makeCard('rule','Способы разделения смесей','§2','

Смеси разделяют, используя различие свойств веществ:

' + +'
  • Фильтрование — нерастворимое отделяют от жидкости.
  • Выпаривание — выделяют растворённое вещество, испаряя воду.
  • Отстаивание (делительная воронка) — несмешивающиеся жидкости разной плотности.
  • Перегонка (дистилляция) — вещества с разной температурой кипения.
  • Действие магнитом — если одно вещество притягивается.
') + +makeCard('example','Разделение смеси соли и песка',null,'
1) Добавить воду — соль растворится, песок нет. 2) Фильтрование — песок останется на фильтре. 3) Выпаривание фильтрата — получим чистую соль.
') + +wgt('Подбери способ разделения смеси','
') + +rememberBox(['Чистое вещество — постоянный состав и свойства.','Смеси бывают однородные и неоднородные.','Способ разделения выбирают по различию свойств веществ.']) + +qList(['Приведи пример однородной и неоднородной смеси.','Как разделить смесь воды и растительного масла?','Почему смесь соли и песка нельзя разделить только фильтрованием?']) + +secNav('p1','pr1')+readButton('p2'); + wireReadBtn('p2'); +} + +function build_pr1(){ + document.getElementById('pr1-body').innerHTML = + '
Практическая работа 1

Знакомство с лабораторией. Разделение смесей

' + +'
Познакомиться с лабораторным оборудованием и разделить неоднородную смесь на чистые вещества.
' + +makeCard('lab','Оборудование и порядок работы',null,'

Оборудование: стакан, стеклянная палочка, воронка с фильтром, фарфоровая чашка, спиртовка, штатив.

' + +'
  1. Рассмотри смесь поваренной соли и песка — она неоднородная.
  2. Пересыпь смесь в стакан, добавь воды и размешай — соль растворится.
  3. Профильтруй смесь: песок останется на фильтре, раствор соли пройдёт.
  4. Выпари фильтрат в фарфоровой чашке — вода испарится, останется соль.
  5. Сделай вывод: какие свойства позволили разделить смесь.
' + +'
Со спиртовкой и горячей чашкой работай осторожно; не пробуй вещества на вкус.
') + +wgt('Тренажёр: выбери способ разделения','
') + +secNav('p2','p3')+readButton('pr1'); + wireReadBtn('pr1'); +} + +function build_p3(){ + document.getElementById('p3-body').innerHTML = + '
§ 3 · Химия 7

Атомы. Химические элементы

' + +'
Из каких мельчайших частиц состоят вещества и что такое химический элемент.
' + +'
атомэлементсимвол
' + +makeCard('theory','Атомы','§3','

Все вещества состоят из мельчайших частиц. Атом — мельчайшая химически неделимая частица вещества. Атомы очень малы: их нельзя увидеть даже в обычный микроскоп.

') + +makeCard('theory','Химические элементы','§3','

Химический элемент — это вид атомов с одинаковым зарядом ядра. Каждый элемент имеет название и символ (знак): водород — $\\text{H}$, кислород — $\\text{O}$, железо — $\\text{Fe}$. Порядковый номер элемента $Z$ равен числу протонов в ядре его атомов.

' + +'
Известно более 100 химических элементов. В земной коре больше всего атомов кислорода и кремния, а во Вселенной — водорода.
') + +wgt('Каталог элементов: клик → номер, название, $A_r$','
Выбери элемент, чтобы увидеть его характеристики.
') + +wgt('Тренажёр: символ → название элемента','
') + +rememberBox(['Атом — мельчайшая химически неделимая частица.','Химический элемент — вид атомов с одинаковым зарядом ядра.','У каждого элемента есть символ и порядковый номер $Z$.']) + +qList(['Чем атом отличается от химического элемента?','Запиши символы водорода, кислорода, железа и меди.','Что показывает порядковый номер элемента?']) + +secNav('pr1','p4')+readButton('p3'); + wireReadBtn('p3'); +} + +/* заглушки для ещё не наполненных § (фазы — следующие волны) */ (function(){ var P = window.PARAS, B = {}; function ph(p, prev, next){ @@ -116,7 +227,7 @@ window.TIPS = [{ sec:'p1', html:'Глава наполняется содерж '
' + p.num + ' · Химия 7

' + p.name + '

' + '
Содержание этого ' + (p.final ? 'раздела' : 'параграфа') + ' готовится.
' + makeCard('theory', p.name, p.num, - '

Скоро здесь появятся теория, наглядные SVG-схемы, молекулярные модели и интерактивные тренажёры по теме «' + p.name + '». Пока доступна навигация по главе' + (p.final ? '.' : ' и отметка о прочтении.') + '

') + '

Скоро здесь появятся теория, наглядные SVG-схемы и интерактивные тренажёры по теме «' + p.name + '». Пока доступна навигация по главе' + (p.final ? '.' : ' и отметка о прочтении.') + '

') + secNav(prev, next) + (p.final ? '' : readButton(p.id)); if (!p.final) wireReadBtn(p.id); }; @@ -126,6 +237,12 @@ window.TIPS = [{ sec:'p1', html:'Глава наполняется содерж } window.BUILDERS = B; })(); + +/* переопределяем заглушки реальными builder'ами Волны 1 */ +window.BUILDERS.p1 = build_p1; +window.BUILDERS.p2 = build_p2; +window.BUILDERS.pr1 = build_pr1; +window.BUILDERS.p3 = build_p3; From f7d27ecb91050f954efee89d45a29cfeab2b8f71 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:26:17 +0300 Subject: [PATCH 05/11] =?UTF-8?q?feat(chemistry7):=20Phase=201=20=D0=92?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B0=202=20=E2=80=94=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=201,=20=C2=A7=C2=A74=E2=80=936?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §4 Относительная атомная масса (весы атомов: во сколько раз тяжелее), §5 Молекулы и простые вещества (галерея молекул O2/O3/H2/N2 шариками), §6 Сложные вещества (классификатор простое/сложное + галерея H2O/CO2/CH4/NH3). Теория, тренажёры задач. Тест: 8/8 pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/chemistry7-page.test.js | 12 ++++ frontend/js/chem7_ch1_widgets.js | 84 ++++++++++++++++++++++++- frontend/textbooks/chemistry_7_ch1.html | 82 +++++++++++++++++++++++- 3 files changed, 174 insertions(+), 4 deletions(-) diff --git a/backend/tests/chemistry7-page.test.js b/backend/tests/chemistry7-page.test.js index c34fb1d..c2ead93 100644 --- a/backend/tests/chemistry7-page.test.js +++ b/backend/tests/chemistry7-page.test.js @@ -83,6 +83,18 @@ test('ch1 Волна 1: интерактивы §1–§3 + ПР1 монтиру assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); }); +test('ch1 Волна 2: интерактивы §4–§6 монтируются без ошибок', async () => { + const { doc, errors } = await loadDom('chemistry_7_ch1.html'); + doc.defaultView.goTo('p4'); await wait(100); + assert.ok(doc.querySelector('#p4-bal #p4-a'), 'весы атомов §4'); + doc.defaultView.goTo('p5'); await wait(100); + assert.ok(doc.querySelector('#p5-gal svg'), 'галерея молекул §5'); + doc.defaultView.goTo('p6'); await wait(100); + assert.ok(doc.querySelector('#p6-cls .c7-chip'), 'классификатор простое/сложное §6'); + assert.ok(doc.querySelector('#p6-gal svg'), 'галерея сложных веществ §6'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + test('ch1: переход к §9 и финалу строит заглушку без ошибок', async () => { const { doc, errors } = await loadDom('chemistry_7_ch1.html'); doc.defaultView.goTo('p9'); await wait(80); diff --git a/frontend/js/chem7_ch1_widgets.js b/frontend/js/chem7_ch1_widgets.js index b435934..3fdcf6a 100644 --- a/frontend/js/chem7_ch1_widgets.js +++ b/frontend/js/chem7_ch1_widgets.js @@ -154,8 +154,90 @@ } } + /* ── Волна 2 ── */ + + /* §4 — «весы атомов»: во сколько раз один атом тяжелее другого */ + var ABEL = ['H','C','N','O','Na','Mg','Al','S','Cl','Ca','Fe','Cu','Zn','Ag']; + function mount_p4() { + var m = $('p4-bal'); if (!m || m._built) return; m._built = 1; + function opts(sel){ return ABEL.map(function(e){ var ar=C().arOf?C().arOf(e):''; return ''; }).join(''); } + m.innerHTML = '
' + +'
'; + function upd(){ + var a=$('p4-a').value, b=$('p4-b').value, ara=+C().arOf(a), arb=+C().arOf(b); + var out=$('p4-out'); + if(!ara||!arb){ out.textContent='—'; return; } + var big=Math.max(ara,arb), sm=Math.min(ara,arb), k=big/sm; + var heavier=ara>=arb?a:b, lighter=ara>=arb?b:a; + out.className='out ok'; + out.innerHTML='A_r('+a+')='+ara+', A_r('+b+')='+arb+'
' + +(ara===arb?('Атомы '+a+' и '+b+' имеют одинаковую массу.') + :('Атом '+heavier+' тяжелее атома '+lighter+' в '+(Math.round(k*100)/100).toString().replace('.',',')+' раз.'))+'
'; + } + $('p4-a').addEventListener('change',upd); $('p4-b').addEventListener('change',upd); upd(); + } + + /* рисуем молекулу как набор шариков-атомов */ + var COL = { H:'#cbd5e1', O:'#ef4444', N:'#3b82f6', C:'#334155', S:'#eab308', Cl:'#22c55e', Fe:'#b45309', Na:'#a78bfa' }; + var RAD = { H:11, O:16, N:15, C:16, S:18, Cl:17, Fe:18, Na:17 }; + function molBalls(atoms) { + // atoms: [[el,n],...]; рисуем в ряд + var balls = [], cx = 26; + atoms.forEach(function(pair){ for(var i=0;i' + + ''+el+''; + x += r + 8; + }); + return ''+svg+''; + } + function molCard(name, formula, atoms, note) { + return '
' + +'
'+esc(name)+' · '+(C().formula?C().formula(formula):formula)+'
' + + molBalls(atoms) + +'
'+esc(note)+'
'; + } + + /* §5 — галерея простых веществ */ + function mount_p5() { + var m = $('p5-gal'); if (!m || m._built) return; m._built = 1; + m.innerHTML = '
' + + molCard('Водород','H2',[['H',2]],'2 атома H — двухатомная молекула') + + molCard('Кислород','O2',[['O',2]],'2 атома O') + + molCard('Озон','O3',[['O',3]],'3 атома O — тоже простое вещество') + + molCard('Азот','N2',[['N',2]],'2 атома N') + + '
Во всех молекулах — атомы одного элемента → это простые вещества. Кислород $\\text{O}_2$ и озон $\\text{O}_3$ образованы одним элементом, но это разные простые вещества.
'; + if (W.chem8RenderMath) try { W.chem8RenderMath(m); } catch(e){} + } + + /* §6 — классификатор простое/сложное + галерея сложных веществ */ + function mount_p6() { + var c = $('p6-cls'); + if (c) classifier(c, { + buckets: ['Простое вещество', 'Сложное вещество'], + items: [ + { t:'O₂', b:0 }, { t:'H₂O', b:1 }, { t:'Fe', b:0 }, { t:'CO₂', b:1 }, + { t:'N₂', b:0 }, { t:'NH₃', b:1 }, { t:'S', b:0 }, { t:'CH₄', b:1 } + ] + }); + var g = $('p6-gal'); + if (g && !g._built) { g._built = 1; + g.innerHTML = '
' + + molCard('Вода','H2O',[['O',1],['H',2]],'2 элемента: H и O') + + molCard('Углекислый газ','CO2',[['C',1],['O',2]],'2 элемента: C и O') + + molCard('Метан','CH4',[['C',1],['H',4]],'2 элемента: C и H') + + molCard('Аммиак','NH3',[['N',1],['H',3]],'2 элемента: N и H') + + '
В каждой молекуле — атомы разных элементов → это сложные вещества.
'; + if (W.chem8RenderMath) try { W.chem8RenderMath(g); } catch(e){} + } + } + W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, { - p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3 + p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3, + p4: mount_p4, p5: mount_p5, p6: mount_p6 }); W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {}); })(window); diff --git a/frontend/textbooks/chemistry_7_ch1.html b/frontend/textbooks/chemistry_7_ch1.html index baa8e3e..1e48637 100644 --- a/frontend/textbooks/chemistry_7_ch1.html +++ b/frontend/textbooks/chemistry_7_ch1.html @@ -104,18 +104,25 @@ window.PARAS = [ ]; window.ACH_LABELS = { start:'Начало главы 1!', p1_done:'§1 изучен!', p2_done:'§2 изучен!', - pr1_done:'Практическая работа 1 выполнена!', p3_done:'§3 изучен!', final1_tasks:'Глава 1 пройдена!' }; + pr1_done:'Практическая работа 1 выполнена!', p3_done:'§3 изучен!', + p4_done:'§4 изучен!', p5_done:'§5 изучен!', p6_done:'§6 изучен!', final1_tasks:'Глава 1 пройдена!' }; window.SIDEBARS = { p1:{ title:'Шпаргалка §1', rows:[['Вещество','то, из чего состоит тело'],['Тело','предмет из вещества'],['Свойства','цвет, запах, плотность, $t_{пл}$…']] }, p2:{ title:'Шпаргалка §2', rows:[['Чистое','постоянный состав'],['Смесь','2+ вещества'],['Разделение','по различию свойств']] }, pr1:{ title:'Практическая 1', rows:[['Цель','разделить смесь'],['Соль+песок','раствор → фильтр → выпаривание'],['ТБ','аккуратно с нагревом']] }, - p3:{ title:'Шпаргалка §3', rows:[['Атом','мельчайшая частица'],['Элемент','атомы с одинаковым $Z$'],['Символ','H, O, Fe, Cu…']] } + p3:{ title:'Шпаргалка §3', rows:[['Атом','мельчайшая частица'],['Элемент','атомы с одинаковым $Z$'],['Символ','H, O, Fe, Cu…']] }, + p4:{ title:'Шпаргалка §4', rows:[['$A_r$','во сколько раз тяжелее'],['Эталон','$1/12$ массы $^{12}$C'],['Пример','$A_r(\\text{O})=16$']] }, + p5:{ title:'Шпаргалка §5', rows:[['Молекула','частица из атомов'],['Простое','1 элемент: $O_2$, $H_2$'],['Атомность','$O_2$, $O_3$']] }, + p6:{ title:'Шпаргалка §6', rows:[['Сложное','разные элементы'],['Примеры','$H_2O$, $CO_2$, $NH_3$'],['Состав','можно разложить']] } }; window.TIPS = [ { sec:'p1', html:'Тело — это предмет (гвоздь, стакан), а вещество — то, из чего он сделан (железо, стекло). Из одного вещества можно сделать много тел.' }, { sec:'p2', html:'Способ разделения подбирают по тому, чем различаются вещества смеси: растворимостью, магнитными свойствами, температурой кипения, плотностью.' }, { sec:'pr1', html:'Соль растворяется в воде, а песок — нет. Сначала отфильтруй песок, потом выпари воду — на дне останется соль.' }, - { sec:'p3', html:'Химический элемент определяется зарядом ядра (числом протонов) — это и есть порядковый номер $Z$.' } + { sec:'p3', html:'Химический элемент определяется зарядом ядра (числом протонов) — это и есть порядковый номер $Z$.' }, + { sec:'p4', html:'$A_r$ показывает, во сколько раз масса атома больше $1/12$ массы атома углерода-12. $A_r(\\text{H})=1$, $A_r(\\text{O})=16$, $A_r(\\text{Fe})=56$.' }, + { sec:'p5', html:'Простое вещество — атомы одного элемента ($O_2$, $Fe$). Кислород $O_2$ и озон $O_3$ — разные простые вещества одного и того же элемента.' }, + { sec:'p6', html:'Сложное вещество образовано атомами разных элементов ($H_2O$ — водород и кислород) и может быть разложено на простые.' } ]; /* ── задачи (тренажёр) ── */ @@ -137,6 +144,24 @@ window.POOLS = { {q:'Химический элемент — это…',opts:['Любая частица','Вид атомов с одинаковым зарядом ядра','Молекула','Смесь атомов'],a:1,ex:'Элемент определяется зарядом ядра (числом протонов).'}, {q:'Каков порядковый номер $Z$ кислорода?',hint:'число протонов в ядре',unit:'',a:8,ex:'У кислорода Z = 8.'}, {q:'Какой символ у железа?',opts:['Fe','Ir','F','Zn'],a:0,ex:'Железо — Fe (от лат. ferrum).'} + ], + p4:[ + {q:'Что показывает относительная атомная масса $A_r$?',opts:['Массу атома в граммах','Во сколько раз масса атома больше $1/12$ массы атома $^{12}$C','Число протонов','Число молекул'],a:1,ex:'$A_r$ — сравнение массы атома с $1/12$ массы атома углерода-12.'}, + {q:'$A_r(\\text{S})=32$, $A_r(\\text{O})=16$. Во сколько раз атом серы тяжелее атома кислорода?',hint:'$32/16$',unit:'раза',a:2,ex:'$32/16=2$.'}, + {q:'$A_r(\\text{Mg})=24$, $A_r(\\text{C})=12$. Во сколько раз атом магния тяжелее атома углерода?',hint:'$24/12$',unit:'раза',a:2,ex:'$24/12=2$.'}, + {q:'Относительная атомная масса $A_r$…',opts:['Измеряется в граммах','Безразмерна','Измеряется в литрах','Равна числу нейтронов'],a:1,ex:'$A_r$ — безразмерная величина сравнения.'} + ], + p5:[ + {q:'Молекула $\\text{O}_2$ образует…',opts:['Сложное вещество','Простое вещество','Смесь','Раствор'],a:1,ex:'Атомы одного элемента — простое вещество.'}, + {q:'Простое вещество — это вещество, образованное…',opts:['Атомами одного элемента','Атомами разных элементов','Только молекулами','Только смесями'],a:0,ex:'Простое — один вид атомов.'}, + {q:'Кислород $\\text{O}_2$ и озон $\\text{O}_3$ — это…',opts:['Одно и то же вещество','Разные простые вещества одного элемента','Сложные вещества','Смесь газов'],a:1,ex:'Один элемент — кислород, но разные простые вещества.'}, + {q:'Сколько атомов в молекуле озона $\\text{O}_3$?',hint:'смотри индекс',unit:'',a:3,ex:'Озон $O_3$ — три атома кислорода.'} + ], + p6:[ + {q:'Вода $\\text{H}_2\\text{O}$ — это…',opts:['Простое вещество','Сложное вещество','Смесь','Атом'],a:1,ex:'Атомы разных элементов (H и O) — сложное вещество.'}, + {q:'Сложное вещество образовано…',opts:['Атомами одного элемента','Атомами разных элементов','Только смесью','Одним атомом'],a:1,ex:'Сложное — разные элементы.'}, + {q:'Какое из веществ — простое?',opts:['$\\text{H}_2\\text{O}$','$\\text{CO}_2$','$\\text{N}_2$','$\\text{NH}_3$'],a:2,ex:'$N_2$ — один элемент (азот).'}, + {q:'Сколько разных химических элементов в молекуле метана $\\text{CH}_4$?',hint:'углерод и водород',unit:'',a:2,ex:'C и H — два элемента.'} ] }; @@ -217,6 +242,54 @@ function build_p3(){ wireReadBtn('p3'); } +function build_p4(){ + document.getElementById('p4-body').innerHTML = + '
§ 4 · Химия 7

Относительная атомная масса химических элементов

' + +'
$A_r(\\text{O}) = 16$
' + +'
Как сравнивают массы атомов, которые невозможно взвесить по отдельности.
' + +'
$A_r$а.е.м.
' + +makeCard('theory','Зачем нужна относительная масса','§4','

Атомы очень малы, их масса в граммах — крошечное число, с которым неудобно работать. Поэтому массы атомов сравнивают между собой. За единицу принята $\\tfrac{1}{12}$ массы атома углерода-12 — это атомная единица массы (а.е.м.).

') + +makeCard('rule','Относительная атомная масса','§4','
Относительная атомная масса $A_r$ показывает, во сколько раз масса атома больше $\\tfrac{1}{12}$ массы атома углерода-12. Это безразмерная величина.
' + +'

Значения $A_r$ берут из периодической таблицы: $A_r(\\text{H})=1$, $A_r(\\text{C})=12$, $A_r(\\text{O})=16$, $A_r(\\text{S})=32$, $A_r(\\text{Fe})=56$.

') + +makeCard('example','Сравнение масс атомов',null,'

Во сколько раз атом серы тяжелее атома кислорода?

$\\dfrac{A_r(\\text{S})}{A_r(\\text{O})}=\\dfrac{32}{16}=2$ раза.
') + +wgt('Весы атомов: во сколько раз тяжелее','
') + +rememberBox(['$A_r$ — безразмерная величина.','Эталон — $1/12$ массы атома углерода-12.','Значения $A_r$ берут из периодической таблицы.']) + +qList(['Что принято за атомную единицу массы?','Во сколько раз атом магния ($A_r=24$) тяжелее атома углерода ($A_r=12$)?','Почему $A_r$ не имеет единиц измерения?']) + +secNav('p3','p5')+readButton('p4'); + wireReadBtn('p4'); +} + +function build_p5(){ + document.getElementById('p5-body').innerHTML = + '
§ 5 · Химия 7

Молекулы. Простые вещества

' + +'
Из чего состоят вещества и какие вещества называют простыми.
' + +'
молекулапростое вещество
' + +makeCard('theory','Молекулы','§5','

Молекула — частица, состоящая из нескольких атомов, соединённых вместе. Многие вещества состоят из молекул: кислород — из молекул $\\text{O}_2$, вода — из молекул $\\text{H}_2\\text{O}$.

') + +makeCard('theory','Простые вещества','§5','
Простое вещество образовано атомами одного химического элемента: $\\text{O}_2$, $\\text{H}_2$, $\\text{N}_2$, а также металлы — железо $\\text{Fe}$, медь $\\text{Cu}$.
' + +'

Один элемент может образовать несколько простых веществ. Например, элемент кислород образует кислород $\\text{O}_2$ и озон $\\text{O}_3$ — это разные простые вещества.

') + +wgt('Молекулы простых веществ','
') + +rememberBox(['Молекула — частица из нескольких атомов.','Простое вещество — атомы одного элемента.','Один элемент может давать разные простые вещества ($O_2$ и $O_3$).']) + +qList(['Что такое молекула? Приведи пример.','Чем простое вещество отличается от химического элемента?','Назови два простых вещества, образованных элементом кислород.']) + +secNav('p4','p6')+readButton('p5'); + wireReadBtn('p5'); +} + +function build_p6(){ + document.getElementById('p6-body').innerHTML = + '
§ 6 · Химия 7

Сложные вещества

' + +'
Какие вещества называют сложными и чем они отличаются от простых.
' + +'
сложное веществосостав
' + +makeCard('theory','Сложные вещества','§6','
Сложное вещество образовано атомами разных химических элементов: вода $\\text{H}_2\\text{O}$ (водород и кислород), углекислый газ $\\text{CO}_2$, метан $\\text{CH}_4$, аммиак $\\text{NH}_3$.
' + +'

Сложные вещества можно разложить на простые. Например, при разложении воды получаются простые вещества — водород и кислород.

') + +makeCard('example','Простое или сложное?',null,'

$\\text{N}_2$ — атомы только азота → простое. $\\text{NH}_3$ — атомы азота и водорода → сложное.

') + +wgt('Распредели: простое или сложное вещество?','
') + +wgt('Молекулы сложных веществ','
') + +rememberBox(['Сложное вещество — атомы разных элементов.','Сложные вещества можно разложить на простые.','Число элементов в формуле подскажет: 1 — простое, 2+ — сложное.']) + +qList(['Чем сложное вещество отличается от простого?','Из каких элементов состоит углекислый газ $\\text{CO}_2$?','Приведи пример разложения сложного вещества на простые.']) + +secNav('p5','p7')+readButton('p6'); + wireReadBtn('p6'); +} + /* заглушки для ещё не наполненных § (фазы — следующие волны) */ (function(){ var P = window.PARAS, B = {}; @@ -243,6 +316,9 @@ window.BUILDERS.p1 = build_p1; window.BUILDERS.p2 = build_p2; window.BUILDERS.pr1 = build_pr1; window.BUILDERS.p3 = build_p3; +window.BUILDERS.p4 = build_p4; +window.BUILDERS.p5 = build_p5; +window.BUILDERS.p6 = build_p6; From 4a424505a807c64670236dff22581721e480749f Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:27:58 +0300 Subject: [PATCH 06/11] =?UTF-8?q?feat(admin/health):=20System=20Health=20L?= =?UTF-8?q?evel=202=20=E2=80=94=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20HTTP-=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backend/src/utils/metrics.js: лёгкие in-memory метрики (сброс при рестарте) — всего запросов, req/min (скользящее окно), латентность avg/p50/p95/p99, разбивка по статусам 2xx/3xx/4xx/5xx, топ маршрутов по частоте/латентности/ ошибкам (группировка по шаблону route.path, не по URL). server.js: middleware (на /api, по res 'finish') пишет латентность и статус. adminController.getMetrics + GET /api/admin/metrics (под admin-auth). admin.js: health-страница переведена на refreshHealth/renderHealth (Level 1) + секция «Метрики запросов»: карточки req/min/всего/avg/p95/p99/5xx, цветная полоса статусов, топ медленных/частых/ошибочных маршрутов. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/controllers/adminController.js | 8 +- backend/src/routes/admin.js | 1 + backend/src/server.js | 13 ++ backend/src/utils/metrics.js | 67 ++++++++ frontend/js/admin/admin.js | 177 ++++++++++++++++----- 5 files changed, 224 insertions(+), 42 deletions(-) create mode 100644 backend/src/utils/metrics.js diff --git a/backend/src/controllers/adminController.js b/backend/src/controllers/adminController.js index 75648ed..9c6ce00 100644 --- a/backend/src/controllers/adminController.js +++ b/backend/src/controllers/adminController.js @@ -713,6 +713,12 @@ function getHealth(_req, res) { }); } +/* ── GET /api/admin/metrics — метрики HTTP-запросов (Level 2) ──────────── */ +const metrics = require('../utils/metrics'); +function getMetrics(_req, res) { + res.json(metrics.snapshot()); +} + /* ── Topics CRUD ─────────────────────────────────────────────────────── */ function getTopics(req, res) { const { subject_id } = req.query; @@ -802,7 +808,7 @@ module.exports = { getUsers, updateRole, getUserSessions, getAllSessions, getSessionDetail, clearUserSessions, deleteSession, updateUser, banUser, deleteUser, getFeatures, updateFeatures, getFreeStudentFeatures, updateFreeStudentFeatures, - getAuditLog, clearAuditLog, getErrorLog, clearErrorLog, getHealth, + getAuditLog, clearAuditLog, getErrorLog, clearErrorLog, getHealth, getMetrics, getTopics, createTopic, updateTopic, deleteTopic, broadcast, }; diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 3875227..b78d83c 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -38,6 +38,7 @@ router.delete('/error-log', ctrl.clearErrorLog); /* System health */ router.get('/health', ctrl.getHealth); +router.get('/metrics', ctrl.getMetrics); /* Topics CRUD */ router.get('/topics', ctrl.getTopics); diff --git a/backend/src/server.js b/backend/src/server.js index 3fb3b07..b3f6b55 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -138,6 +138,19 @@ const { requireFeature } = require('./middleware/features'); app.use('/api/classroom', rateLimit({ windowMs: 60_000, max: 6000, message: 'Слишком много запросов' })); app.use('/api', rateLimit({ windowMs: 60_000, max: 600, message: 'Слишком много запросов, подождите минуту' })); +/* ── Request metrics (System Health Level 2) ── */ +const metrics = require('./utils/metrics'); +app.use((req, res, next) => { + if (!req.originalUrl.startsWith('/api')) return next(); + const start = process.hrtime.bigint(); + res.on('finish', () => { + const ms = Number(process.hrtime.bigint() - start) / 1e6; + const route = (req.baseUrl || '') + (req.route && req.route.path ? req.route.path : ''); + metrics.record(req.method, route || req.path || '(unmatched)', res.statusCode, ms); + }); + next(); +}); + /* ── Routes ── */ app.use('/api/auth', authRoutes); app.use('/api/subjects', subjectRoutes); diff --git a/backend/src/utils/metrics.js b/backend/src/utils/metrics.js new file mode 100644 index 0000000..8c96fe4 --- /dev/null +++ b/backend/src/utils/metrics.js @@ -0,0 +1,67 @@ +'use strict'; +/* + * Лёгкие in-memory метрики HTTP-запросов для System Health (Level 2). + * Сбрасываются при перезапуске процесса. Память ограничена кольцевыми буферами. + * Группировка по шаблону маршрута (req.route.path), а не по конкретному URL — + * чтобы /api/biochem/molecules/:id не плодил тысячи ключей. + */ +const MAX_LAT = 3000; // окно последних латентностей для перцентилей +const WINDOW_MS = 60_000; // окно для req/min + +let total = 0; +let totalServerErrors = 0; +const statusClasses = { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0 }; +const routeStats = new Map(); // "METHOD /path" -> { count, totalMs, maxMs, errors } +const latencies = []; // последние латентности (мс) +const recentTs = []; // метки времени последних запросов (для req/min) +const startedAt = Date.now(); + +function record(method, route, status, ms) { + total++; + const cls = Math.floor(status / 100) + 'xx'; + if (statusClasses[cls] !== undefined) statusClasses[cls]++; + if (status >= 500) totalServerErrors++; + + const key = method + ' ' + route; + let r = routeStats.get(key); + if (!r) { r = { count: 0, totalMs: 0, maxMs: 0, errors: 0 }; routeStats.set(key, r); } + r.count++; r.totalMs += ms; if (ms > r.maxMs) r.maxMs = ms; + if (status >= 400) r.errors++; + + latencies.push(ms); + if (latencies.length > MAX_LAT) latencies.shift(); + + const now = Date.now(); + recentTs.push(now); + const cutoff = now - WINDOW_MS; + while (recentTs.length && recentTs[0] < cutoff) recentTs.shift(); +} + +function _pct(sorted, p) { + if (!sorted.length) return 0; + return sorted[Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length))]; +} + +function snapshot() { + const now = Date.now(); + const cutoff = now - WINDOW_MS; + let reqPerMin = 0; + for (let i = recentTs.length - 1; i >= 0 && recentTs[i] >= cutoff; i--) reqPerMin++; + const sorted = [...latencies].sort((a, b) => a - b); + const avg = latencies.length ? latencies.reduce((s, x) => s + x, 0) / latencies.length : 0; + const routes = [...routeStats.entries()].map(([k, r]) => ({ + route: k, count: r.count, avgMs: r.totalMs / r.count, maxMs: r.maxMs, errors: r.errors, + })); + return { + total, totalServerErrors, statusClasses, + reqPerMin, + avgMs: avg, p50: _pct(sorted, 50), p95: _pct(sorted, 95), p99: _pct(sorted, 99), + routeCount: routeStats.size, + sinceMs: now - startedAt, + topBusy: [...routes].sort((a, b) => b.count - a.count).slice(0, 8), + topSlow: [...routes].sort((a, b) => b.avgMs - a.avgMs).slice(0, 8), + topErrors: routes.filter(r => r.errors > 0).sort((a, b) => b.errors - a.errors).slice(0, 8), + }; +} + +module.exports = { record, snapshot }; diff --git a/frontend/js/admin/admin.js b/frontend/js/admin/admin.js index 71637f3..057057a 100644 --- a/frontend/js/admin/admin.js +++ b/frontend/js/admin/admin.js @@ -299,55 +299,150 @@ window.clearErrorLog = clearErrorLog; /* ═══ SYSTEM HEALTH ════════════════════════════════════════════════ */ + let _healthLive = false, _healthTimer = null; + async function loadHealth() { const el = document.getElementById('health-content'); el.innerHTML = LS.skeleton(3, 'row'); + await refreshHealth(); + setupHealthTimer(); + } + + function setupHealthTimer() { + if (_healthTimer) { clearInterval(_healthTimer); _healthTimer = null; } + if (_healthLive) { + _healthTimer = setInterval(() => { + const el = document.getElementById('health-content'); + if (!el || !el.offsetParent) { clearInterval(_healthTimer); _healthTimer = null; return; } + refreshHealth(); + }, 5000); + } + } + + async function refreshHealth() { try { - const h = await LS.api('/api/admin/health'); - const fmtBytes = b => b > 1e9 ? (b/1e9).toFixed(1)+' GB' : b > 1e6 ? (b/1e6).toFixed(1)+' MB' : (b/1e3).toFixed(0)+' KB'; - const fmtUp = s => { const d=Math.floor(s/86400), hr=Math.floor(s%86400/3600), m=Math.floor(s%3600/60); return d>0?`${d}d ${hr}h`:hr>0?`${hr}h ${m}m`:`${m}m`; }; - el.innerHTML = ` -
-
-
${fmtUp(h.uptime)}
-
Uptime
+ const [h, m] = await Promise.all([ + LS.api('/api/admin/health'), + LS.api('/api/admin/metrics').catch(() => null), + ]); + renderHealth(h, m); + } catch (e) { const el = document.getElementById('health-content'); if (el) el.innerHTML = `
${esc(e.message)}
`; } + } + + function renderHealth(h, m) { + const el = document.getElementById('health-content'); + if (!el) return; + const fmtBytes = b => !b ? '0' : b > 1e9 ? (b/1e9).toFixed(1)+' GB' : b > 1e6 ? (b/1e6).toFixed(1)+' MB' : (b/1e3).toFixed(0)+' KB'; + const fmtUp = s => { const d=Math.floor(s/86400), hr=Math.floor(s%86400/3600), mm=Math.floor(s%3600/60); return d>0?`${d}d ${hr}h`:hr>0?`${hr}h ${mm}m`:`${mm}m`; }; + const stColor = h.status==='critical'?'var(--pink)':h.status==='warning'?'#facc15':'var(--green)'; + const stLabel = h.status==='critical'?'Критическое состояние':h.status==='warning'?'Требует внимания':'Всё в норме'; + const memPct = Math.round((h.memPercent||0)*100); + const memCol = memPct>92?'var(--pink)':memPct>80?'#facc15':'var(--green)'; + const lag = h.eventLoopLagMs||0, lagCol = lag>200?'var(--pink)':lag>70?'#facc15':'var(--green)'; + const card = (val, label, col) => `
+
${val}
+
${label}
`; + const maxRows = Math.max(1, ...(h.db.tables||[]).map(t=>t.rows)); + + // ── секция метрик запросов (Level 2) ── + let metricsHtml = ''; + if (m) { + const sc = m.statusClasses||{}, tot = Math.max(1, (sc['2xx']||0)+(sc['3xx']||0)+(sc['4xx']||0)+(sc['5xx']||0)); + const seg = (n,col)=> n>0?`
`:''; + const routeRows = (arr, valFn, valLabel) => (arr&&arr.length)? arr.map(r=>`
+
${esc(r.route)}
+
${valFn(r)}
`).join('') + : `
нет данных
`; + const lagP = (v)=> (v||0)>200?'var(--pink)':(v||0)>70?'#facc15':'var(--text-1)'; + metricsHtml = ` +
+
Метрики запросов (с рестарта · ${fmtUp(Math.floor((m.sinceMs||0)/1000))})
+
+ ${card(m.reqPerMin, 'Req/min', 'var(--green)')} + ${card((m.total||0).toLocaleString('ru'), 'Всего')} + ${card((m.avgMs||0).toFixed(0)+' мс', 'Средн.')} + ${card((m.p95||0).toFixed(0)+' мс', 'p95', lagP(m.p95))} + ${card((m.p99||0).toFixed(0)+' мс', 'p99', lagP(m.p99))} + ${card(sc['5xx']||0, '5xx', (sc['5xx']||0)>0?'var(--pink)':'var(--green)')}
-
-
${fmtBytes(h.db.sizeBytes)}
-
База данных
+
Статусы: 2xx ${sc['2xx']||0} · 3xx ${sc['3xx']||0} · 4xx ${sc['4xx']||0} · 5xx ${sc['5xx']||0}
+
+ ${seg(sc['2xx'],'#4ade80')}${seg(sc['3xx'],'#60a5fa')}${seg(sc['4xx'],'#facc15')}${seg(sc['5xx'],'#f87171')}
-
-
${fmtBytes(h.uploads.sizeBytes)}
-
Файлы
-
-
-
${h.recentErrors}
-
Ошибок за 24ч
-
-
-
-
-
Платформа
- - - - - - - -
Node.js${h.node}
OS${h.platform}
CPU ядра${h.cpus}
RAM использовано${fmtBytes(h.memory.rss)}
RAM heap${fmtBytes(h.memory.heapUsed)}
RAM свободно${fmtBytes(h.freeMem)} / ${fmtBytes(h.totalMem)}
-
-
-
Данные
- - - - - -
Пользователей${h.db.totalUsers}
Всего сессий${h.db.totalSessions}
Сессий сегодня${h.db.todaySessions}
Вопросов в базе${h.db.totalQuestions}
+
+
Самые медленные
${routeRows(m.topSlow, r=>r.avgMs.toFixed(0)+' мс')}
+
Самые частые
${routeRows(m.topBusy, r=>r.count.toLocaleString('ru'))}
+ ${(m.topErrors&&m.topErrors.length)?`
Маршруты с ошибками
${routeRows(m.topErrors, r=>r.errors+' ош. / '+r.count)}
`:''}
`; - } catch (e) { el.innerHTML = `
${esc(e.message)}
`; } + } + + el.innerHTML = ` +
+
+
+
${stLabel}
+
${h.reasons&&h.reasons.length?h.reasons.map(esc).join(' · '):'Все показатели в пределах нормы'}
+
+ +
+ +
+ ${card(fmtUp(h.uptime), 'Uptime', 'var(--green)')} + ${card(fmtBytes(h.db.sizeBytes), 'База данных', 'var(--violet)')} + ${card(fmtBytes(h.uploads.sizeBytes), 'Файлы')} + ${card(h.recentErrors, 'Ошибок 24ч', h.recentErrors>0?'var(--pink)':'var(--green)')} + ${card((h.sse?h.sse.connections:0), 'SSE онлайн', 'var(--green)')} + ${card(memPct+'%', 'Память', memCol)} + ${card(lag.toFixed(0)+' мс', 'Event-loop', lagCol)} + ${card(h.disk?fmtBytes(h.disk.freeBytes)+' своб.':'—', 'Диск')} +
+ +
+
+
Платформа
+ + + + + + + + + ${h.disk?``:''} +
Версия${esc(h.version||'?')} ${h.commit?`${esc(h.commit)}`:''}
Окружение${esc(h.env||'?')} · PID ${h.pid||'?'}
Node.js${esc(h.node)}
OS / арх${esc(h.platform)} ${esc(h.arch||'')}
CPU ядра · load${h.cpus} · ${(h.loadavg||[0]).map(x=>x.toFixed(2)).join(' / ')}
RAM rss / heap${fmtBytes(h.memory.rss)} / ${fmtBytes(h.memory.heapUsed)}
RAM система${fmtBytes(h.totalMem-h.freeMem)} / ${fmtBytes(h.totalMem)}
Диск${fmtBytes(h.disk.freeBytes)} своб. / ${fmtBytes(h.disk.totalBytes)}
+
+
+
Данные и активность
+ + + + + + + +
Пользователей${h.db.totalUsers}
Онлайн (SSE)${h.sse?h.sse.users:0} польз. · ${h.sse?h.sse.connections:0} соед.
Всего сессий${h.db.totalSessions}
Сессий сегодня${h.db.todaySessions}
Вопросов в базе${h.db.totalQuestions}
WAL${fmtBytes(h.db.walBytes||0)}
+
+
+ + ${metricsHtml} + +
+
Крупнейшие таблицы БД
+ ${(h.db.tables||[]).map(t=>`
+
${esc(t.name)}
+
+
${t.rows.toLocaleString('ru')}
+
`).join('')} +
`; + + const btn = document.getElementById('health-live-btn'); + if (btn) btn.addEventListener('click', () => { + _healthLive = !_healthLive; + btn.textContent = _healthLive ? '● Live' : '○ Авто-обновление'; + btn.style.color = _healthLive ? 'var(--green)' : 'var(--text-3)'; + setupHealthTimer(); + }); } /* ════════════════════════════════════════════════ From bc50a0d9f1f5c592ff61debe308a73d51d6c72e3 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:29:40 +0300 Subject: [PATCH 07/11] =?UTF-8?q?feat(chemistry7):=20Phase=201=20=D0=92?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B0=203=20=E2=80=94=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=201,=20=C2=A7=C2=A77=E2=80=939?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §7 Химическая формула (разбор формулы на состав, индекс/коэффициент), §8 Относительная молекулярная масса (калькулятор M_r через Chem8.molarMass), §9 Валентность (конструктор формулы по валентности через НОК индексов). Теория, тренажёры задач. Тест: 9/9 pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/chemistry7-page.test.js | 13 ++++ frontend/js/chem7_ch1_widgets.js | 66 ++++++++++++++++++- frontend/textbooks/chemistry_7_ch1.html | 86 ++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 4 deletions(-) diff --git a/backend/tests/chemistry7-page.test.js b/backend/tests/chemistry7-page.test.js index c2ead93..955788b 100644 --- a/backend/tests/chemistry7-page.test.js +++ b/backend/tests/chemistry7-page.test.js @@ -95,6 +95,19 @@ test('ch1 Волна 2: интерактивы §4–§6 монтируются assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); }); +test('ch1 Волна 3: интерактивы §7–§9 монтируются и считают', async () => { + const { doc, errors } = await loadDom('chemistry_7_ch1.html'); + doc.defaultView.goTo('p7'); await wait(100); + assert.ok(doc.querySelector('#p7-out'), 'парсер формулы §7'); + assert.match(doc.querySelector('#p7-out').textContent, /4/, 'H2SO4 → 4 атома O в разборе'); + doc.defaultView.goTo('p8'); await wait(100); + assert.match(doc.querySelector('#p8-out').textContent, /100/, 'M_r(CaCO3)=100'); + doc.defaultView.goTo('p9'); await wait(100); + assert.ok(doc.querySelector('#p9-bld #p9-a'), 'конструктор валентности §9'); + assert.match(doc.querySelector('#p9-bout').textContent, /Al/, 'формула по валентности построена'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + test('ch1: переход к §9 и финалу строит заглушку без ошибок', async () => { const { doc, errors } = await loadDom('chemistry_7_ch1.html'); doc.defaultView.goTo('p9'); await wait(80); diff --git a/frontend/js/chem7_ch1_widgets.js b/frontend/js/chem7_ch1_widgets.js index 3fdcf6a..ef6b5e6 100644 --- a/frontend/js/chem7_ch1_widgets.js +++ b/frontend/js/chem7_ch1_widgets.js @@ -235,9 +235,73 @@ } } + /* ── Волна 3 ── */ + + /* §7 — разбор химической формулы на состав */ + function mount_p7() { + var inp = $('p7-in'), out = $('p7-out'), go = $('p7-go'); if (!inp || inp._built) return; inp._built = 1; + function calc() { + var f = inp.value.trim(), cnt = C().elementCounts ? C().elementCounts(f) : null; + if (!cnt || !Object.keys(cnt).length) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу. Проверь символы элементов (например, H2SO4).'; return; } + var els = Object.keys(cnt), tot = els.reduce(function (s, e) { return s + cnt[e]; }, 0); + out.className = 'out ok'; + out.innerHTML = '' + (C().formula ? C().formula(f) : f) + '
' + + 'Элементов: ' + els.length + ' (' + (els.length === 1 ? 'простое' : 'сложное') + ' вещество)
' + + els.map(function (e) { return e + ': ' + cnt[e] + ' ' + (cnt[e] === 1 ? 'атом' : 'атома(ов)'); }).join('
') + + '
Всего атомов в формуле: ' + tot + '
'; + } + go.addEventListener('click', calc); + inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); }); + document.querySelectorAll('.p7-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); }); + calc(); + } + + /* §8 — калькулятор относительной молекулярной массы M_r */ + function mount_p8() { + var inp = $('p8-in'), out = $('p8-out'), go = $('p8-go'); if (!inp || inp._built) return; inp._built = 1; + function calc() { + var f = inp.value.trim(), cnt = C().elementCounts ? C().elementCounts(f) : null, mr = C().molarMass ? C().molarMass(f) : NaN; + if (!cnt || isNaN(mr)) { out.className = 'out bad'; out.textContent = 'Не удалось разобрать формулу.'; return; } + out.className = 'out ok'; + out.innerHTML = 'M_r(' + f + ') = ' + C().fmt(mr) + '
' + + Object.keys(cnt).map(function (e) { return e + ': A_r=' + (C().arOf ? C().arOf(e) : '?') + ' × ' + cnt[e]; }).join('  |  ') + + '
Σ = ' + Object.keys(cnt).map(function (e) { return (C().arOf ? C().arOf(e) : '?') + '·' + cnt[e]; }).join(' + ') + ' = ' + C().fmt(mr) + '
'; + } + go.addEventListener('click', calc); + inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') calc(); }); + document.querySelectorAll('.p8-ex').forEach(function (b) { b.addEventListener('click', function () { inp.value = b.dataset.f; calc(); }); }); + calc(); + } + + /* §9 — конструктор формулы по валентности (НОК индексов) */ + function gcd(a, b) { return b ? gcd(b, a % b) : a; } + var VA = [ ['Na', 1], ['K', 1], ['H', 1], ['Mg', 2], ['Ca', 2], ['Zn', 2], ['Cu', 2], ['Al', 3], ['C', 4] ]; + var VB = [ ['O', 2], ['Cl', 1], ['S', 2] ]; + function mount_p9() { + var m = $('p9-bld'); if (!m || m._built) return; m._built = 1; + function optA(){ return VA.map(function(e,i){ return ''; }).join(''); } + function optB(){ return VB.map(function(e,i){ return ''; }).join(''); } + m.innerHTML = '
' + +'
'; + function upd() { + var a = VA[+$('p9-a').value], b = VB[+$('p9-b').value]; + var lcm = a[1] * b[1] / gcd(a[1], b[1]); + var ia = lcm / a[1], ib = lcm / b[1]; + var raw = a[0] + (ia > 1 ? ia : '') + b[0] + (ib > 1 ? ib : ''); + var out = $('p9-bout'); out.className = 'out ok'; + out.innerHTML = 'Валентности: ' + a[0] + ' = ' + 'I'.repeat(a[1]).replace('IIII','IV') + ', ' + b[0] + ' = ' + 'I'.repeat(b[1]) + '
' + + 'Наименьшее общее кратное валентностей = ' + lcm + '
' + + 'Индексы: ' + a[0] + ' → ' + ia + ', ' + b[0] + ' → ' + ib + '
' + + 'Формула: ' + (C().formula ? C().formula(raw) : raw) + '
' + + 'Проверка: ' + ia + '·' + a[1] + ' = ' + ib + '·' + b[1] + ' = ' + lcm + ' единиц валентности — совпало.
'; + } + $('p9-a').addEventListener('change', upd); $('p9-b').addEventListener('change', upd); upd(); + } + W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, { p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3, - p4: mount_p4, p5: mount_p5, p6: mount_p6 + p4: mount_p4, p5: mount_p5, p6: mount_p6, + p7: mount_p7, p8: mount_p8, p9: mount_p9 }); W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {}); })(window); diff --git a/frontend/textbooks/chemistry_7_ch1.html b/frontend/textbooks/chemistry_7_ch1.html index 1e48637..07caf6d 100644 --- a/frontend/textbooks/chemistry_7_ch1.html +++ b/frontend/textbooks/chemistry_7_ch1.html @@ -105,7 +105,8 @@ window.PARAS = [ window.ACH_LABELS = { start:'Начало главы 1!', p1_done:'§1 изучен!', p2_done:'§2 изучен!', pr1_done:'Практическая работа 1 выполнена!', p3_done:'§3 изучен!', - p4_done:'§4 изучен!', p5_done:'§5 изучен!', p6_done:'§6 изучен!', final1_tasks:'Глава 1 пройдена!' }; + p4_done:'§4 изучен!', p5_done:'§5 изучен!', p6_done:'§6 изучен!', + p7_done:'§7 изучен!', p8_done:'§8 изучен!', p9_done:'§9 изучен!', final1_tasks:'Глава 1 пройдена!' }; window.SIDEBARS = { p1:{ title:'Шпаргалка §1', rows:[['Вещество','то, из чего состоит тело'],['Тело','предмет из вещества'],['Свойства','цвет, запах, плотность, $t_{пл}$…']] }, p2:{ title:'Шпаргалка §2', rows:[['Чистое','постоянный состав'],['Смесь','2+ вещества'],['Разделение','по различию свойств']] }, @@ -113,7 +114,10 @@ window.SIDEBARS = { p3:{ title:'Шпаргалка §3', rows:[['Атом','мельчайшая частица'],['Элемент','атомы с одинаковым $Z$'],['Символ','H, O, Fe, Cu…']] }, p4:{ title:'Шпаргалка §4', rows:[['$A_r$','во сколько раз тяжелее'],['Эталон','$1/12$ массы $^{12}$C'],['Пример','$A_r(\\text{O})=16$']] }, p5:{ title:'Шпаргалка §5', rows:[['Молекула','частица из атомов'],['Простое','1 элемент: $O_2$, $H_2$'],['Атомность','$O_2$, $O_3$']] }, - p6:{ title:'Шпаргалка §6', rows:[['Сложное','разные элементы'],['Примеры','$H_2O$, $CO_2$, $NH_3$'],['Состав','можно разложить']] } + p6:{ title:'Шпаргалка §6', rows:[['Сложное','разные элементы'],['Примеры','$H_2O$, $CO_2$, $NH_3$'],['Состав','можно разложить']] }, + p7:{ title:'Шпаргалка §7', rows:[['Индекс','число атомов'],['Коэффициент','число молекул'],['Состав','качеств. + количеств.']] }, + p8:{ title:'Шпаргалка §8', rows:[['$M_r$','$=\\sum A_r$'],['$M_r(H_2O)$','18'],['$M_r(H_2SO_4)$','98']] }, + p9:{ title:'Шпаргалка §9', rows:[['Валентность','число связей'],['H — I, O — II',''],['Формула','по НОК валентностей']] } }; window.TIPS = [ { sec:'p1', html:'Тело — это предмет (гвоздь, стакан), а вещество — то, из чего он сделан (железо, стекло). Из одного вещества можно сделать много тел.' }, @@ -122,7 +126,10 @@ window.TIPS = [ { sec:'p3', html:'Химический элемент определяется зарядом ядра (числом протонов) — это и есть порядковый номер $Z$.' }, { sec:'p4', html:'$A_r$ показывает, во сколько раз масса атома больше $1/12$ массы атома углерода-12. $A_r(\\text{H})=1$, $A_r(\\text{O})=16$, $A_r(\\text{Fe})=56$.' }, { sec:'p5', html:'Простое вещество — атомы одного элемента ($O_2$, $Fe$). Кислород $O_2$ и озон $O_3$ — разные простые вещества одного и того же элемента.' }, - { sec:'p6', html:'Сложное вещество образовано атомами разных элементов ($H_2O$ — водород и кислород) и может быть разложено на простые.' } + { sec:'p6', html:'Сложное вещество образовано атомами разных элементов ($H_2O$ — водород и кислород) и может быть разложено на простые.' }, + { sec:'p7', html:'Индекс относится к атому/группе слева от него. В $H_2SO_4$: 2 атома H, 1 атом S, 4 атома O. Коэффициент (число перед формулой) — это число молекул.' }, + { sec:'p8', html:'$M_r$ — сумма $A_r$ всех атомов формулы. $M_r(\\text{CO}_2)=12+2\\cdot16=44$.' }, + { sec:'p9', html:'Кислород в соединениях имеет валентность II, водород — I. Зная их, можно определить валентность другого элемента и составить формулу по НОК.' } ]; /* ── задачи (тренажёр) ── */ @@ -162,6 +169,24 @@ window.POOLS = { {q:'Сложное вещество образовано…',opts:['Атомами одного элемента','Атомами разных элементов','Только смесью','Одним атомом'],a:1,ex:'Сложное — разные элементы.'}, {q:'Какое из веществ — простое?',opts:['$\\text{H}_2\\text{O}$','$\\text{CO}_2$','$\\text{N}_2$','$\\text{NH}_3$'],a:2,ex:'$N_2$ — один элемент (азот).'}, {q:'Сколько разных химических элементов в молекуле метана $\\text{CH}_4$?',hint:'углерод и водород',unit:'',a:2,ex:'C и H — два элемента.'} + ], + p7:[ + {q:'Что показывает индекс в химической формуле?',opts:['Число молекул','Число атомов элемента','Массу вещества','Заряд'],a:1,ex:'Индекс — число атомов элемента (или группы) в формуле.'}, + {q:'Сколько атомов кислорода в формуле $\\text{H}_2\\text{SO}_4$?',hint:'индекс при O',unit:'',a:4,ex:'4 атома кислорода.'}, + {q:'Сколько всего атомов в молекуле воды $\\text{H}_2\\text{O}$?',hint:'2 H + 1 O',unit:'',a:3,ex:'2 + 1 = 3 атома.'}, + {q:'Число, стоящее перед формулой (коэффициент), показывает…',opts:['Число атомов','Число молекул','Валентность','Массу'],a:1,ex:'Коэффициент — число молекул вещества.'} + ], + p8:[ + {q:'Чему равна $M_r(\\text{H}_2\\text{O})$?',hint:'$2\\cdot1+16$',unit:'',a:18,ex:'$M_r=18$.'}, + {q:'Чему равна $M_r(\\text{CO}_2)$?',hint:'$12+2\\cdot16$',unit:'',a:44,ex:'$12+32=44$.'}, + {q:'Чему равна $M_r(\\text{H}_2\\text{SO}_4)$?',hint:'$2+32+4\\cdot16$',unit:'',a:98,ex:'$2+32+64=98$.'}, + {q:'Чему равна $M_r(\\text{CaCO}_3)$?',hint:'$40+12+3\\cdot16$',unit:'',a:100,ex:'$40+12+48=100$.'} + ], + p9:[ + {q:'За единицу валентности принята валентность атома…',opts:['кислорода','водорода','углерода','железа'],a:1,ex:'Единица валентности — валентность водорода (I).'}, + {q:'Какова валентность кислорода в соединениях?',hint:'постоянная',unit:'',a:2,ex:'Кислород почти всегда двухвалентен (II).'}, + {q:'Какова валентность хлора в молекуле $\\text{HCl}$?',hint:'равна числу атомов H',unit:'',a:1,ex:'Хлор соединён с 1 атомом H → валентность I.'}, + {q:'Какова формула оксида алюминия (Al — III, O — II)?',opts:['AlO','Al₂O₃','AlO₂','Al₃O₂'],a:1,ex:'НОК(3,2)=6 → индексы 2 и 3 → Al₂O₃.'} ] }; @@ -290,6 +315,58 @@ function build_p6(){ wireReadBtn('p6'); } +function build_p7(){ + document.getElementById('p7-body').innerHTML = + '
§ 7 · Химия 7

Химическая формула

' + +'
$\\text{H}_2\\text{O}$
' + +'
Как с помощью формулы записывают, из каких атомов и в каком числе состоит вещество.
' + +'
индекскоэффициентсостав
' + +makeCard('theory','Что показывает формула','§7','

Химическая формула показывает состав вещества: качественный (из каких элементов) и количественный (сколько атомов каждого элемента).

' + +'
Индекс — маленькое число справа внизу: показывает число атомов элемента ($\\text{H}_2\\text{O}$ — 2 атома H, 1 атом O). Коэффициент — число перед формулой: показывает число молекул ($2\\text{H}_2\\text{O}$ — две молекулы воды).
') + +makeCard('example','Чтение формул',null,'

$\\text{H}_2\\text{O}$ читают «аш-два-о», $\\text{H}_2\\text{SO}_4$ — «аш-два-эс-о-четыре», $\\text{CH}_4$ — «цэ-аш-четыре».

') + +wgt('Разбор формулы на состав','
' + +'
' + +'
Введи формулу и нажми «Разобрать».
') + +rememberBox(['Индекс — число атомов; стоит справа внизу.','Коэффициент — число молекул; стоит перед формулой.','Скобки: индекс умножает всё внутри — Ca(OH)₂ = 1 Ca, 2 O, 2 H.']) + +qList(['Чем индекс отличается от коэффициента?','Сколько атомов каждого элемента в $\\text{H}_3\\text{PO}_4$?','Что означает запись $3\\text{H}_2\\text{O}$?']) + +secNav('p6','p8')+readButton('p7'); + wireReadBtn('p7'); +} + +function build_p8(){ + document.getElementById('p8-body').innerHTML = + '
§ 8 · Химия 7

Относительная молекулярная масса

' + +'
$M_r=\\sum A_r$
' + +'
Как по формуле рассчитать, во сколько раз молекула тяжелее эталона.
' + +'
$M_r$$\\sum A_r$
' + +makeCard('rule','Относительная молекулярная масса','§8','
Относительная молекулярная масса $M_r$ равна сумме относительных атомных масс всех атомов в формуле. Это безразмерная величина: она показывает, во сколько раз молекула тяжелее $\\tfrac{1}{12}$ массы атома углерода-12.
') + +makeCard('example','Расчёт $M_r$',null,'

$M_r(\\text{H}_2\\text{O})=2\\cdot A_r(\\text{H})+A_r(\\text{O})=2\\cdot1+16=18$.

$M_r(\\text{H}_2\\text{SO}_4)=2\\cdot1+32+4\\cdot16=98$.
') + +wgt('Калькулятор $M_r$ по формуле','
' + +'
' + +'
Введи формулу и нажми «Вычислить».
') + +rememberBox(['$M_r$ — сумма $A_r$ всех атомов формулы.','$M_r$ безразмерна.','Индекс умножает $A_r$ соответствующего элемента.']) + +qList(['Вычисли $M_r(\\text{Na}_2\\text{CO}_3)$.','Во сколько раз молекула воды тяжелее $\\tfrac{1}{12}$ атома углерода-12?','Вычисли $M_r(\\text{Fe}_2\\text{O}_3)$.']) + +secNav('p7','p9')+readButton('p8'); + wireReadBtn('p8'); +} + +function build_p9(){ + document.getElementById('p9-body').innerHTML = + '
§ 9 · Химия 7

Валентность

' + +'
H — I, O — II
' + +'
Сколько связей образует атом и как по валентности составить формулу вещества.
' + +'
валентностьНОК
' + +makeCard('theory','Что такое валентность','§9','

Валентность — способность атома соединяться с определённым числом атомов других элементов. За единицу валентности принята валентность атома водорода (I). В $\\text{HCl}$ хлор одновалентен, в $\\text{H}_2\\text{O}$ кислород двухвалентен, в $\\text{NH}_3$ азот трёхвалентен, в $\\text{CH}_4$ углерод четырёхвалентен.

') + +makeCard('rule','Составление формул по валентности','§9','

Кислород в соединениях обычно имеет валентность II, водород — I. Зная валентности, формулу составляют так, чтобы суммарное число единиц валентности обоих элементов совпало (по наименьшему общему кратному).

' + +'
Постоянные валентности: H — I; O — II; Na, K — I; Mg, Ca, Zn — II; Al — III.
') + +makeCard('example','Оксид алюминия',null,'

Al — III, O — II. НОК(3, 2) = 6. Индексы: Al → 6/3 = 2, O → 6/2 = 3.

Формула: $\\text{Al}_2\\text{O}_3$.
') + +wgt('Конструктор формулы по валентности','
') + +rememberBox(['Валентность — число связей атома; единица — валентность водорода.','Кислород — II, водород — I (постоянные).','Формулу составляют по НОК валентностей.']) + +qList(['Определи валентность серы в $\\text{SO}_2$ (O — II).','Составь формулу соединения кальция (II) с кислородом.','Чему равна валентность азота в $\\text{NH}_3$?']) + +secNav('p8','p10')+readButton('p9'); + wireReadBtn('p9'); +} + /* заглушки для ещё не наполненных § (фазы — следующие волны) */ (function(){ var P = window.PARAS, B = {}; @@ -319,6 +396,9 @@ window.BUILDERS.p3 = build_p3; window.BUILDERS.p4 = build_p4; window.BUILDERS.p5 = build_p5; window.BUILDERS.p6 = build_p6; +window.BUILDERS.p7 = build_p7; +window.BUILDERS.p8 = build_p8; +window.BUILDERS.p9 = build_p9; From 13cbbacc1fd0d91caaf572ad81185283695b7045 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:33:55 +0300 Subject: [PATCH 08/11] =?UTF-8?q?feat(chemistry7):=20Phase=201=20=D0=92?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B0=204=20=E2=80=94=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=201=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20(=C2=A7=C2=A710=E2=80=9312=20+=20=D0=9B=D0=9E1?= =?UTF-8?q?=20+=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §10 Физические и химические явления (детектор признаков реакции), ЛО1 Признаки реакций (опыты с признаками), §11 Закон сохранения массы (весы сохранения массы), §12 Составление уравнений (балансировщик через Chem8.equationBalancer), финал главы (6 интегрированных боссов + шпаргалка). Глава 1 «Первоначальные химические понятия» наполнена полностью (12§). Тесты: 10/10 chem7 pass; полный прогон 156/159 (3 — известный baseline Auth). Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/chemistry7-page.test.js | 17 +++- frontend/js/chem7_ch1_widgets.js | 73 +++++++++++++- frontend/textbooks/chemistry_7_ch1.html | 125 +++++++++++++++++++++++- 3 files changed, 209 insertions(+), 6 deletions(-) diff --git a/backend/tests/chemistry7-page.test.js b/backend/tests/chemistry7-page.test.js index 955788b..067e6d1 100644 --- a/backend/tests/chemistry7-page.test.js +++ b/backend/tests/chemistry7-page.test.js @@ -108,7 +108,22 @@ test('ch1 Волна 3: интерактивы §7–§9 монтируются assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); }); -test('ch1: переход к §9 и финалу строит заглушку без ошибок', async () => { +test('ch1 Волна 4: §10–§12 + ЛО1 + финал главы монтируются', async () => { + const { doc, errors } = await loadDom('chemistry_7_ch1.html'); + doc.defaultView.goTo('p10'); await wait(100); + assert.ok(doc.querySelector('#p10-signs #p10-signs-go'), 'детектор признаков §10'); + doc.defaultView.goTo('lo1'); await wait(100); + assert.ok(doc.querySelector('#lo1-signs #lo1-signs-go'), 'детектор признаков ЛО1'); + doc.defaultView.goTo('p11'); await wait(100); + assert.ok(doc.querySelector('#p11-bal svg'), 'весы сохранения массы §11'); + doc.defaultView.goTo('p12'); await wait(120); + assert.ok(doc.querySelector('#p12-mount').childElementCount > 0, 'балансировщик §12'); + doc.defaultView.goTo('final1'); await wait(120); + assert.ok(doc.querySelectorAll('#navDotsfinal1 .nav-dot').length >= 6, 'боссы финала главы'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + +test('ch1: переход к §9 и финалу строит без ошибок', async () => { const { doc, errors } = await loadDom('chemistry_7_ch1.html'); doc.defaultView.goTo('p9'); await wait(80); assert.ok(doc.querySelector('#p9-body .para-hero'), 'para-hero §9'); diff --git a/frontend/js/chem7_ch1_widgets.js b/frontend/js/chem7_ch1_widgets.js index ef6b5e6..a9776d6 100644 --- a/frontend/js/chem7_ch1_widgets.js +++ b/frontend/js/chem7_ch1_widgets.js @@ -298,10 +298,79 @@ $('p9-a').addEventListener('change', upd); $('p9-b').addEventListener('change', upd); upd(); } + /* ── Волна 4 ── */ + + /* §10 / ЛО1 — детектор признаков химической реакции */ + var DEMOS = [ + { name: 'Нагревание малахита', signs: ['изменение цвета: зелёный → чёрный', 'выделение газа (водяной пар и углекислый газ)'] }, + { name: 'Сливание растворов CuSO₄ и NaOH', signs: ['образование осадка (голубой)', 'изменение цвета раствора'] }, + { name: 'Горение серы', signs: ['выделение света и тепла (пламя)', 'появление резкого запаха'] }, + { name: 'Добавление соды в уксус', signs: ['выделение газа (пузырьки)'] } + ]; + function mount_signs(mountId) { + var m = $(mountId); if (!m || m._built) return; m._built = 1; + var idx = 0; + function render() { + m.innerHTML = '
' + + '
' + + '
Выбери опыт и нажми «Провести опыт».
'; + $(mountId + '-pick').addEventListener('change', function (e) { idx = +e.target.value; m._built = 0; render(); }); + $(mountId + '-go').addEventListener('click', function () { + var d = DEMOS[idx], out = $(mountId + '-out'); out.className = 'out ok'; + out.innerHTML = 'Наблюдаемые признаки реакции:
' + + d.signs.map(function (s) { return '
✓ ' + esc(s) + '
'; }).join('') + + '
Эти признаки указывают, что произошла химическая реакция — образовались новые вещества.
'; + }); + } + render(); + } + function mount_p10() { mount_signs('p10-signs'); } + function mount_lo1() { mount_signs('lo1-signs'); } + + /* §11 — весы сохранения массы */ + function mount_p11() { + var m = $('p11-bal'); if (!m || m._built) return; m._built = 1; + var mixed = false; + function scale(level) { + // level: 0 = равновесие + return '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '100 г' + + '100 г' + + '' + (mixed ? 'продукты' : 'реагенты') + '' + + '' + (mixed ? 'продукты' : 'реагенты') + '' + + ''; + } + function render() { + m.innerHTML = scale() + + '
' + (mixed + ? 'После реакции: осадок Cu(OH)₂ + раствор Na₂SO₄. Стрелка весов не сдвинулась — масса сохранилась (100 г = 100 г).' + : 'До реакции: раствор CuSO₄ + раствор NaOH, общая масса 100 г.') + '
' + + ''; + $('p11-mix').addEventListener('click', function () { mixed = !mixed; m._built = 0; render(); }); + } + render(); + } + + /* §12 — балансировщик уравнений (переиспользуем Chem8.equationBalancer) */ + function mount_p12() { + var pick = $('p12-pick'), mount = $('p12-mount'); if (!pick || pick._built || !C().equationBalancer) return; pick._built = 1; + function build() { var parts = pick.value.split('|'); C().equationBalancer(mount, { skeleton: parts[0], solution: parts[1].split(',').map(Number) }); } + pick.addEventListener('change', build); build(); + } + W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, { p1: mount_p1, p2: mount_p2, pr1: mount_pr1, p3: mount_p3, p4: mount_p4, p5: mount_p5, p6: mount_p6, - p7: mount_p7, p8: mount_p8, p9: mount_p9 + p7: mount_p7, p8: mount_p8, p9: mount_p9, + p10: mount_p10, lo1: mount_lo1, p11: mount_p11 }); - W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {}); + W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, { p12: mount_p12 }); })(window); diff --git a/frontend/textbooks/chemistry_7_ch1.html b/frontend/textbooks/chemistry_7_ch1.html index 07caf6d..14df0b9 100644 --- a/frontend/textbooks/chemistry_7_ch1.html +++ b/frontend/textbooks/chemistry_7_ch1.html @@ -106,7 +106,9 @@ window.PARAS = [ window.ACH_LABELS = { start:'Начало главы 1!', p1_done:'§1 изучен!', p2_done:'§2 изучен!', pr1_done:'Практическая работа 1 выполнена!', p3_done:'§3 изучен!', p4_done:'§4 изучен!', p5_done:'§5 изучен!', p6_done:'§6 изучен!', - p7_done:'§7 изучен!', p8_done:'§8 изучен!', p9_done:'§9 изучен!', final1_tasks:'Глава 1 пройдена!' }; + p7_done:'§7 изучен!', p8_done:'§8 изучен!', p9_done:'§9 изучен!', + p10_done:'§10 изучен!', lo1_done:'Лабораторный опыт 1 выполнен!', p11_done:'§11 изучен!', p12_done:'§12 изучен!', + final1_tasks:'Глава 1 пройдена! Вы — Мастер первоначальных понятий!' }; window.SIDEBARS = { p1:{ title:'Шпаргалка §1', rows:[['Вещество','то, из чего состоит тело'],['Тело','предмет из вещества'],['Свойства','цвет, запах, плотность, $t_{пл}$…']] }, p2:{ title:'Шпаргалка §2', rows:[['Чистое','постоянный состав'],['Смесь','2+ вещества'],['Разделение','по различию свойств']] }, @@ -117,7 +119,12 @@ window.SIDEBARS = { p6:{ title:'Шпаргалка §6', rows:[['Сложное','разные элементы'],['Примеры','$H_2O$, $CO_2$, $NH_3$'],['Состав','можно разложить']] }, p7:{ title:'Шпаргалка §7', rows:[['Индекс','число атомов'],['Коэффициент','число молекул'],['Состав','качеств. + количеств.']] }, p8:{ title:'Шпаргалка §8', rows:[['$M_r$','$=\\sum A_r$'],['$M_r(H_2O)$','18'],['$M_r(H_2SO_4)$','98']] }, - p9:{ title:'Шпаргалка §9', rows:[['Валентность','число связей'],['H — I, O — II',''],['Формула','по НОК валентностей']] } + p9:{ title:'Шпаргалка §9', rows:[['Валентность','число связей'],['H — I, O — II',''],['Формула','по НОК валентностей']] }, + p10:{ title:'Шпаргалка §10', rows:[['Физическое','форма/состояние'],['Химическое','новые вещества'],['Признаки','цвет, газ, осадок, запах, тепло']] }, + lo1:{ title:'Лаб. опыт 1', rows:[['Цель','наблюдать признаки реакций'],['Признак','новое вещество']] }, + p11:{ title:'Шпаргалка §11', rows:[['Закон','масса сохраняется'],['$m$ реаг.','= $m$ прод.'],['Авторы','Ломоносов, Лавуазье']] }, + p12:{ title:'Шпаргалка §12', rows:[['Уравнивают','коэффициентами'],['Индексы','не трогать'],['Баланс','атомы слева = справа']] }, + final1:{ title:'Финал главы 1', rows:[['§§1–12','все понятия'],['Награда','ачивка + XP']] } }; window.TIPS = [ { sec:'p1', html:'Тело — это предмет (гвоздь, стакан), а вещество — то, из чего он сделан (железо, стекло). Из одного вещества можно сделать много тел.' }, @@ -129,7 +136,12 @@ window.TIPS = [ { sec:'p6', html:'Сложное вещество образовано атомами разных элементов ($H_2O$ — водород и кислород) и может быть разложено на простые.' }, { sec:'p7', html:'Индекс относится к атому/группе слева от него. В $H_2SO_4$: 2 атома H, 1 атом S, 4 атома O. Коэффициент (число перед формулой) — это число молекул.' }, { sec:'p8', html:'$M_r$ — сумма $A_r$ всех атомов формулы. $M_r(\\text{CO}_2)=12+2\\cdot16=44$.' }, - { sec:'p9', html:'Кислород в соединениях имеет валентность II, водород — I. Зная их, можно определить валентность другого элемента и составить формулу по НОК.' } + { sec:'p9', html:'Кислород в соединениях имеет валентность II, водород — I. Зная их, можно определить валентность другого элемента и составить формулу по НОК.' }, + { sec:'p10', html:'Признаки химической реакции: изменение цвета, выделение газа, образование осадка, появление запаха, выделение или поглощение тепла и света.' }, + { sec:'lo1', html:'Если после действия появились новые вещества (изменился цвет, выпал осадок, выделился газ) — произошла химическая реакция.' }, + { sec:'p11', html:'Атомы в реакции не исчезают и не появляются, поэтому масса веществ до реакции равна массе после (М. В. Ломоносов, А. Лавуазье).' }, + { sec:'p12', html:'Уравнивают реакцию только коэффициентами (числами перед формулами). Индексы внутри формул менять нельзя.' }, + { sec:'final1', html:'Собери всё: вещество/смесь, атом/элемент, формула, $M_r$, валентность, признаки реакции, закон сохранения, уравнение.' } ]; /* ── задачи (тренажёр) ── */ @@ -187,6 +199,32 @@ window.POOLS = { {q:'Какова валентность кислорода в соединениях?',hint:'постоянная',unit:'',a:2,ex:'Кислород почти всегда двухвалентен (II).'}, {q:'Какова валентность хлора в молекуле $\\text{HCl}$?',hint:'равна числу атомов H',unit:'',a:1,ex:'Хлор соединён с 1 атомом H → валентность I.'}, {q:'Какова формула оксида алюминия (Al — III, O — II)?',opts:['AlO','Al₂O₃','AlO₂','Al₃O₂'],a:1,ex:'НОК(3,2)=6 → индексы 2 и 3 → Al₂O₃.'} + ], + p10:[ + {q:'Что НЕ является признаком химической реакции?',opts:['Изменение цвета','Выделение газа','Образование осадка','Изменение формы предмета'],a:3,ex:'Изменение формы — физическое изменение, не признак реакции.'}, + {q:'Таяние льда — это явление…',opts:['Химическое','Физическое','Реакция','Горение'],a:1,ex:'Вода не превращается в другое вещество — физическое явление.'}, + {q:'Ржавление железа — это явление…',opts:['Физическое','Химическое','Растворение','Плавление'],a:1,ex:'Образуется новое вещество (ржавчина) — химическое явление.'}, + {q:'Появление резкого запаха при горении серы — это…',opts:['Признак химической реакции','Физическое изменение','Не имеет значения','Изменение формы'],a:0,ex:'Образовался новый газ — признак реакции.'} + ], + p11:[ + {q:'Закон сохранения массы означает, что…',opts:['Масса всегда растёт','Масса реагентов равна массе продуктов','Атомы исчезают','Масса уменьшается'],a:1,ex:'Атомы не исчезают — масса сохраняется.'}, + {q:'При реакции $4$ г водорода с $32$ г кислорода сколько граммов воды образуется?',hint:'$4+32$',unit:'г',a:36,ex:'$4+32=36$ г.'}, + {q:'Сожгли $12$ г углерода, получили $44$ г $\\text{CO}_2$. Сколько граммов кислорода вступило?',hint:'$44-12$',unit:'г',a:32,ex:'$44-12=32$ г.'}, + {q:'Кто открыл закон сохранения массы веществ?',opts:['Д. И. Менделеев','М. В. Ломоносов','И. Ньютон','Архимед'],a:1,ex:'М. В. Ломоносов (и позже А. Лавуазье).'} + ], + p12:[ + {q:'В уравнении $2\\text{H}_2+\\text{O}_2=2\\text{H}_2\\text{O}$ коэффициент перед $\\text{H}_2\\text{O}$ равен…',hint:'смотри на воду',unit:'',a:2,ex:'Коэффициент 2.'}, + {q:'Что можно изменять при уравнивании реакции?',opts:['Только индексы','Только коэффициенты','И индексы, и коэффициенты','Ничего'],a:1,ex:'Уравнивают только коэффициентами.'}, + {q:'В уравнении $3\\text{Fe}+2\\text{O}_2=\\text{Fe}_3\\text{O}_4$ коэффициент перед $\\text{Fe}$ равен…',hint:'железо слева',unit:'',a:3,ex:'Коэффициент 3.'}, + {q:'Если в реакции 2 атома фосфора, сколько их должно быть в продуктах?',hint:'закон сохранения',unit:'',a:2,ex:'Столько же — атомы не исчезают.'} + ], + final1:[ + {q:'$M_r(\\text{H}_2\\text{SO}_4)=?$',hint:'$2+32+64$',unit:'',a:98,ex:'$98$.'}, + {q:'Валентность серы в $\\text{SO}_2$ (кислород — II)?',hint:'$2\\cdot\\text{II}$',unit:'',a:4,ex:'IV.'}, + {q:'Коэффициент перед $\\text{H}_2$ в $2\\text{H}_2+\\text{O}_2=2\\text{H}_2\\text{O}$?',hint:'',unit:'',a:2,ex:'2.'}, + {q:'Сколько атомов кислорода в формуле $\\text{Fe}_3\\text{O}_4$?',hint:'индекс при O',unit:'',a:4,ex:'4.'}, + {q:'$\\text{N}_2$ — простое или сложное вещество?',opts:['Простое','Сложное'],a:0,ex:'Один элемент — простое.'}, + {q:'Сожгли $4$ г серы, получили $8$ г $\\text{SO}_2$. Сколько граммов кислорода вступило?',hint:'$8-4$',unit:'г',a:4,ex:'$8-4=4$ г.'} ] }; @@ -367,6 +405,82 @@ function build_p9(){ wireReadBtn('p9'); } +function build_p10(){ + document.getElementById('p10-body').innerHTML = + '
§ 10 · Химия 7

Явления физические и химические. Признаки химических реакций

' + +'
Чем химическое явление отличается от физического и как распознать химическую реакцию.
' + +'
явлениереакцияпризнаки
' + +makeCard('theory','Физические и химические явления','§10','

При физическом явлении вещество не превращается в другое — меняются лишь форма, размеры или состояние (таяние льда, испарение воды, измельчение мела).

' + +'
При химическом явлении (реакции) из одних веществ образуются другие вещества с новыми свойствами (горение, ржавление, скисание молока).
') + +makeCard('rule','Признаки химических реакций','§10','

О том, что произошла химическая реакция, судят по признакам:

' + +'
  • изменение цвета;
  • выделение газа (пузырьки);
  • образование или растворение осадка;
  • появление запаха;
  • выделение или поглощение тепла и света.
') + +makeCard('example','Нагревание малахита',null,'

Зелёный порошок малахита при нагревании чернеет (образуется $\\text{CuO}$) и выделяет газы. Видны сразу два признака — изменение цвета и выделение газа.

') + +wgt('Детектор признаков реакции','
') + +rememberBox(['Физическое явление — вещество остаётся тем же.','Химическая реакция — образуются новые вещества.','Признаки реакции: цвет, газ, осадок, запах, тепло/свет.']) + +qList(['Приведи примеры физического и химического явлений.','Какие признаки реакции наблюдаются при горении?','Почему ржавление железа — химическое явление?']) + +secNav('p9','lo1')+readButton('p10'); + wireReadBtn('p10'); +} + +function build_lo1(){ + document.getElementById('lo1-body').innerHTML = + '
Лабораторный опыт 1

Признаки протекания химических реакций

' + +'
Пронаблюдать признаки, по которым узнают химическую реакцию.
' + +makeCard('lab','Ход работы',null,'
  1. Прилей к раствору соли меди раствор щёлочи — наблюдай образование осадка и изменение цвета.
  2. Капни на мел (или соду) кислоту — наблюдай выделение газа (пузырьки).
  3. Нагрей выданное вещество — отметь изменение цвета или выделение газа.
  4. Запиши, какой признак реакции наблюдался в каждом опыте, и сделай вывод.
' + +'
Кислоты и щёлочи едкие — не допускай попадания на кожу; нагревай осторожно.
') + +wgt('Определи признаки в опытах','
') + +secNav('p10','p11')+readButton('lo1'); + wireReadBtn('lo1'); +} + +function build_p11(){ + document.getElementById('p11-body').innerHTML = + '
§ 11 · Химия 7

Закон сохранения массы веществ. Химические уравнения

' + +'
$m_{реагентов}=m_{продуктов}$
' + +'
Почему масса веществ в реакции не меняется и как это записывают уравнением.
' + +'
закон сохраненияуравнение
' + +makeCard('theory','Закон сохранения массы','§11','
Закон сохранения массы: масса веществ, вступивших в реакцию, равна массе веществ, образовавшихся в результате реакции. Открыт М. В. Ломоносовым и подтверждён А. Лавуазье.
' + +'

Причина проста: атомы в реакции не исчезают и не появляются — они лишь по-новому соединяются. Поэтому их общее число (и масса) сохраняется.

') + +makeCard('example','Химическое уравнение',null,'

Реакцию записывают уравнением: слева — реагенты, справа — продукты. Например: $2\\text{H}_2+\\text{O}_2=2\\text{H}_2\\text{O}$. Число атомов каждого элемента слева и справа одинаково.

') + +wgt('Весы сохранения массы','
') + +rememberBox(['Масса реагентов = масса продуктов.','Атомы лишь перегруппировываются, их число сохраняется.','Реакцию записывают химическим уравнением.']) + +qList(['Сформулируй закон сохранения массы.','Почему масса не изменяется в ходе реакции?','При сжигании 12 г угля в кислороде получили 44 г углекислого газа. Сколько кислорода вступило в реакцию?']) + +secNav('lo1','p12')+readButton('p11'); + wireReadBtn('p11'); +} + +function build_p12(){ + document.getElementById('p12-body').innerHTML = + '
§ 12 · Химия 7

Составление уравнений химических реакций

' + +'
подбор коэффициентов
' + +'
Как уравнять реакцию, чтобы число атомов слева и справа совпало.
' + +'
коэффициентыбаланс
' + +makeCard('rule','Как уравнять реакцию','§12','
  1. Записать формулы реагентов и продуктов.
  2. Подобрать коэффициенты (числа перед формулами) так, чтобы число атомов каждого элемента слева и справа стало одинаковым.
  3. Проверить баланс по каждому элементу.
' + +'
Менять индексы внутри формул нельзя — это изменило бы сами вещества. Уравнивают только коэффициентами.
') + +makeCard('example','Горение водорода',null,'
$\\text{H}_2+\\text{O}_2 \\to$ ? Ставим коэффициенты: $2\\text{H}_2+\\text{O}_2=2\\text{H}_2\\text{O}$. Слева и справа: 4 атома H и 2 атома O.
') + +'
Балансировщик: расставь коэффициенты
Подбери коэффициенты так, чтобы число атомов каждого элемента совпало слева и справа.
' + +'
' + +rememberBox(['Уравнивают только коэффициентами.','Индексы в формулах не трогают.','После расстановки проверь число атомов каждого элемента.']) + +qList(['Почему при уравнивании нельзя менять индексы?','Уравняй реакцию $\\text{P}+\\text{O}_2\\to\\text{P}_2\\text{O}_5$.','Что показывает коэффициент перед формулой?']) + +secNav('p11','final1')+readButton('p12'); + wireReadBtn('p12'); +} + +function build_final1(){ + document.getElementById('final1-body').innerHTML = + '
Финал главы 1

Босс: первоначальные химические понятия

' + +'
вещество · атом · формула · $M_r$ · валентность · уравнение
' + +'
Шесть интегрированных задач на всё, что изучено в главе. Реши все — получи звание «Мастер первоначальных понятий».
' + +makeCard('rule','Шпаргалка главы 1',null,'
    ' + +'
  • Тело — предмет, вещество — из чего он сделан; смеси разделяют по различию свойств.
  • ' + +'
  • Атом — мельчайшая частица; элемент — атомы с одинаковым $Z$; $A_r$ — относительная атомная масса.
  • ' + +'
  • Простое вещество — 1 элемент, сложное — разные; формула показывает состав.
  • ' + +'
  • $M_r=\\sum A_r$; валентность: H — I, O — II; формула — по НОК валентностей.
  • ' + +'
  • Признаки реакции: цвет, газ, осадок, запах, тепло; масса сохраняется; уравнивают коэффициентами.
') + +'

Реши задачи ниже — за каждую +5 XP, за полный разгром босса — звание и бонус.

' + +secNav('p12',null); +} + /* заглушки для ещё не наполненных § (фазы — следующие волны) */ (function(){ var P = window.PARAS, B = {}; @@ -399,6 +513,11 @@ window.BUILDERS.p6 = build_p6; window.BUILDERS.p7 = build_p7; window.BUILDERS.p8 = build_p8; window.BUILDERS.p9 = build_p9; +window.BUILDERS.p10 = build_p10; +window.BUILDERS.lo1 = build_lo1; +window.BUILDERS.p11 = build_p11; +window.BUILDERS.p12 = build_p12; +window.BUILDERS.final1 = build_final1; From 6a934ca6c61e426ed5b280c90553b9c82477724d Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:36:04 +0300 Subject: [PATCH 09/11] =?UTF-8?q?feat(admin/health):=20System=20Health=20L?= =?UTF-8?q?evel=203=20=E2=80=94=20=D1=82=D1=80=D0=B5=D0=BD=D0=B4=D1=8B=20(?= =?UTF-8?q?=D1=81=D1=8D=D0=BC=D0=BF=D0=BB=D0=B8=D0=BD=D0=B3=20+=20canvas-?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit metrics.js: сэмплинг раз в минуту в кольцевой буфер (cap 24ч, unref) — ts/rss/heapUsed/reqPerMin/reqDelta/err5xx/p95; history() + поле history в snapshot (последние 180 точек). admin.js: секция «Тренды» с 4 мини-графиками (canvas): Память RSS, Запросы/мин, Ошибки 5xx, Латентность p95 — линия + заливка + подписи макс/последнее. Обновляются вместе с live-рефрешем. Проверено: сэмплер пишет, история в snapshot, графики рисуются (на старте — «накопление данных…», далее наполняются). Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/utils/metrics.js | 36 +++++++++++++++++++++++- frontend/js/admin/admin.js | 54 ++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/backend/src/utils/metrics.js b/backend/src/utils/metrics.js index 8c96fe4..7b1acd6 100644 --- a/backend/src/utils/metrics.js +++ b/backend/src/utils/metrics.js @@ -42,6 +42,39 @@ function _pct(sorted, p) { return sorted[Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length))]; } +/* ── Сэмплинг для трендов (Level 3): раз в минуту в кольцевой буфер ─────── */ +const HISTORY_CAP = 1440; // ~24 часа при 1 сэмпле/мин +const samples = []; +let _lastErr5xx = 0, _lastTotal = 0; + +function sample() { + const now = Date.now(); + const cutoff = now - WINDOW_MS; + let rpm = 0; + for (let i = recentTs.length - 1; i >= 0 && recentTs[i] >= cutoff; i--) rpm++; + const sorted = [...latencies].sort((a, b) => a - b); + const cur5xx = statusClasses['5xx']; + const errDelta = Math.max(0, cur5xx - _lastErr5xx); _lastErr5xx = cur5xx; + const reqDelta = Math.max(0, total - _lastTotal); _lastTotal = total; + const mem = process.memoryUsage(); + samples.push({ + ts: now, + rss: mem.rss, + heapUsed: mem.heapUsed, + reqPerMin: rpm, + reqDelta, + err5xx: errDelta, + p95: _pct(sorted, 95), + }); + if (samples.length > HISTORY_CAP) samples.shift(); +} + +const _sampler = setInterval(sample, 60_000); +if (_sampler.unref) _sampler.unref(); // не держим процесс живым +sample(); // стартовая точка сразу + +function history(limit = 180) { return samples.slice(-limit); } + function snapshot() { const now = Date.now(); const cutoff = now - WINDOW_MS; @@ -61,7 +94,8 @@ function snapshot() { topBusy: [...routes].sort((a, b) => b.count - a.count).slice(0, 8), topSlow: [...routes].sort((a, b) => b.avgMs - a.avgMs).slice(0, 8), topErrors: routes.filter(r => r.errors > 0).sort((a, b) => b.errors - a.errors).slice(0, 8), + history: history(180), }; } -module.exports = { record, snapshot }; +module.exports = { record, snapshot, sample, history }; diff --git a/frontend/js/admin/admin.js b/frontend/js/admin/admin.js index 057057a..b024712 100644 --- a/frontend/js/admin/admin.js +++ b/frontend/js/admin/admin.js @@ -377,6 +377,17 @@
`; } + // ── секция трендов (Level 3) ── + let trendsHtml = ''; + if (m && m.history && m.history.length) { + const tc = (id, label) => `
${label}
`; + trendsHtml = `
+
Тренды (${m.history.length} точек · 1/мин)
+
+ ${tc('trend-mem','Память RSS')}${tc('trend-req','Запросы/мин')}${tc('trend-err','Ошибки 5xx')}${tc('trend-p95','Латентность p95')} +
`; + } + el.innerHTML = `
@@ -427,6 +438,8 @@ ${metricsHtml} + ${trendsHtml} +
Крупнейшие таблицы БД
${(h.db.tables||[]).map(t=>`
@@ -436,6 +449,18 @@
`).join('')}
`; + if (m && m.history && m.history.length) { + const draw = (id, key, color, fmt) => { + const c = document.getElementById(id); if (!c) return; + c.width = c.offsetWidth || 300; + drawTrend(c, m.history, key, color, fmt); + }; + draw('trend-mem', 'rss', '#9B5DE5', v => (v/1e6).toFixed(0)+' МБ'); + draw('trend-req', 'reqPerMin', '#34d399'); + draw('trend-err', 'err5xx', '#f87171'); + draw('trend-p95', 'p95', '#facc15', v => v.toFixed(0)+' мс'); + } + const btn = document.getElementById('health-live-btn'); if (btn) btn.addEventListener('click', () => { _healthLive = !_healthLive; @@ -445,6 +470,35 @@ }); } + // Мини-график тренда (canvas): линия + заливка + подписи макс/последнее. + function drawTrend(canvas, points, key, color, fmt) { + const ctx = canvas.getContext('2d'); + const W = canvas.width, H = canvas.height; + ctx.clearRect(0, 0, W, H); + const vals = points.map(p => p[key] || 0); + if (vals.length < 2) { + ctx.fillStyle = '#555'; ctx.font = '11px Manrope,sans-serif'; + ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; + ctx.fillText('накопление данных…', W/2, H/2); return; + } + let max = Math.max(...vals), min = Math.min(...vals); + if (max === min) max += 1; + const pad = 4, range = (max - min) || 1; + const X = i => pad + i/(vals.length-1)*(W-pad*2); + const Y = v => H-pad - (v-min)/range*(H-pad*2-10); + ctx.beginPath(); ctx.moveTo(X(0), H); + vals.forEach((v,i)=>ctx.lineTo(X(i), Y(v))); + ctx.lineTo(X(vals.length-1), H); ctx.closePath(); + ctx.fillStyle = color + '22'; ctx.fill(); + ctx.beginPath(); vals.forEach((v,i)=> i?ctx.lineTo(X(i),Y(v)):ctx.moveTo(X(i),Y(v))); + ctx.strokeStyle = color; ctx.lineWidth = 1.6; ctx.lineJoin = 'round'; ctx.stroke(); + const lastV = vals[vals.length-1]; + ctx.fillStyle = color; ctx.beginPath(); ctx.arc(X(vals.length-1), Y(lastV), 2.5, 0, Math.PI*2); ctx.fill(); + ctx.font = '10px Manrope,sans-serif'; ctx.textBaseline = 'top'; + ctx.fillStyle = '#888'; ctx.textAlign = 'left'; ctx.fillText('макс ' + (fmt?fmt(max):max), pad, 1); + ctx.fillStyle = color; ctx.textAlign = 'right'; ctx.fillText(fmt?fmt(lastV):lastV, W-pad, 1); + } + /* ════════════════════════════════════════════════ ОНЛАЙН-УРОКИ (classroom admin) ════════════════════════════════════════════════ */ From a6567d0938ba1987476b6ff9ed2447070073f33b Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:38:56 +0300 Subject: [PATCH 10/11] =?UTF-8?q?feat(admin/health):=20System=20Health=20L?= =?UTF-8?q?evel=204=20=E2=80=94=20=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BA=D0=B0=20+=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=D0=B4=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adminController.getHealth: активные health-проверки — отклик БД (ping, мс) и тест записи на диск рядом с БД; вердикт уходит в critical при недоступной БД или диске, warning при медленном отклике БД (>100мс). Плюс recentErrorList — последние 8 записей error_log (level/route/method/message/время). admin.js: панель «Диагностика» — индикаторы БД/диска (зелёный/красный) + лента последних ошибок с цветом по уровню. Проверено: checks {dbOk,dbPingMs,diskWritable}, список ошибок отдаётся. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/controllers/adminController.js | 23 +++++++++++++++++++ frontend/js/admin/admin.js | 26 ++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/backend/src/controllers/adminController.js b/backend/src/controllers/adminController.js index 9c6ce00..76a235f 100644 --- a/backend/src/controllers/adminController.js +++ b/backend/src/controllers/adminController.js @@ -648,6 +648,21 @@ function _dbTables() { } catch { return []; } } +// Активные проверки: отклик БД (мс) и тест записи на диск рядом с БД. +function _runChecks() { + let dbPingMs = null, dbOk = false; + try { const t = process.hrtime.bigint(); db.prepare('SELECT 1 AS ok').get(); dbPingMs = Number(process.hrtime.bigint() - t) / 1e6; dbOk = true; } catch {} + let diskWritable = false; + try { + const f = path.join(path.dirname(path.resolve(DB_PATH)), '.health-write-test'); + fs.writeFileSync(f, 'ok'); fs.unlinkSync(f); diskWritable = true; + } catch {} + return { dbPingMs, dbOk, diskWritable }; +} +const _recentErrStmt = db.prepare( + "SELECT id, level, message, route, method, created_at FROM error_log ORDER BY id DESC LIMIT 8" +); + function getHealth(_req, res) { const uptimeSec = process.uptime(); const mem = process.memoryUsage(); @@ -672,6 +687,10 @@ function getHealth(_req, res) { let sseStats = { users: 0, guests: 0, connections: 0 }; try { sseStats = sse.stats(); } catch {} + // Активные health-проверки (Level 4): отклик БД и запись на диск. + const checks = _runChecks(); + const recentErrorList = (() => { try { return _recentErrStmt.all(); } catch { return []; } })(); + // Вердикт здоровья по порогам. const reasons = []; let status = 'ok'; @@ -688,9 +707,13 @@ function getHealth(_req, res) { if (eventLoopLagMs > 200) crit(`Лаг event-loop ${eventLoopLagMs.toFixed(0)} мс`); else if (eventLoopLagMs > 70) warn(`Лаг event-loop ${eventLoopLagMs.toFixed(0)} мс`); if (dbSizeBytes > 1.5e9) warn('БД >1.5 ГБ'); + if (!checks.dbOk) crit('БД недоступна'); + if (!checks.diskWritable) crit('Диск недоступен для записи'); + if (checks.dbPingMs != null && checks.dbPingMs > 100) warn(`Медленный отклик БД ${checks.dbPingMs.toFixed(0)} мс`); res.json({ status, reasons, + checks, recentErrorList, uptime: uptimeSec, startedAt: new Date(Date.now() - uptimeSec * 1000).toISOString(), memory: { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal }, diff --git a/frontend/js/admin/admin.js b/frontend/js/admin/admin.js index b024712..830485c 100644 --- a/frontend/js/admin/admin.js +++ b/frontend/js/admin/admin.js @@ -388,6 +388,30 @@
`; } + // ── панель диагностики (Level 4): health-чеки + последние ошибки ── + let diagHtml = ''; + { + const ch = h.checks || {}; + const okCol = '#4ade80', badCol = 'var(--pink)'; + const chip = (label, ok, extra) => `
${label}${extra?` ${extra}`:''}
`; + const errs = h.recentErrorList || []; + const lvlCol = l => l==='error'||l==='fatal'?'var(--pink)':l==='warn'?'#facc15':'var(--text-3)'; + diagHtml = `
+
Диагностика
+
+ ${chip('База данных', !!ch.dbOk, ch.dbPingMs!=null?ch.dbPingMs.toFixed(2)+' мс':'')} + ${chip('Запись на диск', !!ch.diskWritable, ch.diskWritable?'доступна':'НЕДОСТУПНА')} +
+
Последние ошибки
+ ${errs.length ? errs.map(e=>`
+ ${esc((e.created_at||'').replace('T',' ').slice(5,16))} + ${esc(e.level||'')} + ${e.route?`${esc(e.method||'')} ${esc(e.route)}`:''} + ${esc(e.message||'')} +
`).join('') : `
Ошибок нет
`} +
`; + } + el.innerHTML = `
@@ -440,6 +464,8 @@ ${trendsHtml} + ${diagHtml} +
Крупнейшие таблицы БД
${(h.db.tables||[]).map(t=>`
From e949cb18a52008653a0469e762c09d1e5ef6dd6b Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 18:40:16 +0300 Subject: [PATCH 11/11] =?UTF-8?q?feat(chemistry7):=20Phase=202=20=D0=92?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B0=201=20=E2=80=94=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=202,=20=C2=A713=20+=20=D0=9B=D0=9E2=20+=20=C2=A714?= =?UTF-8?q?=20+=20=C2=A715?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §13 Воздух как смесь газов (интерактивная диаграмма состава), ЛО2 Сборка приборов и собирание газов (выбор способа собирания), §14 Кислород — элемент и простое вещество (переключатель O/O2/O3 + модели), §15 Химические свойства кислорода (симулятор горения C/S/P/Fe/Mg → оксид). chem7_ch2_widgets.js. Тест: 11/11 pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/chemistry7-page.test.js | 15 ++ frontend/js/chem7_ch2_widgets.js | 192 ++++++++++++++++++++++++ frontend/textbooks/chemistry_7_ch2.html | 119 ++++++++++++++- 3 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 frontend/js/chem7_ch2_widgets.js diff --git a/backend/tests/chemistry7-page.test.js b/backend/tests/chemistry7-page.test.js index 067e6d1..dc4c4c6 100644 --- a/backend/tests/chemistry7-page.test.js +++ b/backend/tests/chemistry7-page.test.js @@ -24,6 +24,7 @@ function buildPage(file) { '/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'), '/js/chem7_svg.js': readF('frontend/js/chem7_svg.js'), '/js/chem7_ch1_widgets.js': readF('frontend/js/chem7_ch1_widgets.js'), + '/js/chem7_ch2_widgets.js': readF('frontend/js/chem7_ch2_widgets.js'), '/js/chem8_engine.js': readF('frontend/js/chem8_engine.js') }; html = html @@ -133,6 +134,20 @@ test('ch1: переход к §9 и финалу строит без ошибо assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); }); +test('ch2 Волна 1: интерактивы §13 + ЛО2 + §14 + §15 монтируются', async () => { + const { doc, errors } = await loadDom('chemistry_7_ch2.html'); + assert.ok(doc.querySelector('#p13-air .air-seg'), 'диаграмма состава воздуха §13'); + doc.defaultView.goTo('lo2'); await wait(100); + assert.ok(doc.querySelector('#lo2-coll #lo2-pick'), 'выбор собирания газа ЛО2'); + doc.defaultView.goTo('p14'); await wait(100); + assert.ok(doc.querySelector('#p14-tog #p14-o2'), 'переключатель элемент/вещество §14'); + doc.defaultView.goTo('p15'); await wait(100); + assert.ok(doc.querySelector('#p15-burn #p15-go'), 'симулятор горения §15'); + doc.defaultView.goTo('p15'); doc.getElementById('p15-go').dispatchEvent(new doc.defaultView.Event('click', { bubbles: true })); + assert.match(doc.querySelector('#p15-out').textContent, /оксид/, 'горение даёт оксид'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + /* ── Хаб: каталог глав + финал курса ── */ function buildHub() { let html = readF('frontend/textbooks/chemistry_7_hub.html'); diff --git a/frontend/js/chem7_ch2_widgets.js b/frontend/js/chem7_ch2_widgets.js new file mode 100644 index 0000000..4109066 --- /dev/null +++ b/frontend/js/chem7_ch2_widgets.js @@ -0,0 +1,192 @@ +/* chem7_ch2_widgets.js — интерактивы главы 2 «Кислород» (Химия 7). + * Монтируются движком chem8_engine.js: window.CHEM8_WIDGETS[id] / window.FLAG_MOUNTS[id]. + * Используют window.Chem8 (chem8_svg.js): chemEq, formula, molarMass, arOf. + * Без эмоджи; KaTeX-рендер — через window.chem8RenderMath. + */ +(function (W) { + 'use strict'; + function C() { return W.Chem8 || {}; } + function $(id) { return document.getElementById(id); } + function esc(s){ return String(s).replace(/&/g,'&').replace(//g,'>'); } + function gcd(a, b) { return b ? gcd(b, a % b) : a; } + function ceq(src, opts){ return C().chemEq ? C().chemEq(src, opts || {}) : esc(src); } + + var COL = { H:'#cbd5e1', O:'#ef4444', N:'#3b82f6', C:'#334155', S:'#eab308', P:'#f97316', Fe:'#b45309', Mg:'#22c55e', Cu:'#ea580c' }; + function ball(el, x, r){ + return '' + + ''+el+''; + } + function molSvg(atoms){ // atoms: [['O',2]] + var list=[]; atoms.forEach(function(p){ for(var i=0;i'+svg+''; + } + + /* §13 — состав воздуха (стопочная полоса с кликом) */ + var AIR = [ + { g:'Азот N₂', p:78, c:'#3b82f6', note:'не поддерживает горение и дыхание' }, + { g:'Кислород O₂', p:21, c:'#ef4444', note:'нужен для дыхания и горения' }, + { g:'Другие газы (Ar, CO₂…)', p:1, c:'#94a3b8', note:'аргон, углекислый газ и др. — около 1 %' } + ]; + function mount_p13() { + var m = $('p13-air'); if (!m || m._built) return; m._built = 1; + var bar = AIR.map(function (a, i) { + return '
' + a.p + '%
'; + }).join(''); + m.innerHTML = '
' + bar + '
' + + '
Кликни по части диаграммы, чтобы узнать о газе.
'; + var out = $('p13-air-out'); + m.querySelectorAll('.air-seg').forEach(function (s) { + s.addEventListener('click', function () { + var a = AIR[+s.dataset.i]; out.className = 'out ok'; + out.innerHTML = '' + esc(a.g) + ' — около ' + a.p + ' % воздуха. ' + esc(a.note) + '.'; + }); + }); + } + + /* ЛО2 — выбор способа собирания газа */ + var GASES = [ + { g:'Кислород O₂', heavier:true, ways:['вытеснением воды', 'в сосуд отверстием вверх (тяжелее воздуха)'] }, + { g:'Водород H₂', heavier:false, ways:['вытеснением воды', 'в сосуд отверстием вниз (легче воздуха)'] }, + { g:'Углекислый газ CO₂', heavier:true, ways:['в сосуд отверстием вверх (тяжелее воздуха)'] } + ]; + function mount_lo2() { + var m = $('lo2-coll'); if (!m || m._built) return; m._built = 1; + var idx = 0; + function render(){ + var g = GASES[idx]; + m.innerHTML = '
' + + '
' + esc(g.g) + ' ' + (g.heavier?'тяжелее':'легче') + ' воздуха.
Способы собирания:
' + + g.ways.map(function(w){ return '✓ ' + esc(w); }).join('
') + '
'; + $('lo2-pick').addEventListener('change', function(e){ idx=+e.target.value; m._built=0; render(); }); + } + render(); + } + + /* §14 — кислород: элемент или простое вещество + модели O₂ / O₃ */ + function mount_p14() { + var m = $('p14-tog'); if (!m || m._built) return; m._built = 1; + var mode = 'el'; + function render(){ + m.innerHTML = '
' + + '' + + '
' + + '
' + ( + mode==='el' ? 'Кислород — химический элемент (символ O, Z = 8, A_r = 16). Так говорят, когда речь о атомах кислорода в составе веществ (например, в воде H₂O).' + : mode==='o2' ? 'Кислород — простое вещество O₂: молекула из двух атомов. Газ без цвета и запаха, немного тяжелее воздуха.' + molSvg([['O',2]]) + : 'Озон O₃: молекула из трёх атомов кислорода — другое простое вещество того же элемента.' + molSvg([['O',3]]) + ) + '
'; + $('p14-el').addEventListener('click',function(){mode='el';m._built=0;render();}); + $('p14-o2').addEventListener('click',function(){mode='o2';m._built=0;render();}); + $('p14-o3').addEventListener('click',function(){mode='o3';m._built=0;render();}); + } + render(); + } + + /* §15 — симулятор горения: вещество + O₂ → оксид */ + var FUELS = [ + { el:'C', name:'углерод', eq:'C + O2 = CO2', note:'горит с образованием углекислого газа' }, + { el:'S', name:'сера', eq:'S + O2 = SO2', note:'горит синим пламенем, резкий запах' }, + { el:'P', name:'фосфор', eq:'4P + 5O2 = 2P2O5', note:'горит ярко, белый дым' }, + { el:'Fe', name:'железо', eq:'3Fe + 2O2 = Fe3O4', note:'горит, разбрасывая искры' }, + { el:'Mg', name:'магний', eq:'2Mg + O2 = 2MgO', note:'ослепительно яркое пламя' } + ]; + function flame(){ + return ''; + } + function mount_p15() { + var m = $('p15-burn'); if (!m || m._built) return; m._built = 1; + var idx = 0; + function render(){ + var f = FUELS[idx]; + m.innerHTML = '
' + + '
' + + '
Выбери вещество и подожги его в кислороде.
'; + $('p15-pick').addEventListener('change', function(e){ idx=+e.target.value; m._built=0; render(); }); + $('p15-go').addEventListener('click', function(){ + var out = $('p15-out'); out.className='out ok'; + out.innerHTML = flame() + ' ' + esc(f.name[0].toUpperCase()+f.name.slice(1)) + ' горит в кислороде: ' + esc(f.note) + '.
' + + '
' + ceq(f.eq) + '
' + + '
Продукт — оксид (соединение элемента с кислородом).
'; + }); + } + render(); + } + + /* §16 — конструктор оксида (элемент + валентность, кислород II) + классификатор */ + var OXEL = [ ['Na',1], ['Ca',2], ['Mg',2], ['Cu',2], ['Zn',2], ['Al',3], ['C',4], ['S',4], ['P',5] ]; + function mount_p16() { + var b = $('p16-bld'); + if (b && !b._built) { b._built = 1; + b.innerHTML = '
+ кислород (O, II)
'; + function rom(n){ return ['','I','II','III','IV','V'][n]; } + function upd(){ + var e = OXEL[+$('p16-el').value], lcm = e[1]*2/gcd(e[1],2), ix=lcm/e[1], iy=lcm/2; + var raw = e[0] + (ix>1?ix:'') + 'O' + (iy>1?iy:''); + var out=$('p16-out'); out.className='out ok'; + out.innerHTML = 'Валентности: '+e[0]+' = '+rom(e[1])+', O = II; НОК = '+lcm+'
Формула оксида: '+(C().formula?C().formula(raw):raw)+'
'; + } + $('p16-el').addEventListener('change',upd); upd(); + } + var cl = $('p16-cls'); + if (cl && W.Chem7Classify) W.Chem7Classify(cl); + } + + /* §17 — схема получения кислорода + роль катализатора */ + var PROD = [ + { name:'Разложение перманганата калия (при нагревании)', eq:'2KMnO4 = K2MnO4 + MnO2 + O2^', cond:'t°', note:'Реакция разложения: из одного вещества — несколько. Идёт при нагревании.' }, + { name:'Разложение пероксида водорода (катализатор MnO₂)', eq:'2H2O2 = 2H2O + O2^', cond:'MnO₂', note:'MnO₂ — катализатор: ускоряет реакцию, но сам в ней не расходуется.' } + ]; + function mount_p17() { + var m = $('p17-prod'); if (!m || m._built) return; m._built = 1; + var idx = 0; + function render(){ + var p = PROD[idx]; + m.innerHTML = '
' + + '
' + ceq(p.eq, {cond:p.cond}) + '
' + + '
' + esc(p.note) + '
'; + $('p17-pick').addEventListener('change', function(e){ idx=+e.target.value; m._built=0; render(); }); + } + render(); + } + + /* ПР2 — проверка кислорода тлеющей лучинкой */ + function mount_pr2() { + var m = $('pr2-test'); if (!m || m._built) return; m._built = 1; + m.innerHTML = '
Как доказать, что собранный газ — кислород?
'; + $('pr2-go').addEventListener('click', function(){ + var out=$('pr2-out'); out.className='out ok'; + out.innerHTML = flame() + ' Тлеющая лучинка ярко вспыхивает — значит, газ поддерживает горение. Это кислород.'; + }); + } + + /* классификатор «оксид / не оксид» (используется в §16) */ + W.Chem7Classify = function(mount){ + if (!mount || mount._built) return; mount._built = 1; + var items = [ {t:'CuO',ox:1}, {t:'NaCl',ox:0}, {t:'CO₂',ox:1}, {t:'H₂SO₄',ox:0}, {t:'Fe₂O₃',ox:1}, {t:'HCl',ox:0}, {t:'SO₂',ox:1}, {t:'CaO',ox:1} ]; + var pool = items.map(function(_,i){return i;}), placed={}, sel=null; + function render(){ + var chips = pool.map(function(i){ return ''; }).join('') || 'Готово.'; + var cols = [['Оксид',1],['Не оксид',0]].map(function(b){ + var inb = Object.keys(placed).filter(function(k){return placed[k]===b[1];}); + var cells = inb.map(function(k){ var ok=items[k].ox===b[1]; return '
'+esc(items[k].t)+(ok?' ✓':' ✗')+'
'; }).join('') || '
сюда…
'; + return '
'+b[0]+'
'+cells+'
'; + }).join(''); + mount.innerHTML = '
'+chips+'
'+cols+'
'; + mount.querySelectorAll('.c7-chip').forEach(function(b){ b.addEventListener('click',function(){ mount.querySelectorAll('.c7-chip').forEach(function(x){x.style.outline='';}); sel=+b.dataset.i; b.style.outline='2px solid var(--pri)'; }); }); + mount.querySelectorAll('.c7-bucket').forEach(function(col){ col.addEventListener('click',function(){ if(sel==null)return; placed[sel]=+col.dataset.b; pool=pool.filter(function(x){return x!==sel;}); sel=null; render(); }); }); + } + render(); + }; + + W.CHEM8_WIDGETS = Object.assign(W.CHEM8_WIDGETS || {}, { + p13: mount_p13, lo2: mount_lo2, p14: mount_p14, p15: mount_p15, + p16: mount_p16, p17: mount_p17, pr2: mount_pr2 + }); + W.FLAG_MOUNTS = Object.assign(W.FLAG_MOUNTS || {}, {}); +})(window); diff --git a/frontend/textbooks/chemistry_7_ch2.html b/frontend/textbooks/chemistry_7_ch2.html index e58c89f..4842c14 100644 --- a/frontend/textbooks/chemistry_7_ch2.html +++ b/frontend/textbooks/chemistry_7_ch2.html @@ -17,6 +17,7 @@ + @@ -88,11 +89,113 @@ window.PARAS = [ {id:'final2', num:'★', name:'Финал главы', sub:'босс · ачивка', final:true} ]; -window.ACH_LABELS = { start:'Начало главы 2!', final2_tasks:'Глава 2 пройдена!' }; -window.SIDEBARS = { p13:{ title:'Глава 2 · Химия 7', rows:[['Раздел','Кислород'],['§§','13–17'],['Лаб/ПР','ЛО 2 · ПР 2']] } }; -window.TIPS = [{ sec:'p13', html:'Глава наполняется содержанием по фазам. Сейчас доступны навигация по параграфам и отметка о прочтении (+10 XP).' }]; +window.ACH_LABELS = { start:'Начало главы 2!', p13_done:'§13 изучен!', lo2_done:'Лабораторный опыт 2 выполнен!', + p14_done:'§14 изучен!', p15_done:'§15 изучен!', final2_tasks:'Глава 2 пройдена!' }; +window.SIDEBARS = { + p13:{ title:'Шпаргалка §13', rows:[['Воздух','смесь газов'],['$N_2$','≈ 78 %'],['$O_2$','≈ 21 %']] }, + lo2:{ title:'Лаб. опыт 2', rows:[['Прибор','пробирка + трубка'],['Собирание','воздуха или воды']] }, + p14:{ title:'Шпаргалка §14', rows:[['Элемент','O, $Z=8$'],['Вещество','$O_2$'],['Озон','$O_3$']] }, + p15:{ title:'Шпаргалка §15', rows:[['Горение','+ $O_2$'],['Продукт','оксид'],['Окисление','медленное и быстрое']] } +}; +window.TIPS = [ + { sec:'p13', html:'Воздух — смесь газов: примерно $78\\,\\%$ азота $N_2$ и $21\\,\\%$ кислорода $O_2$, около $1\\,\\%$ — другие газы.' }, + { sec:'lo2', html:'Газ, который тяжелее воздуха (как $O_2$), собирают в сосуд отверстием вверх; легче воздуха ($H_2$) — отверстием вниз; нерастворимый — вытеснением воды.' }, + { sec:'p14', html:'$O$ — элемент (атом в составе веществ). $O_2$ — простое вещество. Кислород $O_2$ и озон $O_3$ — разные простые вещества одного элемента.' }, + { sec:'p15', html:'При горении вещество соединяется с кислородом — образуется оксид. Реакции с кислородом называют реакциями окисления.' } +]; -/* Phase 0: заглушки-builder'ы из PARAS (теория и интерактивы добавляются в фазах 1–4). */ +window.POOLS = { + p13:[ + {q:'Воздух — это…',opts:['Чистое вещество','Смесь газов','Простое вещество','Один газ — кислород'],a:1,ex:'Воздух — смесь нескольких газов.'}, + {q:'Какова примерная объёмная доля кислорода в воздухе (%)?',hint:'около 21',unit:'%',a:21,ex:'Кислорода ≈ 21 %.'}, + {q:'Какова примерная объёмная доля азота в воздухе (%)?',hint:'около 78',unit:'%',a:78,ex:'Азота ≈ 78 %.'}, + {q:'Какой газ воздуха необходим для дыхания и горения?',opts:['Азот','Кислород','Углекислый газ','Аргон'],a:1,ex:'Кислород $O_2$.'} + ], + p14:[ + {q:'Запись «$\\text{O}$» обозначает…',opts:['Химический элемент (атом)','Молекулу кислорода','Озон','Смесь'],a:0,ex:'O — символ химического элемента.'}, + {q:'$\\text{O}_2$ — это…',opts:['Химический элемент','Простое вещество','Сложное вещество','Смесь'],a:1,ex:'Молекула из атомов одного элемента — простое вещество.'}, + {q:'Чему равна относительная атомная масса кислорода $A_r(\\text{O})$?',hint:'из таблицы',unit:'',a:16,ex:'$A_r(\\text{O})=16$.'}, + {q:'$\\text{O}_2$ и $\\text{O}_3$ — это…',opts:['Одно вещество','Разные простые вещества одного элемента','Сложные вещества','Смесь газов'],a:1,ex:'Кислород и озон — разные простые вещества элемента кислород.'} + ], + p15:[ + {q:'Продукт горения простого вещества в кислороде — это…',opts:['Кислота','Оксид','Соль','Металл'],a:1,ex:'Образуется оксид — соединение с кислородом.'}, + {q:'При горении серы в кислороде образуется…',opts:['$\\text{SO}_2$','$\\text{H}_2\\text{S}$','$\\text{S}$','$\\text{SO}_3$ только'],a:0,ex:'$S+O_2=SO_2$ (с резким запахом).'}, + {q:'Реакция $\\text{C}+\\text{O}_2=\\text{CO}_2$ относится к реакциям…',opts:['Разложения','Соединения','Обмена','Замещения'],a:1,ex:'Из двух веществ одно — соединение.'}, + {q:'В уравнении $2\\text{Mg}+\\text{O}_2=2\\text{MgO}$ коэффициент перед $\\text{MgO}$ равен…',hint:'смотри на оксид',unit:'',a:2,ex:'Коэффициент 2.'} + ] +}; + +function rememberBox(items){ + return '
' + +' Запомни!
    ' + +items.map(function(t){return '
  • '+t+'
  • ';}).join('')+'
'; +} +function qList(items){ + return '
Вопросы и задания
    ' + +items.map(function(t){return '
  1. '+t+'
  2. ';}).join('')+'
'; +} +function wgt(title, inner){ + return '
'+title+'
'+inner+'
'; +} + +function build_p13(){ + document.getElementById('p13-body').innerHTML = + '
§ 13 · Химия 7

Воздух как смесь газов

' + +'
Из каких газов состоит воздух и почему он так важен.
' + +'
смесь$N_2$$O_2$
' + +makeCard('theory','Состав воздуха','§13','

Воздух — это смесь газов. Основные из них: азот $\\text{N}_2$ (около 78 %) и кислород $\\text{O}_2$ (около 21 %). Ещё около 1 % приходится на другие газы — аргон, углекислый газ и др.

' + +'
Воздух — однородная смесь: газы перемешаны равномерно. Именно кислород воздуха обеспечивает дыхание живых организмов и горение.
') + +wgt('Состав воздуха: кликни по части диаграммы','
') + +rememberBox(['Воздух — смесь газов.','Азота ≈ 78 %, кислорода ≈ 21 %.','Кислород нужен для дыхания и горения.']) + +qList(['Из каких газов в основном состоит воздух?','Какова доля кислорода в воздухе?','Почему воздух называют смесью, а не чистым веществом?']) + +secNav(null,'lo2')+readButton('p13'); + wireReadBtn('p13'); +} + +function build_lo2(){ + document.getElementById('lo2-body').innerHTML = + '
Лабораторный опыт 2

Сборка простейших приборов для получения и собирания газов

' + +'
Научиться собирать прибор для получения газа и выбирать способ его собирания.
' + +makeCard('lab','Прибор и способы собирания',null,'

Простейший прибор: пробирка с пробкой и газоотводной трубкой. Газ из пробирки по трубке поступает в сосуд для собирания.

' + +'
  • Вытеснением воздуха. Газ тяжелее воздуха ($O_2$, $CO_2$) собирают в сосуд, держа его отверстием вверх; легче воздуха ($H_2$) — отверстием вниз.
  • Вытеснением воды. Так собирают газы, мало растворимые в воде (например, кислород).
' + +'
Проверь герметичность прибора перед опытом; нагревай пробирку осторожно.
') + +wgt('Выбери способ собирания газа','
') + +secNav('p13','p14')+readButton('lo2'); + wireReadBtn('lo2'); +} + +function build_p14(){ + document.getElementById('p14-body').innerHTML = + '
§ 14 · Химия 7

Кислород как химический элемент и простое вещество

' + +'
Чем отличается элемент кислород от простого вещества кислород.
' + +'
$O$$O_2$$O_3$
' + +makeCard('theory','Элемент и простое вещество','§14','

Кислород как элемент — это атомы O ($Z=8$, $A_r=16$). Они входят в состав очень многих веществ (воды $\\text{H}_2\\text{O}$, оксидов, песка $\\text{SiO}_2$). Кислород — самый распространённый элемент земной коры.

' + +'
Кислород как простое вещество — это газ $\\text{O}_2$ (молекула из двух атомов). Элемент кислород образует и другое простое вещество — озон $\\text{O}_3$.
') + +makeCard('theory','Физические свойства кислорода','§14','

$\\text{O}_2$ — газ без цвета и запаха, немного тяжелее воздуха, мало растворим в воде. При сильном охлаждении превращается в голубоватую жидкость.

') + +wgt('Элемент O или простое вещество?','
') + +rememberBox(['Элемент кислород — атомы O в составе веществ.','Простое вещество — газ $O_2$.','Озон $O_3$ — другое простое вещество того же элемента.']) + +qList(['Когда говорят о кислороде-элементе, а когда — о простом веществе?','Назови физические свойства кислорода $O_2$.','Чем озон отличается от кислорода?']) + +secNav('lo2','p15')+readButton('p14'); + wireReadBtn('p14'); +} + +function build_p15(){ + document.getElementById('p15-body').innerHTML = + '
§ 15 · Химия 7

Химические свойства кислорода

' + +'
вещество $+ \\text{O}_2 \\to$ оксид
' + +'
Как кислород реагирует с другими веществами и что такое горение и окисление.
' + +'
горениеоксидокисление
' + +makeCard('theory','Кислород — активное вещество','§15','

Кислород реагирует со многими простыми и сложными веществами. Реакции, идущие с выделением тепла и света, называют горением. Продукты горения простых веществ в кислороде — оксиды.

' + +'
Окисление — реакция вещества с кислородом. Оно бывает быстрым (горение) и медленным (дыхание, гниение, ржавление железа).
') + +makeCard('example','Горение веществ в кислороде',null,'
  • $\\text{C}+\\text{O}_2=\\text{CO}_2$
  • $\\text{S}+\\text{O}_2=\\text{SO}_2$ (резкий запах)
  • $4\\text{P}+5\\text{O}_2=2\\text{P}_2\\text{O}_5$
  • $3\\text{Fe}+2\\text{O}_2=\\text{Fe}_3\\text{O}_4$ (искры)
') + +wgt('Симулятор горения в кислороде','
') + +rememberBox(['Горение — реакция с кислородом, идёт с выделением тепла и света.','Продукт горения простого вещества — оксид.','Окисление бывает быстрым (горение) и медленным.']) + +qList(['Что образуется при горении вещества в кислороде?','Приведи пример медленного окисления.','Запиши уравнение горения углерода.']) + +secNav('p14','p16')+readButton('p15'); + wireReadBtn('p15'); +} + +/* заглушки для ещё не наполненных § (следующая волна) */ (function(){ var P = window.PARAS, B = {}; function ph(p, prev, next){ @@ -102,7 +205,7 @@ window.TIPS = [{ sec:'p13', html:'Глава наполняется содерж '
' + p.num + ' · Химия 7

' + p.name + '

' + '
Содержание этого ' + (p.final ? 'раздела' : 'параграфа') + ' готовится.
' + makeCard('theory', p.name, p.num, - '

Скоро здесь появятся теория, наглядные SVG-схемы, молекулярные модели и интерактивные тренажёры по теме «' + p.name + '». Пока доступна навигация по главе' + (p.final ? '.' : ' и отметка о прочтении.') + '

') + '

Скоро здесь появятся теория, наглядные SVG-схемы и интерактивные тренажёры по теме «' + p.name + '». Пока доступна навигация по главе' + (p.final ? '.' : ' и отметка о прочтении.') + '

') + secNav(prev, next) + (p.final ? '' : readButton(p.id)); if (!p.final) wireReadBtn(p.id); }; @@ -112,6 +215,12 @@ window.TIPS = [{ sec:'p13', html:'Глава наполняется содерж } window.BUILDERS = B; })(); + +/* реальные builder'ы Волны 1 главы 2 */ +window.BUILDERS.p13 = build_p13; +window.BUILDERS.lo2 = build_lo2; +window.BUILDERS.p14 = build_p14; +window.BUILDERS.p15 = build_p15;