feat(geom11 ch3 wave1): §5 «Сфера» + 3D + уравнение + сечения

This commit is contained in:
Maxim Dolgolyov
2026-05-29 14:33:45 +03:00
parent 4814d5edeb
commit 3df79d081c
+368 -3
View File
@@ -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',
'<p><b>Сфера</b> — множество всех точек пространства, равноудалённых от заданной точки $O$ (<b>центра</b>).</p>'
+ '<p><b>Шар</b> — множество точек, для которых $|OM|\\le R$, где $O$ — центр, $R$ — радиус. Шар ограничен сферой; сфера — поверхность шара.</p>'
+ '<p><b>Элементы:</b></p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li><b>Центр</b> $O$ — точка, от которой все точки сферы равноудалены.</li>'
+ '<li><b>Радиус</b> $R=|OM|$ для любой точки $M$ сферы.</li>'
+ '<li><b>Диаметр</b> — отрезок через центр между двумя точками сферы. Длина $d=2R$.</li>'
+ '<li><b>Хорда</b> — отрезок между двумя точками сферы (не обязательно через центр).</li>'
+ '</ul>'
+ '<p><b>Уравнение сферы</b> в декартовой системе координат. Для центра $C(a,b,c)$ и радиуса $R$:</p>'
+ '<p style="text-align:center;margin:8px 0">$$(x-a)^2+(y-b)^2+(z-c)^2=R^2$$</p>'
+ '<p>Если центр в начале координат:</p>'
+ '<p style="text-align:center;margin:8px 0">$$x^2+y^2+z^2=R^2$$</p>'
+ '<p>Уравнение выражает то, что квадрат расстояния от точки $(x,y,z)$ до центра $C$ равен $R^2$.</p>');
html += makeCard('rule', 'Касательная плоскость', '§ 5.2',
'<p><b>Касательная плоскость к сфере</b> — плоскость, имеющая со сферой <b>ровно одну</b> общую точку. Эту точку называют <b>точкой касания</b>.</p>'
+ '<p><b>Признак касания</b> (необходимый и достаточный):</p>'
+ '<p style="background:var(--sec-acc-soft,var(--pri-soft));border-left:4px solid var(--sec-acc,var(--pri));padding:8px 12px;border-radius:6px;margin:8px 0">Плоскость касается сферы в точке $M$ тогда и только тогда, когда она <b>перпендикулярна радиусу $OM$</b>, проведённому в точку касания.</p>'
+ '<p>Через каждую точку сферы можно провести <b>единственную</b> касательную плоскость.</p>'
+ '<p>Расстояние от центра сферы до касательной плоскости равно радиусу $R$.</p>'
+ '<details class="spoiler"><summary>Пример: касательная к сфере $x^2+y^2+z^2=25$ в точке $M(3;4;0)$</summary><div class="spoiler-body">'
+ '<p>Центр $O(0;0;0)$, радиус-вектор $\\overrightarrow{OM}=(3;4;0)$ направлен из центра в точку касания.</p>'
+ '<p>Значит нормаль касательной плоскости $\\vec{n}=(3;4;0)$. Уравнение плоскости в точке $M$:</p>'
+ '<p>$3(x-3)+4(y-4)+0\\cdot(z-0)=0$, то есть $3x+4y=25$.</p>'
+ '</div></details>');
html += makeCard('example', 'Сечения сферы и большой круг', '§ 5.3',
'<p><b>Сечение сферы плоскостью</b>, пересекающей её — всегда <b>окружность</b>. Это следует из того, что точки сечения равноудалены от проекции центра сферы на эту плоскость.</p>'
+ '<p>Связь радиуса сечения $r$, радиуса сферы $R$ и расстояния $d$ от центра сферы до секущей плоскости:</p>'
+ '<p style="text-align:center;margin:8px 0">$$r=\\sqrt{R^2-d^2}$$</p>'
+ '<p><b>Три случая:</b></p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li>$d<R$ — пересечение есть, сечение — окружность радиуса $r$.</li>'
+ '<li>$d=R$ — плоскость касается сферы; «сечение» вырождается в точку.</li>'
+ '<li>$d>R$ — общих точек нет.</li>'
+ '</ul>'
+ '<p><b>Большой круг</b> — сечение, проходящее через центр сферы ($d=0$). Радиус большого круга максимален и равен $R$.</p>'
+ '<p>Любые два больших круга пересекаются по диаметру сферы.</p>'
+ '<details class="spoiler"><summary>Пример: $R=5$, $d=3$</summary><div class="spoiler-body">'
+ '<p>$r=\\sqrt{R^2-d^2}=\\sqrt{25-9}=\\sqrt{16}=4$.</p>'
+ '<p>Сечение — окружность радиуса $4$ в секущей плоскости.</p>'
+ '</div></details>');
/* === ИНТЕРАКТИВ 1 — 3D-визуализатор сферы === */
html += '<div class="wg" id="p5-iv1">'
+ '<div class="wg-header"><span class="wg-badge">3D · сфера</span><div class="wg-title">Визуализатор сферы (каркас)</div></div>'
+ '<div class="wg-help">Меняй радиус $R$ ползунком, вращай мышью или выбирай вид. После <b>4 разных значений $R$</b> — +10 XP.</div>'
+ '<div class="sliders">'
+ '<label>$R$ (радиус):<b id="p5-iv1-R-v">2.0</b><input type="range" id="p5-iv1-R" min="1" max="4" step="0.1" value="2"></label>'
+ '</div>'
+ '<div class="g3d-tools">'
+ '<button class="btn" data-view="iso">Изо</button>'
+ '<button class="btn" data-view="front">Спереди</button>'
+ '<button class="btn" data-view="top">Сверху</button>'
+ '<button class="btn" data-view="side">Сбоку</button>'
+ '</div>'
+ '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px;text-align:center"><svg id="p5-iv1-svg" viewBox="0 0 480 400" width="100%" style="max-width:480px;height:auto"></svg></div>'
+ '<div class="score-display" style="margin-top:10px;flex-wrap:wrap">'
+ '<span>$R=$<b id="p5-iv1-R-o">—</b></span>'
+ '<span>$d=2R=$<b id="p5-iv1-d-o">—</b></span>'
+ '<span>$S=4\\pi R^2\\approx$<b id="p5-iv1-S-o">—</b></span>'
+ '<span>$V=\\tfrac{4}{3}\\pi R^3\\approx$<b id="p5-iv1-V-o">—</b></span>'
+ '</div>'
+ '<div style="font-size:.78rem;color:var(--muted);margin-top:6px">Разных $R$ изучено: <b id="p5-iv1-cnt">0</b> / 4</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 2 — Уравнение сферы и проверка точки === */
html += '<div class="wg" id="p5-iv2">'
+ '<div class="wg-header"><span class="wg-badge">уравнение</span><div class="wg-title">Уравнение сферы и проверка точки</div></div>'
+ '<div class="wg-help">Введи центр $C(a;b;c)$ и радиус $R$ — получи уравнение. Затем введи точку $M(x_0;y_0;z_0)$ — узнай, лежит ли она на сфере, внутри шара или вне.</div>'
+ '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 12px;margin-bottom:10px">'
+ '<p style="font-weight:700;margin-bottom:6px">Параметры сферы</p>'
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:8px">'
+ '<span>$a=$</span><input type="text" class="tinp" id="p5-iv2-a" value="0" style="width:60px">'
+ '<span>$b=$</span><input type="text" class="tinp" id="p5-iv2-b" value="0" style="width:60px">'
+ '<span>$c=$</span><input type="text" class="tinp" id="p5-iv2-c" value="0" style="width:60px">'
+ '<span>$R=$</span><input type="text" class="tinp" id="p5-iv2-R" value="5" style="width:60px">'
+ '<button class="btn primary" id="p5-iv2-show">Показать уравнение</button>'
+ '</div>'
+ '<div id="p5-iv2-eq" style="font-size:1rem;line-height:1.7;margin-top:6px"></div>'
+ '</div>'
+ '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 12px">'
+ '<p style="font-weight:700;margin-bottom:6px">Проверка точки $M(x_0;y_0;z_0)$</p>'
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:8px">'
+ '<span>$x_0=$</span><input type="text" class="tinp" id="p5-iv2-x0" value="3" style="width:60px">'
+ '<span>$y_0=$</span><input type="text" class="tinp" id="p5-iv2-y0" value="4" style="width:60px">'
+ '<span>$z_0=$</span><input type="text" class="tinp" id="p5-iv2-z0" value="0" style="width:60px">'
+ '<button class="btn primary" id="p5-iv2-check">Проверить точку</button>'
+ '</div>'
+ '<div id="p5-iv2-pt" style="font-size:.94rem;line-height:1.65;margin-top:6px"></div>'
+ '</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 3 — Сечение сферы (квикфайр 6) === */
html += '<div class="wg" id="p5-iv3">'
+ '<div class="wg-header"><span class="wg-badge">квикфайр · 6 заданий</span><div class="wg-title">Сечение сферы плоскостью</div></div>'
+ '<div class="wg-help">Сравни $d$ и $R$. Выбери, что получится при пересечении: окружность, точка (касание) или ничего.</div>'
+ '<div id="p5-iv3-list"></div>'
+ '<div class="score-display" style="margin-top:10px">Верно: <b id="p5-iv3-score">0</b> / 6</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 4 — Тренажёр === */
html += '<div class="wg" id="p5-iv4">'
+ '<div class="wg-header"><span class="wg-badge">тренажёр · 6 задач</span><div class="wg-title">Сечения, расстояния и уравнения</div></div>'
+ '<div class="wg-help">Введи числовой ответ. Допуск $\\pm 0{,}05$ для дробных значений.</div>'
+ '<div id="p5-iv4-list"></div>'
+ '<div class="score-display" style="margin-top:10px">Решено: <b id="p5-iv4-score">0</b> / 6</div>'
+ '</div>';
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 = '&#10003; +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 = '<span style="color:var(--bad)">&#10007; Введи корректные числа ($R>0$).</span>';
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 = '<p>Центр $C('+fmt(a)+';'+fmt(b)+';'+fmt(c)+')$, радиус $R='+fmt(R)+'$.</p>'
+ '<p>Уравнение сферы:</p>' + 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 = '<span style="color:var(--bad)">&#10007; Введи корректные числа.</span>';
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 = 'лежит <b>на сфере</b>'; color = 'var(--ok)'; }
else if(lhs < R2){ verdict = 'лежит <b>внутри шара</b>'; color = '#2563eb'; }
else { verdict = 'лежит <b>вне шара</b>'; color = 'var(--warn)'; }
out.innerHTML = '<p>Подставляем $M('+fmt(x0)+';'+fmt(y0)+';'+fmt(z0)+')$:</p>'
+ '<p>$('+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)+'$</p>'
+ '<p>Сравниваем с $R^2='+fmt(R2)+'$. Расстояние $|CM|='+fmt(+dist.toFixed(4))+'$.</p>'
+ '<p style="color:'+color+';font-weight:700">&#10003; Точка $M$ '+verdict+'.</p>';
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 '$d<R$, сечение — окружность радиуса $r=\\sqrt{'+t.R+'^2-'+t.d+'^2}='+fmt(+r.toFixed(4))+'$'+(t.d===0?' (большой круг)':'')+'.'; },
point: function(t){ return '$d=R='+t.R+'$, плоскость касается сферы — общая точка одна.'; },
none: function(t){ return '$d='+t.d+'>R='+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 '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 12px;margin-bottom:8px">'
+ '<div style="margin-bottom:8px"><b>Задание '+(i+1)+'.</b> Сфера $R='+t.R+'$, расстояние от центра до плоскости $d='+t.d+'$. Что получится?</div>'
+ '<div style="display:flex;gap:6px;flex-wrap:wrap">'
+ '<button class="btn" data-i="'+i+'" data-v="circle">Окружность</button>'
+ '<button class="btn" data-i="'+i+'" data-v="point">Точка</button>'
+ '<button class="btn" data-i="'+i+'" data-v="none">Нет общих точек</button>'
+ '</div>'
+ '<div class="feedback" id="p5-iv3-fb-'+i+'"></div>'
+ '</div>';
}).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, '&#10003; Верно — '+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, '&#10007; Не то. Сравни $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 '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 12px;margin-bottom:8px">'
+ '<div style="margin-bottom:6px"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">'
+ '<input type="text" class="tinp" id="p5-iv4-inp-'+i+'" placeholder="число" style="width:140px">'
+ '<button class="btn primary" data-i="'+i+'">Проверить</button>'
+ '</div>'
+ '<div class="feedback" id="p5-iv4-fb-'+i+'"></div>'
+ '</div>';
}).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, '&#10007; Введи число'); return; }
if(Math.abs(val - t.a) <= t.tol){
feedback(fb, true, '&#10003; Верно!');
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, '&#10007; Не точно. Пересчитай аккуратно.');
}
});
});
})();
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(){