diff --git a/frontend/js/labs/lab-init.js b/frontend/js/labs/lab-init.js
index f47c4d2..9a46798 100644
--- a/frontend/js/labs/lab-init.js
+++ b/frontend/js/labs/lab-init.js
@@ -124,6 +124,7 @@
if (id === 'crystal') _openCrystal();
if (id === 'orbitals') _openOrbitals();
if (id === 'stereo') _openStereo();
+ if (id.startsWith('stereo:')) { _openStereo(id.split(':')[1]); }
if (id === 'chemsandbox') _openChemSandbox();
if (id === 'celldivision') _openCellDivision();
if (id === 'photosynthesis') _openPhotosynthesis();
diff --git a/frontend/js/labs/stereo.js b/frontend/js/labs/stereo.js
index 11ba084..3550290 100644
--- a/frontend/js/labs/stereo.js
+++ b/frontend/js/labs/stereo.js
@@ -112,6 +112,23 @@ class StereoSim {
this._invalidate();
}, { passive: false });
+ // Keyboard navigation (a11y) — works when the canvas is focused.
+ on(el, 'keydown', e => {
+ const STEP = 0.12;
+ let handled = true;
+ switch (e.key) {
+ case 'ArrowLeft': this._rotY -= STEP; this._autoSpin = false; break;
+ case 'ArrowRight': this._rotY += STEP; this._autoSpin = false; break;
+ case 'ArrowUp': this._rotX = Math.min(1.4, this._rotX + STEP); this._autoSpin = false; break;
+ case 'ArrowDown': this._rotX = Math.max(-1.4, this._rotX - STEP); this._autoSpin = false; break;
+ case '+': case '=': this._dist = Math.max(4, this._dist - 1); break;
+ case '-': case '_': this._dist = Math.min(40, this._dist + 1); break;
+ case 'r': case 'R': case 'Home': this.resetView(); break;
+ default: handled = false;
+ }
+ if (handled) { e.preventDefault(); this._idleTime = 0; this._invalidate(); }
+ });
+
// WebGL context loss / restore — keep the page alive if the GPU resets.
on(el, 'webglcontextlost', e => { e.preventDefault(); this._contextLost = true; this.stop(); }, false);
on(el, 'webglcontextrestored', () => {
@@ -3706,10 +3723,14 @@ class StereoSim {
dodecahedron: ['a'],
};
- function _openStereo() {
+ function _openStereo(figureType) {
document.getElementById('sim-topbar-title').textContent = 'Стереометрия 3D';
_simShow('sim-stereo');
document.getElementById('stereo-stats').style.display = '';
+ // Deep-link from a textbook: openSim('stereo:pyramid') or /lab?stereofig=pyramid
+ if (!figureType) {
+ try { figureType = new URLSearchParams(location.search).get('stereofig') || null; } catch (_) {}
+ }
requestAnimationFrame(() => requestAnimationFrame(() => {
if (!stereoSim) {
stereoSim = new StereoSim(document.getElementById('stereo-container'));
@@ -3718,6 +3739,10 @@ class StereoSim {
stereoSim.fit();
stereoSim.play();
}
+ if (figureType && STEREO_PARAM_MAP[figureType]) {
+ const btn = document.querySelector(`.stereo-fig-btn[onclick*="'${figureType}'"]`);
+ setStereoFigure(figureType, btn);
+ }
_stereoShowParams(stereoSim.figureType || 'cube');
_stereoUpdateUI(stereoSim.info());
_stereoUpdateFormulas();
diff --git a/frontend/lab.html b/frontend/lab.html
index 6e0b251..eb48d2e 100644
--- a/frontend/lab.html
+++ b/frontend/lab.html
@@ -4819,7 +4819,7 @@
-
+
diff --git a/plans/STEREO_3D_IMPROVEMENT.md b/plans/STEREO_3D_IMPROVEMENT.md
index b8b31f8..967ec27 100644
--- a/plans/STEREO_3D_IMPROVEMENT.md
+++ b/plans/STEREO_3D_IMPROVEMENT.md
@@ -43,11 +43,13 @@
Бэклог: подсветка грани по ховеру (нужен точный raycast по логическим граням); градиентный/бумажный фон (учесть захват в скриншоте).
-## Фаза 5 — Интеграция и архитектура
+## Фаза 5 — Интеграция и архитектура — ГОТОВО (без дробления файла, по решению пользователя)
-- [ ] 5.1 Разбить `stereo.js` на модули (builders / sections / tools / ui), вынести константы и цвета.
-- [ ] 5.2 Связать симуляцию с учебниками 10–11 (открывать тело/сечение из §-канвы и задач).
-- [ ] 5.3 Доступность: клавиатура, `aria`, фокус.
+- [~] 5.1 Дробление `stereo.js` на модули — **отложено по решению пользователя** (риск регрессий, не видно пользователю). Остаётся в бэклоге.
+- [x] 5.2 Deep-link фигуры из учебников без изменения общего hash-роутера: `openSim('stereo:')` и `/lab?stereofig=#sim/stereo`. `_openStereo(figureType)` применяет фигуру и подсвечивает кнопку. Допустимые: cube, parallelepiped, prism, pyramid, truncpyramid, tetrahedron, octahedron, icosahedron, dodecahedron, cylinder, cone, trunccone, sphere.
+- [x] 5.3 a11y: клавиатурная навигация по сфокусированному canvas — стрелки (орбита), +/− (зум), R/Home (сброс); `tabindex`/`role`/`aria-label` на canvas (Фаза 0), `aria-pressed`/`aria-label` на кнопках вида (Фаза 1), `aria-live` на readout.
+
+Бэклог Фазы 5: модульное дробление файла; deep-link конкретного сечения/инструмента (не только фигуры).
---