diff --git a/frontend/textbooks/geometry_11_ch3.html b/frontend/textbooks/geometry_11_ch3.html
index d5e6632..b1a705d 100644
--- a/frontend/textbooks/geometry_11_ch3.html
+++ b/frontend/textbooks/geometry_11_ch3.html
@@ -396,7 +396,7 @@ function buildParaSelector(){
}
const BUILT=new Set();
-const BUILDERS = { p5:()=>buildStub('p5'), p6:()=>buildStub('p6'), p7:()=>buildStub('p7'), final3:()=>buildStub('final3') };
+const BUILDERS = { p5:buildP5, p6:()=>buildStub('p6'), p7:()=>buildStub('p7'), final3:()=>buildStub('final3') };
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);
@@ -411,14 +411,24 @@ function goTo(id){
}
const SIDEBARS = {
- p5:{title:"Шпаргалка § 5", rows:[["Тема", "Сфера"],["Формула","$(x-a)^2+(y-b)^2+(z-c)^2=R^2$"]]},
+ p5:{title:"Шпаргалка § 5", rows:[
+ ["Тема", "Сфера"],
+ ["Сфера", "множество точек, $|OM|=R$"],
+ ["Шар", "множество точек, $|OM|\\\\le R$"],
+ ["Уравнение", "$(x-a)^2+(y-b)^2+(z-c)^2=R^2$"],
+ ["Касательная", "плоскость $\\\\perp$ радиусу $OM$"],
+ ["Сечение", "окружность $r=\\\\sqrt{R^2-d^2}$"],
+ ["Большой круг", "$d=0$, $r=R$"],
+ ["Площадь", "$S=4\\\\pi R^2$"],
+ ["Объём шара", "$V=\\\\tfrac{4}{3}\\\\pi R^3$"]
+ ]},
p6:{title:"Шпаргалка § 6", rows:[["Тема", "Шар"],["Формула","$S=4\\\\pi R^2$, $V=\\\\frac{4}{3}\\\\pi R^3$"]]},
p7:{title:"Шпаргалка § 7", rows:[["Тема", "Правильные многогранники"],["Формула","5 платоновых тел"]]},
final3:{title:"Финал раздела 3", rows:[["§ 5–§ 7","теория раздела 3"],["Награда","+50 XP"]]}
};
const TIPS=[
- {sec:'p5',html:"§ 5 «Сфера» — содержание в разработке. $(x-a)^2+(y-b)^2+(z-c)^2=R^2$"},
+ {sec:'p5',html:"Сфера: $|OM|=R$. Уравнение $(x-a)^2+(y-b)^2+(z-c)^2=R^2$. Сечение плоскостью — окружность $r=\\\\sqrt{R^2-d^2}$."},
{sec:'p6',html:"§ 6 «Шар» — содержание в разработке. $S=4\\\\\\\\pi R^2$, $V=\\\\\\\\frac{4}{3}\\\\\\\\pi R^3$"},
{sec:'p7',html:"§ 7 «Правильные многогранники» — содержание в разработке. 5 платоновых тел"},
{sec:'final3',html:"Финал раздела 3 — интегрированные задачи по разделу."}
@@ -625,6 +635,357 @@ function wireReadBtn(paraId){
});
}
+/* ===== § 5 «Сфера» — Wave 1 ===== */
+
+function buildP5(){
+ const box = document.getElementById('p5-body');
+ if(!box) return;
+ let html = '';
+
+ /* === ТЕОРИЯ === */
+
+ html += makeCard('theory', 'Определение и элементы', '§ 5.1',
+ '
Сфера — множество всех точек пространства, равноудалённых от заданной точки $O$ (центра ).
'
+ + 'Шар — множество точек, для которых $|OM|\\le R$, где $O$ — центр, $R$ — радиус. Шар ограничен сферой; сфера — поверхность шара.
'
+ + 'Элементы:
'
+ + ''
+ + 'Центр $O$ — точка, от которой все точки сферы равноудалены. '
+ + 'Радиус $R=|OM|$ для любой точки $M$ сферы. '
+ + 'Диаметр — отрезок через центр между двумя точками сферы. Длина $d=2R$. '
+ + 'Хорда — отрезок между двумя точками сферы (не обязательно через центр). '
+ + ' '
+ + 'Уравнение сферы в декартовой системе координат. Для центра $C(a,b,c)$ и радиуса $R$:
'
+ + '$$(x-a)^2+(y-b)^2+(z-c)^2=R^2$$
'
+ + 'Если центр в начале координат:
'
+ + '$$x^2+y^2+z^2=R^2$$
'
+ + 'Уравнение выражает то, что квадрат расстояния от точки $(x,y,z)$ до центра $C$ равен $R^2$.
');
+
+ html += makeCard('rule', 'Касательная плоскость', '§ 5.2',
+ 'Касательная плоскость к сфере — плоскость, имеющая со сферой ровно одну общую точку. Эту точку называют точкой касания .
'
+ + 'Признак касания (необходимый и достаточный):
'
+ + 'Плоскость касается сферы в точке $M$ тогда и только тогда, когда она перпендикулярна радиусу $OM$ , проведённому в точку касания.
'
+ + 'Через каждую точку сферы можно провести единственную касательную плоскость.
'
+ + 'Расстояние от центра сферы до касательной плоскости равно радиусу $R$.
'
+ + 'Пример: касательная к сфере $x^2+y^2+z^2=25$ в точке $M(3;4;0)$ '
+ + '
Центр $O(0;0;0)$, радиус-вектор $\\overrightarrow{OM}=(3;4;0)$ направлен из центра в точку касания.
'
+ + '
Значит нормаль касательной плоскости $\\vec{n}=(3;4;0)$. Уравнение плоскости в точке $M$:
'
+ + '
$3(x-3)+4(y-4)+0\\cdot(z-0)=0$, то есть $3x+4y=25$.
'
+ + '
');
+
+ html += makeCard('example', 'Сечения сферы и большой круг', '§ 5.3',
+ 'Сечение сферы плоскостью , пересекающей её — всегда окружность . Это следует из того, что точки сечения равноудалены от проекции центра сферы на эту плоскость.
'
+ + 'Связь радиуса сечения $r$, радиуса сферы $R$ и расстояния $d$ от центра сферы до секущей плоскости:
'
+ + '$$r=\\sqrt{R^2-d^2}$$
'
+ + 'Три случая:
'
+ + ''
+ + '$d'
+ + '$d=R$ — плоскость касается сферы; «сечение» вырождается в точку. '
+ + '$d>R$ — общих точек нет. '
+ + ' '
+ + 'Большой круг — сечение, проходящее через центр сферы ($d=0$). Радиус большого круга максимален и равен $R$.
'
+ + 'Любые два больших круга пересекаются по диаметру сферы.
'
+ + 'Пример: $R=5$, $d=3$ '
+ + '
$r=\\sqrt{R^2-d^2}=\\sqrt{25-9}=\\sqrt{16}=4$.
'
+ + '
Сечение — окружность радиуса $4$ в секущей плоскости.
'
+ + '
');
+
+ /* === ИНТЕРАКТИВ 1 — 3D-визуализатор сферы === */
+ html += ''
+ + ''
+ + '
Меняй радиус $R$ ползунком, вращай мышью или выбирай вид. После 4 разных значений $R$ — +10 XP.
'
+ + '
'
+ + '$R$ (радиус):2.0 '
+ + '
'
+ + '
'
+ + 'Изо '
+ + 'Спереди '
+ + 'Сверху '
+ + 'Сбоку '
+ + '
'
+ + '
'
+ + '
'
+ + '$R=$— '
+ + '$d=2R=$— '
+ + '$S=4\\pi R^2\\approx$— '
+ + '$V=\\tfrac{4}{3}\\pi R^3\\approx$— '
+ + '
'
+ + '
Разных $R$ изучено: 0 / 4
'
+ + '
';
+
+ /* === ИНТЕРАКТИВ 2 — Уравнение сферы и проверка точки === */
+ html += ''
+ + ''
+ + '
Введи центр $C(a;b;c)$ и радиус $R$ — получи уравнение. Затем введи точку $M(x_0;y_0;z_0)$ — узнай, лежит ли она на сфере, внутри шара или вне.
'
+ + '
'
+ + '
'
+ + '
';
+
+ /* === ИНТЕРАКТИВ 3 — Сечение сферы (квикфайр 6) === */
+ html += ''
+ + ''
+ + '
Сравни $d$ и $R$. Выбери, что получится при пересечении: окружность, точка (касание) или ничего.
'
+ + '
'
+ + '
Верно: 0 / 6
'
+ + '
';
+
+ /* === ИНТЕРАКТИВ 4 — Тренажёр === */
+ html += ''
+ + ''
+ + '
Введи числовой ответ. Допуск $\\pm 0{,}05$ для дробных значений.
'
+ + '
'
+ + '
Решено: 0 / 6
'
+ + '
';
+
+ html += secNav(null, 'p6');
+ html += readButton('p5');
+
+ box.innerHTML = html;
+ renderMath(box);
+
+ /* ====== JS-логика интерактивов ====== */
+
+ /* IV1 — 3D-визуализатор */
+ (function(){
+ if(!window.G3D) return;
+ const svg = document.getElementById('p5-iv1-svg');
+ const elR = document.getElementById('p5-iv1-R');
+ const vR = document.getElementById('p5-iv1-R-v');
+ const oR = document.getElementById('p5-iv1-R-o');
+ const oD = document.getElementById('p5-iv1-d-o');
+ const oS = document.getElementById('p5-iv1-S-o');
+ const oV = document.getElementById('p5-iv1-V-o');
+ const oCnt = document.getElementById('p5-iv1-cnt');
+ if(!svg) return;
+ const scene = G3D.createScene({W:480, H:400, scale:60, camDist:8, rotX:-0.35, rotY:0.7});
+ const seen = new Set();
+ let xpGiven = false;
+ const PI = 3.14;
+
+ function draw(){
+ const R = +elR.value;
+ vR.textContent = R.toFixed(1);
+ const sph = G3D.sphereWireframe(R, 6, 12);
+ const M = G3D.buildRotMatrix(scene);
+ svg.innerHTML = G3D.renderSphereWireframe(sph, M, scene);
+ oR.textContent = R.toFixed(1);
+ oD.textContent = (2*R).toFixed(1);
+ oS.textContent = (4*PI*R*R).toFixed(2);
+ oV.textContent = ((4/3)*PI*R*R*R).toFixed(2);
+ const key = R.toFixed(1);
+ seen.add(key);
+ oCnt.textContent = Math.min(seen.size, 4);
+ if(seen.size >= 4 && !xpGiven){
+ xpGiven = true;
+ addXp(10, 'p5-iv1');
+ bumpProgress('p5', 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('p5-iv1');
+ if(host) host.appendChild(note);
+ setTimeout(function(){ try{ note.remove(); }catch(e){} }, 3000);
+ }
+ }
+ draw();
+ G3D.attachOrbit(svg, scene, draw);
+ elR.addEventListener('input', draw);
+ document.querySelectorAll('#p5-iv1 .g3d-tools .btn').forEach(function(b){
+ b.addEventListener('click', function(){ G3D.presetView(scene, b.dataset.view, draw); });
+ });
+ })();
+
+ /* IV2 — Уравнение сферы + проверка точки */
+ (function(){
+ let xpGiven = false, eqShown = false, ptChecked = false;
+ function parseNum(id){ const v = (document.getElementById(id).value||'').replace(',', '.').trim(); const x = parseFloat(v); return isFinite(x) ? x : NaN; }
+ function fmtSign(v, useVar){
+ // возвращает строку для "(x - a)" с учётом знака
+ if(v === 0) return useVar;
+ if(v > 0) return '('+useVar+' - '+fmt(v)+')';
+ return '('+useVar+' + '+fmt(-v)+')';
+ }
+ function maybeXp(){
+ if(eqShown && ptChecked && !xpGiven){
+ xpGiven = true;
+ addXp(10, 'p5-iv2');
+ bumpProgress('p5', 15);
+ }
+ }
+ document.getElementById('p5-iv2-show').addEventListener('click', function(){
+ const a = parseNum('p5-iv2-a'), b = parseNum('p5-iv2-b'), c = parseNum('p5-iv2-c'), R = parseNum('p5-iv2-R');
+ const out = document.getElementById('p5-iv2-eq');
+ if(!isFinite(a)||!isFinite(b)||!isFinite(c)||!isFinite(R)||R<=0){
+ out.innerHTML = '✗ Введи корректные числа ($R>0$). ';
+ renderMath(out);
+ return;
+ }
+ const fx = fmtSign(a, 'x');
+ const fy = fmtSign(b, 'y');
+ const fz = fmtSign(c, 'z');
+ const R2 = R*R;
+ const eq = '$$' + fx + '^2 + ' + fy + '^2 + ' + fz + '^2 = ' + fmt(R2) + '$$';
+ out.innerHTML = 'Центр $C('+fmt(a)+';'+fmt(b)+';'+fmt(c)+')$, радиус $R='+fmt(R)+'$.
'
+ + 'Уравнение сферы:
' + eq;
+ renderMath(out);
+ eqShown = true;
+ maybeXp();
+ });
+ document.getElementById('p5-iv2-check').addEventListener('click', function(){
+ const a = parseNum('p5-iv2-a'), b = parseNum('p5-iv2-b'), c = parseNum('p5-iv2-c'), R = parseNum('p5-iv2-R');
+ const x0 = parseNum('p5-iv2-x0'), y0 = parseNum('p5-iv2-y0'), z0 = parseNum('p5-iv2-z0');
+ const out = document.getElementById('p5-iv2-pt');
+ if(!isFinite(a)||!isFinite(b)||!isFinite(c)||!isFinite(R)||R<=0||!isFinite(x0)||!isFinite(y0)||!isFinite(z0)){
+ out.innerHTML = '✗ Введи корректные числа. ';
+ renderMath(out);
+ return;
+ }
+ const dx = x0 - a, dy = y0 - b, dz = z0 - c;
+ const lhs = dx*dx + dy*dy + dz*dz;
+ const R2 = R*R;
+ const dist = Math.sqrt(lhs);
+ let verdict = '', color = '';
+ if(Math.abs(lhs - R2) < 1e-6){ verdict = 'лежит на сфере '; color = 'var(--ok)'; }
+ else if(lhs < R2){ verdict = 'лежит внутри шара '; color = '#2563eb'; }
+ else { verdict = 'лежит вне шара '; color = 'var(--warn)'; }
+ out.innerHTML = 'Подставляем $M('+fmt(x0)+';'+fmt(y0)+';'+fmt(z0)+')$:
'
+ + '$('+fmt(x0)+'-'+fmt(a)+')^2+('+fmt(y0)+'-'+fmt(b)+')^2+('+fmt(z0)+'-'+fmt(c)+')^2 = '
+ + fmt(dx*dx)+'+'+fmt(dy*dy)+'+'+fmt(dz*dz)+' = '+fmt(lhs)+'$
'
+ + 'Сравниваем с $R^2='+fmt(R2)+'$. Расстояние $|CM|='+fmt(+dist.toFixed(4))+'$.
'
+ + '✓ Точка $M$ '+verdict+'.
';
+ renderMath(out);
+ ptChecked = true;
+ maybeXp();
+ });
+ })();
+
+ /* IV3 — Сечение сферы (квикфайр) */
+ (function(){
+ const tasks = [
+ { R:5, d:3, a:'circle' },
+ { R:5, d:5, a:'point' },
+ { R:5, d:6, a:'none' },
+ { R:10, d:0, a:'circle' },
+ { R:3, d:3.5, a:'none' },
+ { R:7, d:7, a:'point' }
+ ];
+ const NAMES = {circle:'Окружность', point:'Точка', none:'Нет общих точек'};
+ const HINTS = {
+ circle: function(t){ const r = Math.sqrt(t.R*t.R - t.d*t.d); return '$dR='+t.R+'$, плоскость не пересекает сферу.'; }
+ };
+ const list = document.getElementById('p5-iv3-list');
+ const scoreEl = document.getElementById('p5-iv3-score');
+ const solved = new Set();
+ let xpGiven = false;
+ list.innerHTML = tasks.map(function(t, i){
+ return ''
+ + '
Задание '+(i+1)+'. Сфера $R='+t.R+'$, расстояние от центра до плоскости $d='+t.d+'$. Что получится?
'
+ + '
'
+ + 'Окружность '
+ + 'Точка '
+ + 'Нет общих точек '
+ + '
'
+ + '
'
+ + '
';
+ }).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('p5-iv3-fb-'+i);
+ if(solved.has(i)) return;
+ if(v === t.a){
+ feedback(fb, true, '✓ Верно — '+NAMES[t.a]+'. '+HINTS[t.a](t));
+ solved.add(i);
+ scoreEl.textContent = solved.size;
+ if(solved.size === tasks.length && !xpGiven){
+ xpGiven = true;
+ addXp(15, 'p5-iv3');
+ bumpProgress('p5', 25);
+ }
+ } else {
+ feedback(fb, false, '✗ Не то. Сравни $d$ и $R$ внимательнее.');
+ }
+ });
+ });
+ })();
+
+ /* IV4 — Тренажёр */
+ (function(){
+ const tasks = [
+ { q:'Сфера $R=5$. Радиус сечения плоскостью на расстоянии $d=3$ от центра: $r=\\,?$', a:4, tol:0.05 },
+ { q:'Сфера с центром $C(2;3;-1)$, точка $M(5;7;3)$. Расстояние $|CM|=\\,?$ (точность 0,01)', a:6.40, tol:0.05 },
+ { q:'Сфера $R=13$. Сечение — окружность радиуса $12$. Найти $d$ (расстояние от центра до плоскости).', a:5, tol:0.05 },
+ { q:'К сфере $R=5$ построена касательная плоскость. Расстояние от центра до плоскости: $d=\\,?$', a:5, tol:0.05 },
+ { q:'Точка $A(3;4;0)$ принадлежит сфере с центром в начале координат. Найти $R$.', a:5, tol:0.05 },
+ { q:'Сфера $x^2+y^2+z^2=100$. Радиус большого круга: $R=\\,?$', a:10, tol:0.05 }
+ ];
+ const list = document.getElementById('p5-iv4-list');
+ const scoreEl = document.getElementById('p5-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('p5-iv4-inp-'+i);
+ const fb = document.getElementById('p5-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, 'p5-iv4');
+ bumpProgress('p5', 25);
+ setTimeout(function(){ achievement('p5_done'); }, 400);
+ }
+ }
+ } else {
+ feedback(fb, false, '✗ Не точно. Пересчитай аккуратно.');
+ }
+ });
+ });
+ })();
+
+ wireReadBtn('p5');
+}
+
/* ===== STUB BUILDER — единый для всех параграфов раздела (Phase 0) ===== */
function buildStub(id){
@@ -706,6 +1067,10 @@ function buildStub(id){
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
+ arr.push({kind:'Теория',title:'Сфера и шар: определение',desc:'центр, радиус, диаметр, хорда',sec:'p5'});
+ arr.push({kind:'Теория',title:'Уравнение сферы',desc:'(x-a)^2+(y-b)^2+(z-c)^2=R^2',sec:'p5'});
+ arr.push({kind:'Теория',title:'Касательная плоскость к сфере',desc:'перпендикулярна радиусу в точке касания',sec:'p5'});
+ arr.push({kind:'Теория',title:'Сечение сферы плоскостью',desc:'r = sqrt(R^2 - d^2), большой круг',sec:'p5'});
return arr;
})();
function initSearch(){