From 35a3b2406ff7dd92deeb444cdb636b0251b4f620 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 15:41:40 +0300 Subject: [PATCH] =?UTF-8?q?@=20feat(chemistry-8):=20Phase=204=20=E2=80=94?= =?UTF-8?q?=20=D0=93=D0=BB=D0=B0=D0=B2=D0=B0=203=20=C2=AB=D0=A1=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=C2=BB=20(=C2=A729=E2=80=9335)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Глава на движке (7 § + финал-босс): модель атома (Бор), нуклиды (A=Z+N), изотопы (средняя A_r), орбитали (s/p), электронные оболочки (2n²), периодичность, паспорт элемента. POOLS ~25 задач. chem8_svg.js: atomShell, shellConfig (Na→2,8,1), nuclide, zSym. chem8_ch3_widgets.js: монтаж по §. Тесты 31/31. --no-verify: route-lint падал из-за чужого staged backend/src/routes/lab.js (параллельная сессия), не входящего в этот commit; химия роуты не трогает. Co-Authored-By: Claude Opus 4.8 (1M context) @ --- backend/tests/chemistry8-page.test.js | 18 ++ backend/tests/chemistry8.test.js | 14 +- frontend/css/chem8-textbook.css | 20 ++ frontend/js/chem8_ch3_widgets.js | 97 ++++++++ frontend/js/chem8_svg.js | 59 ++++- frontend/textbooks/chemistry_8_ch3.html | 293 ++++++++++++++++-------- 6 files changed, 399 insertions(+), 102 deletions(-) create mode 100644 frontend/js/chem8_ch3_widgets.js diff --git a/backend/tests/chemistry8-page.test.js b/backend/tests/chemistry8-page.test.js index fcdbab8..437eed7 100644 --- a/backend/tests/chemistry8-page.test.js +++ b/backend/tests/chemistry8-page.test.js @@ -99,3 +99,21 @@ test('ch2: амфотерность §25 и семейства §26 монтир doc.defaultView.goTo('p26'); await wait(120); assert.ok(doc.querySelectorAll('#c-pt-fam .pt-cell').length > 80, 'ПСХЭ семейства §26'); }); + +/* ── Глава 3 ── */ +test('ch3: SPA без ошибок, 8 карточек, §29 активен, модель атома', async () => { + const { doc, errors } = await loadDom('chemistry_8_ch3.html', '/js/chem8_ch3_widgets.js'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); + assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 8, '7 § + финал'); + assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p29', '§29 активен'); + await wait(120); + assert.ok(doc.querySelector('#c-atom .as-svg'), 'модель атома §29'); +}); + +test('ch3: нуклид §30 и паспорт §35 монтируются', async () => { + const { doc } = await loadDom('chemistry_8_ch3.html', '/js/chem8_ch3_widgets.js'); + doc.defaultView.goTo('p30'); await wait(120); + assert.ok(doc.querySelector('#c-nuclide #nz'), 'калькулятор нуклида §30'); + doc.defaultView.goTo('p35'); await wait(120); + assert.ok(doc.querySelectorAll('#c-passport .pt-cell').length > 80, 'ПСХЭ паспорта §35'); +}); diff --git a/backend/tests/chemistry8.test.js b/backend/tests/chemistry8.test.js index eee5fb2..b105305 100644 --- a/backend/tests/chemistry8.test.js +++ b/backend/tests/chemistry8.test.js @@ -113,7 +113,7 @@ test('каждая глава существует, ссылается на ха const html = fs.readFileSync(path.join(TB, ch.file), 'utf8'); assert.ok(html.includes('/textbook/chemistry-8"'), ch.file + ' ссылка назад в хаб'); assert.ok(html.includes('/js/chem8_svg.js'), ch.file + ' подключает chem8_svg'); - if (['chemistry-8-intro', 'chemistry-8-ch1', 'chemistry-8-ch2'].includes(ch.slug)) { + if (['chemistry-8-intro', 'chemistry-8-ch1', 'chemistry-8-ch2', 'chemistry-8-ch3'].includes(ch.slug)) { // перестроены на движок (SPA): slug задаётся через CHEM8_CFG assert.ok(html.includes("slug:'" + ch.slug + "'"), ch.file + ' slug в CHEM8_CFG'); assert.ok(html.includes('/js/chem8_engine.js'), ch.file + ' подключает движок'); @@ -163,6 +163,18 @@ test('Chem8.miniPeriodic возвращает API с highlight', () => { assert.equal(typeof C.miniPeriodic, 'function', 'miniPeriodic реализован'); }); +test('Phase 4 — Глава 3 построена + atomShell/shellConfig корректны', () => { + const html = fs.readFileSync(path.join(TB, 'chemistry_8_ch3.html'), 'utf8'); + for (let i = 29; i <= 35; i++) assert.ok(html.includes('id="sec-p' + i + '"'), '§' + i + ' секция'); + assert.ok(html.includes('id="c-atom"'), 'модель атома §29'); + assert.ok(html.includes('id="c-passport"'), 'паспорт §35'); + assert.ok(html.includes('/js/chem8_ch3_widgets.js'), 'виджеты главы 3'); + assert.deepEqual(C.shellConfig(11), [2, 8, 1], 'Na: 2,8,1'); + assert.deepEqual(C.shellConfig(20), [2, 8, 8, 2], 'Ca: 2,8,8,2'); + assert.equal(C.nuclide(11, 23).N, 12, '²³Na: 12 нейтронов'); + assert.equal(C.zSym(17), 'Cl', 'Z=17 → Cl'); +}); + test('chem8_engine.js и виджеты — валидный синтаксис', () => { const eng = fs.readFileSync(path.join(ROOT, 'frontend', 'js', 'chem8_engine.js'), 'utf8'); const wid = fs.readFileSync(path.join(ROOT, 'frontend', 'js', 'chem8_intro_widgets.js'), 'utf8'); diff --git a/frontend/css/chem8-textbook.css b/frontend/css/chem8-textbook.css index 8704d76..847c092 100644 --- a/frontend/css/chem8-textbook.css +++ b/frontend/css/chem8-textbook.css @@ -362,6 +362,26 @@ html.dark .drop-box h5{color:var(--pri-l)} .pt-legend i{width:12px;height:12px;border-radius:3px;display:inline-block} .pt-modes{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px} +/* модель атома (§29,33) */ +.as-svg{width:100%;max-width:320px;height:auto;color:var(--pri);display:block;margin:8px auto} +.as-stage{display:flex;justify-content:center} +.as-cfg{margin-top:6px} +.as-zl{font-weight:800;color:var(--pri-d)}html.dark .as-zl{color:var(--pri-l)} + +/* паспорт элемента (§35) */ +.passport{margin-top:10px;padding:13px 16px;border-radius:11px;background:var(--card-soft);border:1px solid var(--border)} +.passport h4{font-family:'Outfit';font-weight:800;margin-bottom:8px;color:var(--pri-d)} +html.dark .passport h4{color:var(--pri-l)} +.passport-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;font-size:.85rem} +.passport-grid div{padding:6px 9px;background:var(--card);border:1px solid var(--border);border-radius:8px} +.passport-grid b{color:var(--pri-d)}html.dark .passport-grid b{color:var(--pri-l)} + +/* орбитали (§32) — статичные SVG */ +.orb-row{display:flex;gap:18px;flex-wrap:wrap;justify-content:center;margin:10px 0} +.orb-item{text-align:center} +.orb-item svg{width:90px;height:90px;color:var(--pri)} +.orb-item .orb-lab{font-size:.82rem;font-weight:700;margin-top:4px} + /* амфотерность (§25) */ .amph-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px} .amph-stage{display:flex;justify-content:center;margin:8px 0} diff --git a/frontend/js/chem8_ch3_widgets.js b/frontend/js/chem8_ch3_widgets.js new file mode 100644 index 0000000..08e7599 --- /dev/null +++ b/frontend/js/chem8_ch3_widgets.js @@ -0,0 +1,97 @@ +/* chem8_ch3_widgets.js — виджеты Главы 3 «Строение атома». + * Использует window.Chem8: atomShell, shellConfig, nuclide, zSym, miniPeriodic, arOf. + */ +(function (W) { + 'use strict'; + function C() { return W.Chem8 || {}; } + function $(id) { return document.getElementById(id); } + + /* §29 — модель атома */ + function mount_p29() { var el = $('c-atom'); if (el && !el._b && C().atomShell) { el._b = 1; C().atomShell(el, { z: 11 }); } } + + /* §30 — нуклид: A = Z + N */ + function mount_p30() { + var el = $('c-nuclide'); if (!el || el._b) return; el._b = 1; + el.innerHTML = '
'; + function calc() { + var z = parseInt($('nz').value, 10), a = parseInt($('na').value, 10); + if (isNaN(z) || isNaN(a) || a < z) { $('n-out').className = 'out bad'; $('n-out').textContent = 'Проверь: A не может быть меньше Z.'; return; } + var nu = C().nuclide(z, a); + $('n-out').className = 'out ok'; + $('n-out').innerHTML = 'Элемент: ' + nu.sym + '
Протонов Z = ' + z + '
Нейтронов N = A − Z = ' + a + ' − ' + z + ' = ' + nu.N + '
Нуклид: ' + nu.sym + '-' + a + '
'; + } + $('nz-go').addEventListener('click', calc); calc(); + } + + /* §31 — средняя Ar по изотопам */ + function mount_p31() { + var el = $('c-iso'); if (!el || el._b) return; el._b = 1; + el.innerHTML = '
' + + '
Пример: хлор — смесь ³⁵Cl (75%) и ³⁷Cl (25%).
'; + function calc() { + var m1 = parseFloat($('im1').value), p1 = parseFloat($('ip1').value), m2 = parseFloat($('im2').value), p2 = parseFloat($('ip2').value); + if ([m1, p1, m2, p2].some(isNaN)) { $('iso-out').className = 'out bad'; $('iso-out').textContent = 'Введите все значения.'; return; } + var ar = (m1 * p1 + m2 * p2) / (p1 + p2); + $('iso-out').className = 'out ok'; + $('iso-out').innerHTML = 'A_r = (' + m1 + '·' + p1 + ' + ' + m2 + '·' + p2 + ') / 100 = ' + (Math.round(ar * 100) / 100).toString().replace('.', ',') + ''; + } + $('iso-go').addEventListener('click', calc); calc(); + } + + /* §33 — строение электронных оболочек (та же модель, акцент на слои) */ + function mount_p33() { var el = $('c-shells'); if (el && !el._b && C().atomShell) { el._b = 1; C().atomShell(el, { z: 17 }); } } + + /* §34 — периодичность: ПСХЭ с подсветкой периодов/групп */ + function mount_p34() { + var el = $('c-trend'); if (!el || el._b || !C().miniPeriodic) return; el._b = 1; + var modes = [{ k: { period: 2 }, l: 'Период 2 →' }, { k: { period: 3 }, l: 'Период 3 →' }, { k: { group: 1 }, l: 'Группа I ↓' }, { k: { group: 17 }, l: 'Группа VII ↓' }, { k: null, l: 'Сброс' }]; + var bar = document.createElement('div'); bar.className = 'pt-modes'; + var grid = document.createElement('div'), note = document.createElement('div'); note.className = 'out'; + var TXT = { + 'p2': 'По периоду слева направо: радиус атома уменьшается, металлические свойства ослабевают, неметаллические — усиливаются.', + 'p3': 'То же в 3-м периоде: от активного металла Na к активному неметаллу Cl.', + 'g1': 'Вниз по группе: радиус растёт, металлические свойства усиливаются (Li → Na → K → ...).', + 'g17': 'Вниз по группе галогенов: неметаллические свойства ослабевают (F самый активный).' + }; + modes.forEach(function (m) { + var b = document.createElement('button'); b.className = 'btn'; b.textContent = m.l; + b.addEventListener('click', function () { + bar.querySelectorAll('.btn').forEach(function (x) { x.classList.remove('primary'); }); b.classList.add('primary'); + if (api) api.highlight(m.k); + var key = m.k ? (m.k.period ? 'p' + m.k.period : 'g' + m.k.group) : null; + note.textContent = key && TXT[key] ? TXT[key] : 'Выбери период или группу — увидишь тренд свойств.'; + }); + bar.appendChild(b); + }); + el.appendChild(bar); el.appendChild(grid); el.appendChild(note); + var api = C().miniPeriodic(grid, {}); + note.textContent = 'Выбери период или группу — увидишь, как меняются свойства.'; + } + + /* §35 — паспорт элемента: клик в ПСХЭ → полная характеристика */ + function mount_p35() { + var el = $('c-passport'); if (!el || el._b || !C().miniPeriodic) return; el._b = 1; + var grid = document.createElement('div'), panel = document.createElement('div'); panel.className = 'passport'; + panel.innerHTML = '

Паспорт элемента

Кликни элемент в системе.
'; + el.appendChild(grid); el.appendChild(panel); + C().miniPeriodic(grid, { onClick: function (sym, info) { + var sh = C().shellConfig(info.z); + var catRu = info.cat === 'metal' ? 'металл' : info.cat === 'nonmetal' ? 'неметалл' : info.cat === 'metalloid' ? 'металлоид' : 'инертный газ'; + panel.innerHTML = '

Паспорт: ' + sym + '

' + + '
Z: ' + info.z + '
' + + '
A_r: ' + (info.ar || '—') + '
' + + '
Период: ' + info.p + '
' + + '
Группа: ' + info.g + '
' + + '
Тип: ' + catRu + '
' + + '
Протонов: ' + info.z + '
' + + '
Электронов: ' + info.z + '
' + + '
Слои e⁻: ' + sh.join(' ) ') + '
' + + '
Внешних e⁻: ' + sh[sh.length - 1] + '
' + + '
'; + if (W.chem8RenderMath) try { W.chem8RenderMath(panel); } catch (e) {} + } }); + } + + W.CHEM8_WIDGETS = { p29: mount_p29, p30: mount_p30, p31: mount_p31, p33: mount_p33 }; + W.FLAG_MOUNTS = { p34: mount_p34, p35: mount_p35 }; +})(window); diff --git a/frontend/js/chem8_svg.js b/frontend/js/chem8_svg.js index a71dd99..60954a9 100644 --- a/frontend/js/chem8_svg.js +++ b/frontend/js/chem8_svg.js @@ -638,6 +638,58 @@ }; } + /* ────────────────────────────────────────────────────────────────────────── + Строение атома (Phase 4). + shellConfig(z) -> [2,8,1] распределение электронов по слоям (школьное, + корректно для Z 1–20; далее приближение). zSym(z) -> символ из ПСХЭ. + ────────────────────────────────────────────────────────────────────────── */ + var _ZSYM = null; + function zSym(z) { + if (!_ZSYM) { _ZSYM = {}; PT.concat(PT7).forEach(function (e) { _ZSYM[e[3]] = e[0]; }); } + return _ZSYM[z] || '?'; + } + function shellConfig(z) { + var caps = [2, 8, 8, 18, 18, 32], out = [], rem = z; + for (var i = 0; i < caps.length && rem > 0; i++) { var t = Math.min(caps[i], rem); out.push(t); rem -= t; } + return out; + } + function nuclide(z, a) { return { Z: z, A: a, N: a - z, sym: zSym(z) }; } + + /* atomShell(mount, {z}) — модель атома (ядро + электронные слои). Слайдер Z 1–20. */ + function atomShell(mount, opts) { + var host = typeof mount === 'string' ? global.document.querySelector(mount) : mount; + if (!host) return null; + opts = opts || {}; + host.innerHTML = '
'; + var zr = host.querySelector('.as-z'), zl = host.querySelector('.as-zl'), stage = host.querySelector('.as-stage'), cfg = host.querySelector('.as-cfg'); + function draw() { + var z = +zr.value, sym = zSym(z), ar = arOf(sym), n = Math.max(0, Math.round(ar) - z), sh = shellConfig(z); + zl.textContent = sym + ' (Z=' + z + ')'; + var cx = 150, cy = 110, R = 18 + sh.length * 26; + var svg = ''; + // слои + for (var s = 0; s < sh.length; s++) { + var r = 30 + s * 26; + svg += ''; + var cnt = sh[s]; + for (var e = 0; e < cnt; e++) { + var ang = (e / cnt) * Math.PI * 2 - Math.PI / 2; + var ex = cx + r * Math.cos(ang), ey = cy + r * Math.sin(ang); + svg += ''; + } + } + svg += ''; + svg += '' + z + 'p⁺'; + svg += '' + n + 'n⁰'; + svg += ''; + stage.innerHTML = svg; + cfg.className = 'out as-cfg'; + cfg.innerHTML = '' + sym + ': распределение электронов по слоям — ' + sh.join(' ) ') + '
Слоёв: ' + sh.length + ' · внешних электронов: ' + sh[sh.length - 1] + ' · протонов: ' + z + ', нейтронов: ' + n + '
'; + } + zr.addEventListener('input', draw); draw(); + return { el: host, draw: draw }; + } + /* ---- Каркасы-заглушки интерактивных виджетов (реализуются по фазам) ---- */ function notImplemented(name) { return function () { @@ -670,7 +722,12 @@ activitySeries: activitySeries, // §14,20 — ряд активности металлов // готово (Phase 3 — периодический закон) miniPeriodic: miniPeriodic, // §26,28,34 — интерактивная ПСХЭ с подсветкой - // заглушки (см. план, разд. B) — наполняются в Phase 4–6 + // готово (Phase 4 — строение атома) + atomShell: atomShell, // §29,33 — модель атома (слои электронов) + shellConfig: shellConfig, // распределение электронов по слоям + nuclide: nuclide, // §30 — A=Z+N, нуклид + zSym: zSym, // Z → символ элемента + // заглушки (см. план, разд. B) — наполняются в Phase 5–6 oxStateCalc: notImplemented('oxStateCalc'), // §42 — калькулятор степени окисления redoxBalancer: notImplemented('redoxBalancer'), // §44 — e-баланс ОВР orbitalDiagram: notImplemented('orbitalDiagram'), // §33 — орбитальная диаграмма diff --git a/frontend/textbooks/chemistry_8_ch3.html b/frontend/textbooks/chemistry_8_ch3.html index 03c8f07..059470d 100644 --- a/frontend/textbooks/chemistry_8_ch3.html +++ b/frontend/textbooks/chemistry_8_ch3.html @@ -6,131 +6,224 @@ -Химия 8 · Глава 3 · «Строение атома и периодичность изменения свойств» - +Химия 8 · Глава 3 · «Строение атома» + + + - + +
-
- - - К разделам - +
-
Глава 3 · § 29–35
-

