feat(stereo3d): Фаза 5 — deep-link фигур из учебников + клавиатурная a11y
- openSim('stereo:<figure>') и /lab?stereofig=<figure> открывают нужное тело
(без изменения общего hash-роутера)
- клавиатура на canvas: стрелки=орбита, +/-=зум, R/Home=сброс
- aria-live на readout; bump stereo.js?v=8
- дробление файла на модули отложено по решению пользователя (в бэклоге)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user