diff --git a/frontend/textbooks/geometry_11_ch2.html b/frontend/textbooks/geometry_11_ch2.html
index a157435..1b9e8f8 100644
--- a/frontend/textbooks/geometry_11_ch2.html
+++ b/frontend/textbooks/geometry_11_ch2.html
@@ -392,7 +392,7 @@ function buildParaSelector(){
}
const BUILT=new Set();
-const BUILDERS = { p3:()=>buildStub('p3'), p4:()=>buildStub('p4'), final2:()=>buildStub('final2') };
+const BUILDERS = { p3:buildP3, p4:()=>buildStub('p4'), final2:()=>buildStub('final2') };
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
@@ -407,13 +407,22 @@ function goTo(id){
}
const SIDEBARS = {
- p3:{title:"Шпаргалка § 3", rows:[["Тема", "Пирамида"],["Формула","$V=\\\\frac{1}{3}S_{осн}h$"]]},
+ p3:{title:"Шпаргалка § 3", rows:[
+ ["Пирамида", "основание + апекс"],
+ ["Правильная","основание — правильный многоугольник; высота в центре основания"],
+ ["Апофема $l$","высота боковой грани"],
+ ["Объём","$V=\\\\dfrac{1}{3}S_{осн}\\\\cdot h$"],
+ ["$S_{бок}$ (правильной)","$\\\\dfrac{1}{2}P_{осн}\\\\cdot l$"],
+ ["Боковое ребро","$b=\\\\sqrt{R^2+h^2}$"],
+ ["Апофема","$l=\\\\sqrt{r^2+h^2}$"],
+ ["Усечённая","$V=\\\\dfrac{h}{3}(S_1+S_2+\\\\sqrt{S_1 S_2})$"]
+ ]},
p4:{title:"Шпаргалка § 4", rows:[["Тема", "Конус"],["Формула","$S_{бок}=\\\\pi Rl$"]]},
final2:{title:"Финал раздела 2", rows:[["§ 3–§ 4","теория раздела 2"],["Награда","+50 XP"]]}
};
const TIPS=[
- {sec:'p3',html:"§ 3 «Пирамида» — содержание в разработке. $V=\\\\\\\\frac{1}{3}S_{осн}h$"},
+ {sec:'p3',html:"Главное правило: $V=\\\\\\\\dfrac{1}{3}S_{осн}h$ для любой пирамиды. А для правильной — $S_{бок}=\\\\\\\\dfrac{1}{2}P_{осн}l$, где $l$ — апофема."},
{sec:'p4',html:"§ 4 «Конус» — содержание в разработке. $S_{бок}=\\\\\\\\pi Rl$"},
{sec:'final2',html:"Финал раздела 2 — интегрированные задачи по разделу."}
];
@@ -619,6 +628,364 @@ function wireReadBtn(paraId){
});
}
+/* ===== § 3 «Пирамида» — Wave 1 ===== */
+
+function buildP3(){
+ const box = document.getElementById('p3-body');
+ if(!box) return;
+ let html = '';
+
+ /* === ТЕОРИЯ === */
+
+ html += makeCard('theory', 'Определение и виды', '§ 3.1',
+ '
Пирамида — многогранник, у которого одна грань (основание) — многоугольник, а остальные грани (боковые) — треугольники с общей вершиной.
'
+ + 'Элементы пирамиды:
'
+ + ''
+ + '- Основание — многоугольник.
'
+ + '- Вершина пирамиды (апекс) — общая точка боковых граней.
'
+ + '- Боковые грани — треугольники.
'
+ + '- Боковые рёбра — отрезки от апекса к вершинам основания.
'
+ + '- Высота $h$ — перпендикуляр из апекса к плоскости основания.
'
+ + '
'
+ + 'Правильная пирамида — пирамида, у которой:
'
+ + ''
+ + '- Основание — правильный многоугольник.
'
+ + '- Высота попадает в центр основания.
'
+ + '
'
+ + 'Тогда автоматически выполняются следствия: все боковые рёбра равны, а боковые грани — равные равнобедренные треугольники.
'
+ + 'Апофема $l$ — высота боковой грани, опущенная из апекса к середине стороны основания. У правильной пирамиды все апофемы равны.
');
+
+ html += makeCard('rule', 'Площадь и объём', '§ 3.2',
+ 'Объём (для любой пирамиды):
'
+ + '$$V=\\dfrac{1}{3}\\,S_{осн}\\cdot h$$
'
+ + 'Боковая поверхность правильной пирамиды:
'
+ + '$$S_{бок}=\\dfrac{1}{2}\\,P_{осн}\\cdot l$$
'
+ + 'где $l$ — апофема, $P_{осн}$ — периметр основания.
'
+ + 'Полная поверхность: $S_{полн}=S_{бок}+S_{осн}$.
'
+ + 'Связь между элементами правильной $n$-угольной пирамиды. Пусть $R$ — радиус описанной около основания окружности, $r$ — радиус вписанной, $b$ — боковое ребро, $l$ — апофема, $h$ — высота. Тогда:
'
+ + ''
+ + '- $b^2=R^2+h^2$ — теорема Пифагора в треугольнике «апекс — центр — вершина основания».
'
+ + '- $l^2=r^2+h^2$ — теорема Пифагора в треугольнике «апекс — центр — середина стороны».
'
+ + '
'
+ + 'Пример: правильная 4-угольная пирамида $a=6$, $h=4$
'
+ + '
Основание — квадрат, $r=a/2=3$.
'
+ + '
'
+ + '- $l=\\sqrt{r^2+h^2}=\\sqrt{9+16}=5$ — апофема.
'
+ + '- $S_{осн}=a^2=36$, $P_{осн}=4a=24$.
'
+ + '- $S_{бок}=\\dfrac{1}{2}\\cdot 24\\cdot 5=60$.
'
+ + '- $S_{полн}=60+36=96$.
'
+ + '- $V=\\dfrac{1}{3}\\cdot 36\\cdot 4=48$.
'
+ + '
'
+ + '
');
+
+ html += makeCard('example', 'Усечённая пирамида', '§ 3.3',
+ 'Усечённая пирамида — часть пирамиды, заключённая между основанием и плоскостью, параллельной основанию.
'
+ + 'У неё два основания — подобные многоугольники в параллельных плоскостях. Боковые грани — трапеции.
'
+ + 'Объём усечённой пирамиды:
'
+ + '$$V=\\dfrac{1}{3}\\,h\\bigl(S_1+S_2+\\sqrt{S_1 S_2}\\bigr)$$
'
+ + 'где $S_1,\\,S_2$ — площади оснований, $h$ — высота (расстояние между плоскостями оснований).
'
+ + 'Боковая поверхность правильной усечённой пирамиды:
'
+ + '$$S_{бок}=\\dfrac{1}{2}(P_1+P_2)\\cdot l$$
'
+ + 'где $l$ — апофема (высота боковой трапеции), $P_1,\\,P_2$ — периметры оснований.
'
+ + 'Пример: правильная 4-угольная усечённая, $a_1=6$, $a_2=4$, $h=3$
'
+ + '
$S_1=36$, $S_2=16$.
'
+ + '
Апофема: $l=\\sqrt{h^2+\\left(\\dfrac{a_1-a_2}{2}\\right)^2}=\\sqrt{9+1}=\\sqrt{10}$.
'
+ + '
$V=\\dfrac{1}{3}\\cdot 3\\cdot(36+16+\\sqrt{576})=36+16+24=76$.
'
+ + '
');
+
+ /* === ИНТЕРАКТИВ 1 — 3D-конструктор === */
+ html += ''
+ + ''
+ + '
Меняй число сторон основания $n$, радиус описанной окружности $R$ и высоту $h$. Вращай мышью или выбирай вид. После 4 разных конфигураций — +10 XP.
'
+ + '
'
+ + ''
+ + ''
+ + ''
+ + '
'
+ + '
'
+ + ''
+ + ''
+ + ''
+ + ''
+ + '
'
+ + '
'
+ + '
'
+ + '$a=$—'
+ + '$r=$—'
+ + '$P_{осн}=$—'
+ + '$S_{осн}=$—'
+ + '$l=$—'
+ + '$b=$—'
+ + '$S_{бок}=$—'
+ + '$S_{полн}=$—'
+ + '$V=$—'
+ + '
'
+ + '
Конфигураций изучено: 0 / 4
'
+ + '
';
+
+ /* === ИНТЕРАКТИВ 2 — Калькулятор V и S === */
+ html += ''
+ + ''
+ + '
Введи $n$ (число сторон основания, $3..8$), сторону $a$ и высоту $h$. Калькулятор покажет пошаговый разбор.
'
+ + '
'
+ + ''
+ + ''
+ + ''
+ + '
'
+ + '
'
+ + '
'
+ + '
';
+
+ /* === ИНТЕРАКТИВ 3 — «Найди элемент» (квикфайр) === */
+ html += ''
+ + ''
+ + '
Читай описание отрезка — выбирай, какой это элемент: высота $h$, апофема $l$ или боковое ребро $b$.
'
+ + '
'
+ + '
Верно: 0 / 6
'
+ + '
';
+
+ /* === ИНТЕРАКТИВ 4 — Тренажёр V и S === */
+ html += ''
+ + ''
+ + '
Введи числовой ответ. Допуск $\\pm 0{,}05$ для дробных значений.
'
+ + '
'
+ + '
Решено: 0 / 6
'
+ + '
';
+
+ html += secNav(null, 'p4');
+ html += readButton('p3');
+
+ box.innerHTML = html;
+ renderMath(box);
+
+ /* ====== JS-логика интерактивов ====== */
+
+ /* IV1 — 3D-конструктор */
+ (function(){
+ if(!window.G3D) return;
+ const svg = document.getElementById('p3-iv1-svg');
+ const elN = document.getElementById('p3-iv1-n');
+ const elR = document.getElementById('p3-iv1-R');
+ const elH = document.getElementById('p3-iv1-h');
+ const vN = document.getElementById('p3-iv1-n-v');
+ const vR = document.getElementById('p3-iv1-R-v');
+ const vH = document.getElementById('p3-iv1-h-v');
+ const oA = document.getElementById('p3-iv1-a');
+ const oR = document.getElementById('p3-iv1-r');
+ const oP = document.getElementById('p3-iv1-P');
+ const oSo = document.getElementById('p3-iv1-So');
+ const oL = document.getElementById('p3-iv1-l');
+ const oB = document.getElementById('p3-iv1-b');
+ const oSb = document.getElementById('p3-iv1-Sb');
+ const oSt = document.getElementById('p3-iv1-St');
+ const oV = document.getElementById('p3-iv1-V');
+ const oCnt = document.getElementById('p3-iv1-cnt');
+ if(!svg) return;
+ const scene = G3D.createScene({W:480, H:400, scale:50, camDist:8, rotX:-0.35, rotY:0.7});
+ const seen = new Set();
+ let xpGiven = false;
+
+ function draw(){
+ const n = +elN.value, R = +elR.value, h = +elH.value;
+ vN.textContent = n;
+ vR.textContent = R.toFixed(1);
+ vH.textContent = h.toFixed(1);
+ const mesh = G3D.pyramidMesh(n, R, h);
+ const M = G3D.buildRotMatrix(scene);
+ svg.innerHTML = G3D.renderMesh(mesh, M, scene, {
+ fillBase:'rgba(252,231,243,.55)',
+ fillSide:'rgba(219,234,254,.55)',
+ strokeVisible:'#0f172a',
+ strokeHidden:'#94a3b8'
+ });
+ const a = 2*R*Math.sin(Math.PI/n);
+ const r = R*Math.cos(Math.PI/n);
+ const P = n*a;
+ const So = 0.5*n*R*R*Math.sin(2*Math.PI/n);
+ const l = Math.sqrt(r*r + h*h);
+ const bs = Math.sqrt(R*R + h*h);
+ const Sb = 0.5*P*l;
+ const St = Sb + So;
+ const V = So*h/3;
+ oA.textContent = a.toFixed(2);
+ oR.textContent = r.toFixed(2);
+ oP.textContent = P.toFixed(2);
+ oSo.textContent = So.toFixed(2);
+ oL.textContent = l.toFixed(2);
+ oB.textContent = bs.toFixed(2);
+ oSb.textContent = Sb.toFixed(2);
+ oSt.textContent = St.toFixed(2);
+ oV.textContent = V.toFixed(2);
+ const key = n+'|'+R.toFixed(1)+'|'+h.toFixed(1);
+ seen.add(key);
+ oCnt.textContent = Math.min(seen.size, 4);
+ if(seen.size >= 4 && !xpGiven){
+ xpGiven = true;
+ addXp(10, 'p3-iv1');
+ bumpProgress('p3', 15);
+ const note = document.createElement('div');
+ note.className = 'feedback ok';
+ note.innerHTML = '✓ +10 XP за изучение 4 разных пирамид!';
+ note.style.cssText = 'display:block;margin-top:8px';
+ const host = document.getElementById('p3-iv1');
+ if(host) host.appendChild(note);
+ setTimeout(function(){ try{ note.remove(); }catch(e){} }, 3000);
+ }
+ }
+ draw();
+ G3D.attachOrbit(svg, scene, draw);
+ [elN, elR, elH].forEach(function(el){ el.addEventListener('input', draw); });
+ document.querySelectorAll('#p3-iv1 .g3d-tools .btn').forEach(function(b){
+ b.addEventListener('click', function(){ G3D.presetView(scene, b.dataset.view, draw); });
+ });
+ })();
+
+ /* IV2 — Калькулятор */
+ (function(){
+ const elN = document.getElementById('p3-iv2-n');
+ const elA = document.getElementById('p3-iv2-a');
+ const elH = document.getElementById('p3-iv2-h');
+ const labels = {'p3-iv2-n':'p3-iv2-n-v','p3-iv2-a':'p3-iv2-a-v','p3-iv2-h':'p3-iv2-h-v'};
+ Object.keys(labels).forEach(function(k){
+ const el = document.getElementById(k), lab = document.getElementById(labels[k]);
+ if(!el || !lab) return;
+ const upd = function(){ lab.textContent = el.step === '1' ? el.value : (+el.value).toFixed(1); };
+ el.addEventListener('input', upd); upd();
+ });
+ let xpGiven = false;
+ document.getElementById('p3-iv2-calc').addEventListener('click', function(){
+ const out = document.getElementById('p3-iv2-out');
+ const n = +elN.value, a = +elA.value, h = +elH.value;
+ const r = a / (2*Math.tan(Math.PI/n));
+ const P = n*a;
+ const So = 0.5*P*r;
+ const l = Math.sqrt(r*r + h*h);
+ const Sb = 0.5*P*l;
+ const St = Sb + So;
+ const V = So*h/3;
+ const html2 = ''
+ + '
Правильная '+n+'-угольная пирамида: $a='+a.toFixed(2)+'$, $h='+h.toFixed(2)+'$
'
+ + '
$r=\\dfrac{a}{2\\tan(\\pi/n)}\\approx '+r.toFixed(3)+'$ — радиус вписанной окружности основания.
'
+ + '
$P_{осн}=n\\cdot a='+n+'\\cdot '+a.toFixed(2)+'='+P.toFixed(2)+'$
'
+ + '
$S_{осн}=\\dfrac{1}{2}P\\cdot r\\approx '+So.toFixed(2)+'$
'
+ + '
$l=\\sqrt{r^2+h^2}=\\sqrt{'+(r*r).toFixed(2)+'+'+(h*h).toFixed(2)+'}\\approx '+l.toFixed(3)+'$ — апофема.
'
+ + '
$S_{бок}=\\dfrac{1}{2}P_{осн}\\cdot l\\approx '+Sb.toFixed(2)+'$
'
+ + '
$S_{полн}=S_{бок}+S_{осн}\\approx '+St.toFixed(2)+'$
'
+ + '
$V=\\dfrac{1}{3}S_{осн}\\cdot h\\approx '+V.toFixed(2)+'$
'
+ + '
';
+ out.innerHTML = html2;
+ renderMath(out);
+ if(!xpGiven){
+ xpGiven = true;
+ addXp(10, 'p3-iv2');
+ bumpProgress('p3', 15);
+ }
+ });
+ })();
+
+ /* IV3 — «Найди элемент» (квикфайр) */
+ (function(){
+ const tasks = [
+ { q:'Отрезок от апекса к центру основания.', a:'h' },
+ { q:'Отрезок от апекса к середине стороны основания.', a:'l' },
+ { q:'Отрезок от апекса к вершине основания.', a:'b' },
+ { q:'В равнобедренном треугольнике боковой грани — высота к стороне основания.', a:'l' },
+ { q:'Отрезок из апекса перпендикулярно плоскости основания.', a:'h' },
+ { q:'Длина отрезка апекс — вершина основания.', a:'b' }
+ ];
+ const list = document.getElementById('p3-iv3-list');
+ const scoreEl = document.getElementById('p3-iv3-score');
+ const solved = new Set();
+ let xpGiven = false;
+ const NAMES = {h:'Высота $h$', l:'Апофема $l$', b:'Боковое ребро $b$'};
+ list.innerHTML = tasks.map(function(t, i){
+ return ''
+ + '
Задание '+(i+1)+'. '+t.q+'
'
+ + '
'
+ + ''
+ + ''
+ + ''
+ + '
'
+ + '
'
+ + '
';
+ }).join('');
+ renderMath(list);
+ list.querySelectorAll('button[data-i]').forEach(function(b){
+ b.addEventListener('click', function(){
+ const i = +b.dataset.i, v = b.dataset.v, t = tasks[i];
+ const fb = document.getElementById('p3-iv3-fb-'+i);
+ if(solved.has(i)) return;
+ if(v === t.a){
+ feedback(fb, true, '✓ Верно — это '+NAMES[t.a]+'.');
+ solved.add(i);
+ scoreEl.textContent = solved.size;
+ if(solved.size === tasks.length && !xpGiven){
+ xpGiven = true;
+ addXp(15, 'p3-iv3');
+ bumpProgress('p3', 25);
+ }
+ } else {
+ feedback(fb, false, '✗ Не то. Подумай ещё раз.');
+ }
+ });
+ });
+ })();
+
+ /* IV4 — Тренажёр V и S */
+ (function(){
+ const tasks = [
+ { q:'Правильная 4-угольная пирамида: $a=6$, $h=4$. $V=\\,?$', a:48, tol:0.05 },
+ { q:'Та же пирамида ($a=6$, $h=4$). Апофема $l=\\,?$', a:5, tol:0.05 },
+ { q:'Та же пирамида. $S_{бок}=\\,?$', a:60, tol:0.05 },
+ { q:'Правильная 3-угольная пирамида: $a=6$, $h=4$. $V=\\,?$ (точность 0,01)', a:20.78, tol:0.05 },
+ { q:'Правильная 6-угольная пирамида: $a=4$, $h=3$. $V=\\,?$ (точность 0,01)', a:41.57, tol:0.05 },
+ { q:'Правильная 4-угольная усечённая: $a_1=6$, $a_2=4$, $h=3$. $V=\\,?$', a:76, tol:0.05 }
+ ];
+ const list = document.getElementById('p3-iv4-list');
+ const scoreEl = document.getElementById('p3-iv4-score');
+ const solved = new Set();
+ let xpGiven = false;
+ list.innerHTML = tasks.map(function(t, i){
+ return ''
+ + '
Задача '+(i+1)+'. '+t.q+'
'
+ + '
'
+ + ''
+ + ''
+ + '
'
+ + '
'
+ + '
';
+ }).join('');
+ renderMath(list);
+ list.querySelectorAll('button[data-i]').forEach(function(b){
+ b.addEventListener('click', function(){
+ const i = +b.dataset.i, t = tasks[i];
+ const inp = document.getElementById('p3-iv4-inp-'+i);
+ const fb = document.getElementById('p3-iv4-fb-'+i);
+ const raw = (inp.value || '').replace(',', '.').trim();
+ const val = parseFloat(raw);
+ if(!isFinite(val)){ feedback(fb, false, '✗ Введи число'); return; }
+ if(Math.abs(val - t.a) <= t.tol){
+ feedback(fb, true, '✓ Верно!');
+ if(!solved.has(i)){
+ solved.add(i);
+ scoreEl.textContent = solved.size;
+ if(solved.size === tasks.length && !xpGiven){
+ xpGiven = true;
+ addXp(15, 'p3-iv4');
+ bumpProgress('p3', 25);
+ setTimeout(function(){ achievement('p3_done'); }, 400);
+ }
+ }
+ } else {
+ feedback(fb, false, '✗ Не точно. Пересчитай аккуратно.');
+ }
+ });
+ });
+ })();
+
+ wireReadBtn('p3');
+}
+
/* ===== STUB BUILDER — единый для всех параграфов раздела (Phase 0) ===== */
function buildStub(id){