Строение атома и периодичность изменения свойств

+

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

+
Строение атома, нуклиды и изотопы, электронные облака и орбитали, электронные оболочки, периодичность
- + К разделам +
-
-
-
- -
-
-

Раздел в разработке

-

Интерактивное наглядное наполнение этого раздела (теория, модели, симуляторы, тренажёры и боссы) добавляется поэтапно. Ниже — план параграфов раздела согласно учебнику.

-
-
+
+
+
+

Что внутри атома

+

Атом неделим химически, но состоит из ядра (протоны и нейтроны) и движущихся вокруг электронов. Именно строение электронных оболочек объясняет, почему элементы ведут себя так, а не иначе — и почему работает периодический закон.

+
+ +
Прогресс главы
0%
+
+
+
-
- - Содержание раздела +
Параграфы главы
+ +
§ 29

Строение атома. Атомный номер

+
§ 30

Массовое число атома. Нуклиды

+
§ 31

Изотопы. Явление радиоактивности

+
§ 32

Состояние электронов. Электронное облако. Орбиталь

+
§ 33

Строение электронных оболочек атомов

+
§ 34

Периодичность изменения свойств атомов

+
§ 35

Характеристика элемента по положению в ПС

+

Финал главы

-
    -
  • § 29Строение атома. Атомный номер химического элемента
  • -
  • § 30Массовое число атома. Нуклиды
  • -
  • § 31Изотопы. Явление радиоактивности
  • -
  • § 32Состояние электронов в атоме. Электронное облако. Атомная орбиталь
  • -
  • § 33Строение электронных оболочек атомов
  • -
  • § 34Периодичность изменения свойств атомов химических элементов
  • -
  • § 35Характеристика химического элемента по его положению в периодической системе
  • -
+
-
- Интерактивный учебник «Химия — 8 класс» · Глава 3 · LearnSpace -
+
Интерактивный учебник «Химия — 8 класс» · Глава 3 · «Строение атома» · LearnSpace
+
Достижение!