diff --git a/frontend/js/stereo3d.js b/frontend/js/stereo3d.js index 89373e9..f562679 100644 --- a/frontend/js/stereo3d.js +++ b/frontend/js/stereo3d.js @@ -714,4 +714,74 @@ function formatLabel(s){ S.Scene = Scene; S.formatLabel = formatLabel; +/* ============================================================ */ +/* Drag-to-rotate */ +/* ============================================================ */ +/* + * STEREO3D.attachDragRotate(target, scene, onChange?) + * + * target — SVGElement или контейнер-HTMLElement, на который повешен + * результат scene.render(). + * scene — экземпляр STEREO3D.Scene (мы мутируем scene.rotX / scene.rotY). + * onChange — необязательный callback после изменения углов. + * Если не передан и target не SVG — по умолчанию + * пересобираем содержимое контейнера через scene.render(). + * + * Возвращает функцию detach() — для удаления слушателей. + */ +S.attachDragRotate = function attachDragRotate(target, scene, onChange){ + if (!target || !scene) return function(){}; + // По умолчанию для контейнера-HTMLElement делаем re-render через innerHTML. + const isSvg = (target.tagName && String(target.tagName).toLowerCase() === 'svg'); + const defaultOnChange = isSvg ? null : function(){ target.innerHTML = scene.render(); }; + const cb = onChange || defaultOnChange; + + let dragging = false, lastX = 0, lastY = 0; + try { target.style.touchAction = 'none'; } catch(_){} + try { target.style.cursor = 'grab'; } catch(_){} + + function onDown(e){ + dragging = true; + try { target.style.cursor = 'grabbing'; } catch(_){} + const p = (e.touches && e.touches[0]) ? e.touches[0] : e; + lastX = p.clientX; lastY = p.clientY; + if (e.preventDefault) e.preventDefault(); + } + function onMove(e){ + if (!dragging) return; + const p = (e.touches && e.touches[0]) ? e.touches[0] : e; + const dx = p.clientX - lastX, dy = p.clientY - lastY; + scene.rotY = (scene.rotY || 0) + dx * 0.012; + scene.rotX = (scene.rotX || 0) + dy * 0.012; + if (scene.rotX > 1.4) scene.rotX = 1.4; + if (scene.rotX < -1.4) scene.rotX = -1.4; + lastX = p.clientX; lastY = p.clientY; + if (cb) cb(scene); + if (e.preventDefault) e.preventDefault(); + } + function onUp(){ + if (!dragging) return; + dragging = false; + try { target.style.cursor = 'grab'; } catch(_){} + } + + target.addEventListener('mousedown', onDown, { passive: false }); + target.addEventListener('touchstart', onDown, { passive: false }); + window.addEventListener('mousemove', onMove, { passive: false }); + window.addEventListener('touchmove', onMove, { passive: false }); + window.addEventListener('mouseup', onUp); + window.addEventListener('touchend', onUp); + window.addEventListener('touchcancel',onUp); + + return function detach(){ + target.removeEventListener('mousedown', onDown); + target.removeEventListener('touchstart', onDown); + window.removeEventListener('mousemove', onMove); + window.removeEventListener('touchmove', onMove); + window.removeEventListener('mouseup', onUp); + window.removeEventListener('touchend', onUp); + window.removeEventListener('touchcancel',onUp); + }; +}; + })(); diff --git a/frontend/textbooks/geometry_10_r1.html b/frontend/textbooks/geometry_10_r1.html index a4fef95..f0038e0 100644 --- a/frontend/textbooks/geometry_10_r1.html +++ b/frontend/textbooks/geometry_10_r1.html @@ -245,7 +245,7 @@ const PARAS = [ { id:'p1', num:'§ 1', name:'Пространственные фигуры', sub:'Призма · пирамида · цилиндр · конус · шар' }, { id:'p2', num:'§ 2', name:'Прямые и плоскости', sub:'3 аксиомы стереометрии + следствия' }, { id:'p3', num:'§ 3', name:'Построения сечений', sub:'Метод следов · max 6-угольник у куба' }, - { id:'final', num:'★', name:'Финал раздела', sub:'4 интегрированных босса', final:true } + { id:'final', num:'★', name:'Финал раздела', sub:'4 интегрированных босса', final:true } ]; PARAS.forEach(p => { STATE.progress[p.id] = 0; }); @@ -579,7 +579,7 @@ function buildP1(){ html += makeCard('rule', 'Формула Эйлера', '§ 1.3', '
Для любого выпуклого многогранника:
' + '$$В - Р + Г = 2$$
' - + 'Куб: $8 - 12 + 6 = 2$ ✓. Тетраэдр: $4 - 6 + 4 = 2$ ✓.
' + + 'Куб: $8 - 12 + 6 = 2$ ✓. Тетраэдр: $4 - 6 + 4 = 2$ ✓.
' + '