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 = '
Z (протоны) A (масс. число) Найти N
';
+ 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 = 'Изотоп 1: масса доля, %
'
+ + 'Изотоп 2: масса доля, % Средняя A_r
Пример: хлор — смесь ³⁵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 = 'Элемент (Z)
';
+ 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
+
Строение атома, нуклиды и изотопы, электронные облака и орбитали, электронные оболочки, периодичность
-
-
-
-
-
Раздел в разработке
-
Интерактивное наглядное наполнение этого раздела (теория, модели, симуляторы, тренажёры и боссы) добавляется поэтапно. Ниже — план параграфов раздела согласно учебнику.
-
-
+
+
+
+ Что внутри атома
+ Атом неделим химически, но состоит из ядра (протоны и нейтроны) и движущихся вокруг электронов. Именно строение электронных оболочек объясняет, почему элементы ведут себя так, а не иначе — и почему работает периодический закон.
+
+
-
-
- Содержание раздела
+
+
+
+
+
+
+
+
+
+
-
- § 29 Строение атома. Атомный номер химического элемента
- § 30 Массовое число атома. Нуклиды
- § 31 Изотопы. Явление радиоактивности
- § 32 Состояние электронов в атоме. Электронное облако. Атомная орбиталь
- § 33 Строение электронных оболочек атомов
- § 34 Периодичность изменения свойств атомов химических элементов
- § 35 Характеристика химического элемента по его положению в периодической системе
-
+
-
+
+