feat(chemistry7): визуальный апгрейд V0 (движок) + пилот V1

chem7_anim.js — анимационный движок (window.Chem7Anim): RAF-цикл с паузой
вне экрана (IntersectionObserver), prefers-reduced-motion, headless-guard
(jsdom-safe: молекулы на SVG, canvas без getContext в тестах),
molecule3d (вращающаяся 3D-модель, drag), separation (частицы:
фильтр/выпаривание/магнит/отстаивание/перегонка), colorMorph, confettiSmall.

Пилот в Главе 1:
- §5/§6: статичные галереи → вращающиеся 3D-модели (H2/O2/O3/N2, H2O/CO2/CH4/NH3) с переключателем;
- §2/ПР1: при верном методе разделения проигрывается анимация частиц.

Тесты chem7: 16/16 pass; полный прогон 162/165 (3 — baseline Auth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 19:35:44 +03:00
parent c1ef1ecee9
commit f620562124
4 changed files with 274 additions and 29 deletions
+16
View File
@@ -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_anim.js': readF('frontend/js/chem7_anim.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/chem7_ch3_widgets.js': readF('frontend/js/chem7_ch3_widgets.js'),
@@ -98,6 +99,21 @@ test('ch1 Волна 2: интерактивы §4–§6 монтируются
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch1 V-пилот: 3D-молекулы §5/§6 + анимация разделения §2', async () => {
const { doc, errors } = await loadDom('chemistry_7_ch1.html');
doc.defaultView.goTo('p5'); await wait(120);
assert.ok(doc.querySelector('#p5-gal .mv-b'), 'переключатель молекул §5');
assert.ok(doc.querySelector('#p5-gal-stage svg circle'), '3D-молекула §5 (SVG)');
doc.defaultView.goTo('p6'); await wait(120);
assert.ok(doc.querySelector('#p6-gal-stage svg circle'), '3D-молекула §6 (SVG)');
doc.defaultView.goTo('p2'); await wait(120);
const btn = [...doc.querySelectorAll('#p2-sep .c7-m')].find(b => b.dataset.m === 'Фильтрование');
assert.ok(btn, 'кнопка верного метода §2 найдена');
btn.dispatchEvent(new doc.defaultView.Event('click', { bubbles: true })); await wait(50);
assert.ok(doc.querySelector('#p2-sep-anim canvas'), 'сцена разделения §2 (canvas)');
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);