feat(geom11 ch2 wave2): §4 «Конус» + 3D + развёртка

This commit is contained in:
Maxim Dolgolyov
2026-05-29 14:26:11 +03:00
parent dd0a54d8ca
commit 15de0d914f
+409 -3
View File
@@ -392,7 +392,7 @@ function buildParaSelector(){
}
const BUILT=new Set();
const BUILDERS = { p3:buildP3, p4:()=>buildStub('p4'), final2:()=>buildStub('final2') };
const BUILDERS = { p3:buildP3, p4:buildP4, 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);
@@ -417,13 +417,24 @@ const SIDEBARS = {
["Апофема","$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$"]]},
p4:{title:"Шпаргалка § 4", rows:[
["Конус","основание ($R$) + апекс"],
["Связь","$l^2=R^2+h^2$"],
["$S_{осн}$","$\\\\pi R^2$"],
["$S_{бок}$","$\\\\pi R l$"],
["$S_{полн}$","$\\\\pi R(R+l)$"],
["$V$","$\\\\dfrac{1}{3}\\\\pi R^2 h$"],
["Развёртка","сектор $r=l$, дуга $2\\\\pi R$"],
["Угол развёртки","$\\\\varphi=\\\\dfrac{360°R}{l}$"],
["Усечённый $V$","$\\\\dfrac{\\\\pi h}{3}(R_1^2+R_2^2+R_1R_2)$"],
["Усечённый $S_{бок}$","$\\\\pi(R_1+R_2)l$"]
]},
final2:{title:"Финал раздела 2", rows:[["§ 3–§ 4","теория раздела 2"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p3',html:"Главное правило: <b>$V=\\\\\\\\dfrac{1}{3}S_{осн}h$</b> для <i>любой</i> пирамиды. А для правильной — $S_{бок}=\\\\\\\\dfrac{1}{2}P_{осн}l$, где $l$ — апофема."},
{sec:'p4',html:"§ 4 «Конус»содержание в разработке. $S_{бок}=\\\\\\\\pi Rl$"},
{sec:'p4',html:"Запомни связку: $l^2=R^2+h^2$. Все формулы конусаэто $\\\\\\\\pi R$ умноженное на соответствующий «множитель»: $R$ (основание), $l$ (боковая), $R+l$ (полная), $\\\\\\\\dfrac{Rh}{3}$ (объём)."},
{sec:'final2',html:"Финал раздела 2 — интегрированные задачи по разделу."}
];
@@ -986,6 +997,401 @@ function buildP3(){
wireReadBtn('p3');
}
/* ===== § 4 «Конус» — Wave 2 ===== */
function buildP4(){
const box = document.getElementById('p4-body');
if(!box) return;
let html = '';
/* === ТЕОРИЯ === */
html += makeCard('theory', 'Определение и элементы', '§ 4.1',
'<p><b>Конус</b> (прямой круговой) — тело, образованное вращением прямоугольного треугольника вокруг одного из катетов.</p>'
+ '<p><b>Элементы конуса:</b></p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li><b>Основание</b> — круг радиуса $R$ (получается вращением второго катета).</li>'
+ '<li><b>Вершина конуса</b> (апекс) — точка на оси вращения, не лежащая в основании.</li>'
+ '<li><b>Ось</b> — катет, вокруг которого вращали; совпадает с высотой $h$.</li>'
+ '<li><b>Образующие</b> — отрезки длины $l$ от апекса к точкам окружности основания (это гипотенузы при вращении). Все образующие равны.</li>'
+ '<li><b>Боковая поверхность</b> — поверхность вращения гипотенузы.</li>'
+ '<li><b>Высота</b> $h$ — расстояние от апекса до плоскости основания.</li>'
+ '</ul>'
+ '<p><b>Главная связь</b> (теорема Пифагора в осевом сечении):</p>'
+ '<p style="text-align:center;margin:8px 0">$$l^2 = R^2 + h^2$$</p>'
+ '<p>Конус — <b>«предельный»</b> случай пирамиды: если у правильной $n$-угольной пирамиды устремить число сторон основания $n\\to\\infty$ при фиксированном радиусе описанной окружности, то получится конус. Поэтому формулы для конуса можно получать «по аналогии» с пирамидой, заменяя $P_{осн}$ на $2\\pi R$, а $S_{осн}$ на $\\pi R^2$.</p>');
html += makeCard('rule', 'Площадь и объём. Развёртка', '§ 4.2',
'<p><b>Основные формулы:</b></p>'
+ '<p style="text-align:center;margin:8px 0">$$S_{осн} = \\pi R^2$$</p>'
+ '<p style="text-align:center;margin:8px 0">$$S_{бок} = \\pi R l$$</p>'
+ '<p style="text-align:center;margin:8px 0">$$S_{полн} = \\pi R (R + l)$$</p>'
+ '<p style="text-align:center;margin:8px 0">$$V = \\dfrac{1}{3}\\pi R^2 h$$</p>'
+ '<p><b>Развёртка боковой поверхности конуса</b> — это <b>круговой сектор</b> радиуса $l$ (равного образующей), у которого длина дуги равна длине окружности основания $2\\pi R$.</p>'
+ '<p><b>Угол развёртки:</b></p>'
+ '<p style="text-align:center;margin:8px 0">$$\\varphi = \\dfrac{2\\pi R}{l}\\;\\text{рад} = \\dfrac{360°\\cdot R}{l}$$</p>'
+ '<details class="spoiler"><summary>Пример: конус $R=3$, $h=4$</summary><div class="spoiler-body">'
+ '<ul style="margin:6px 0 0 22px;line-height:1.7">'
+ '<li>$l=\\sqrt{R^2+h^2}=\\sqrt{9+16}=5$ — образующая.</li>'
+ '<li>$S_{осн}=\\pi R^2 = 9\\pi$.</li>'
+ '<li>$S_{бок}=\\pi R l = 15\\pi$.</li>'
+ '<li>$S_{полн}=\\pi R(R+l)= 3\\pi\\cdot 8 = 24\\pi\\approx 75{,}4$.</li>'
+ '<li>$V=\\dfrac{1}{3}\\pi R^2 h=\\dfrac{1}{3}\\cdot 9\\pi\\cdot 4 = 12\\pi\\approx 37{,}7$.</li>'
+ '<li>Угол развёртки: $\\varphi=\\dfrac{360°\\cdot 3}{5}=216°$.</li>'
+ '</ul>'
+ '</div></details>');
html += makeCard('example', 'Сечения. Усечённый конус', '§ 4.3',
'<p><b>Сечения конуса плоскостью:</b></p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li><b>Перпендикулярное оси</b> (параллельное основанию): <b>круг</b> радиуса меньше $R$. Если плоскость находится на расстоянии $h_1$ от апекса, то радиус сечения $r_1 = \\dfrac{R\\,h_1}{h}$.</li>'
+ '<li><b>Осевое</b> (плоскость проходит через ось): <b>равнобедренный треугольник</b> с основанием $2R$ и боковыми сторонами $l$.</li>'
+ '<li><b>Через апекс</b> (плоскость содержит апекс, но не ось): равнобедренный треугольник с боковыми сторонами $l$ и основанием — хордой окружности.</li>'
+ '</ul>'
+ '<p><b>Усечённый конус</b> — часть конуса, заключённая между основанием и плоскостью, параллельной основанию.</p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li>Два круглых основания радиусов $R_1$ (нижнее, большее) и $R_2$ (верхнее, меньшее).</li>'
+ '<li>Высота $h$ — расстояние между плоскостями оснований.</li>'
+ '<li>Образующая $l$ — отрезок между соответствующими точками окружностей. Связь: $l^2 = h^2 + (R_1-R_2)^2$.</li>'
+ '</ul>'
+ '<p style="text-align:center;margin:8px 0">$$V = \\dfrac{1}{3}\\pi h\\bigl(R_1^2 + R_2^2 + R_1 R_2\\bigr)$$</p>'
+ '<p style="text-align:center;margin:8px 0">$$S_{бок} = \\pi (R_1 + R_2)\\,l$$</p>'
+ '<details class="spoiler"><summary>Пример: усечённый конус $R_1=5$, $R_2=3$, $h=4$</summary><div class="spoiler-body">'
+ '<ul style="margin:6px 0 0 22px;line-height:1.7">'
+ '<li>$l=\\sqrt{h^2+(R_1-R_2)^2}=\\sqrt{16+4}=\\sqrt{20}=2\\sqrt{5}\\approx 4{,}47$.</li>'
+ '<li>$S_{бок}=\\pi(R_1+R_2)l = 8\\pi\\cdot 2\\sqrt{5}=16\\pi\\sqrt{5}\\approx 112{,}4$.</li>'
+ '<li>$V=\\dfrac{1}{3}\\pi\\cdot 4\\cdot(25+9+15)=\\dfrac{196\\pi}{3}\\approx 205{,}3$.</li>'
+ '</ul>'
+ '</div></details>');
/* === ИНТЕРАКТИВ 1 — 3D-конструктор конуса === */
html += '<div class="wg" id="p4-iv1">'
+ '<div class="wg-header"><span class="wg-badge">3D · конструктор</span><div class="wg-title">Конус: $R$, $h$ и все формулы</div></div>'
+ '<div class="wg-help">Меняй радиус основания $R$ и высоту $h$. Вращай мышью или выбирай вид. После <b>4 разных конфигураций</b> — +10 XP.</div>'
+ '<div class="sliders">'
+ '<label>$R$ (радиус):<b id="p4-iv1-R-v">1.5</b><input type="range" id="p4-iv1-R" min="1" max="4" step="0.1" value="1.5"></label>'
+ '<label>$h$ (высота):<b id="p4-iv1-h-v">2.6</b><input type="range" id="p4-iv1-h" min="1" max="6" step="0.1" value="2.6"></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="p4-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>$l=$<b id="p4-iv1-l">—</b></span>'
+ '<span>$S_{осн}=$<b id="p4-iv1-So">—</b></span>'
+ '<span>$S_{бок}=$<b id="p4-iv1-Sb">—</b></span>'
+ '<span>$S_{полн}=$<b id="p4-iv1-St">—</b></span>'
+ '<span>$V=$<b id="p4-iv1-V">—</b></span>'
+ '<span>$\\varphi=$<b id="p4-iv1-phi">—</b></span>'
+ '</div>'
+ '<div style="font-size:.78rem;color:var(--muted);margin-top:6px">Конфигураций изучено: <b id="p4-iv1-cnt">0</b> / 4</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 2 — Развёртка (2D-сектор) === */
html += '<div class="wg" id="p4-iv2">'
+ '<div class="wg-header"><span class="wg-badge">2D · развёртка</span><div class="wg-title">Развёртка боковой поверхности</div></div>'
+ '<div class="wg-help">Двигай $R$ и $h$ — увидишь, как меняется <b>круговой сектор</b> развёртки: его радиус равен $l$, длина дуги — $2\\pi R$, а угол — $\\varphi=\\dfrac{360°R}{l}$. После 4 конфигураций — +10 XP.</div>'
+ '<div class="sliders">'
+ '<label>$R$ (радиус):<b id="p4-iv2-R-v">1.5</b><input type="range" id="p4-iv2-R" min="1" max="4" step="0.1" value="1.5"></label>'
+ '<label>$h$ (высота):<b id="p4-iv2-h-v">2.6</b><input type="range" id="p4-iv2-h" min="1" max="6" step="0.1" value="2.6"></label>'
+ '</div>'
+ '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:8px;text-align:center"><svg id="p4-iv2-svg" viewBox="0 0 380 320" width="100%" style="max-width:380px;height:auto"></svg></div>'
+ '<div class="score-display" style="margin-top:10px;flex-wrap:wrap">'
+ '<span>$l=$<b id="p4-iv2-l">—</b></span>'
+ '<span>дуга $=2\\pi R=$<b id="p4-iv2-arc">—</b></span>'
+ '<span>угол $\\varphi=$<b id="p4-iv2-phi">—</b></span>'
+ '</div>'
+ '<div style="font-size:.78rem;color:var(--muted);margin-top:6px">Конфигураций изучено: <b id="p4-iv2-cnt">0</b> / 4</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 3 — Квикфайр «какое сечение?» === */
html += '<div class="wg" id="p4-iv3">'
+ '<div class="wg-header"><span class="wg-badge">квикфайр · 6 заданий</span><div class="wg-title">Какое сечение конуса?</div></div>'
+ '<div class="wg-help">Читай описание плоскости — выбирай форму сечения: <b>круг</b>, <b>равнобедренный треугольник</b> или <b>эллипс</b>.</div>'
+ '<div id="p4-iv3-list"></div>'
+ '<div class="score-display" style="margin-top:10px">Верно: <b id="p4-iv3-score">0</b> / 6</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 4 — Тренажёр V и S === */
html += '<div class="wg" id="p4-iv4">'
+ '<div class="wg-header"><span class="wg-badge">тренажёр · 6 задач</span><div class="wg-title">$V$, $S$, $l$ и угол развёртки</div></div>'
+ '<div class="wg-help">Введи числовой ответ. Используй $\\pi\\approx 3{,}14$. Допуск $\\pm 0{,}05$.</div>'
+ '<div id="p4-iv4-list"></div>'
+ '<div class="score-display" style="margin-top:10px">Решено: <b id="p4-iv4-score">0</b> / 6</div>'
+ '</div>';
html += secNavFor('p4');
html += readButton('p4');
box.innerHTML = html;
renderMath(box);
/* ====== JS-логика интерактивов ====== */
/* IV1 — 3D-конструктор конуса */
(function(){
if(!window.G3D) return;
const svg = document.getElementById('p4-iv1-svg');
const elR = document.getElementById('p4-iv1-R');
const elH = document.getElementById('p4-iv1-h');
const vR = document.getElementById('p4-iv1-R-v');
const vH = document.getElementById('p4-iv1-h-v');
const oL = document.getElementById('p4-iv1-l');
const oSo = document.getElementById('p4-iv1-So');
const oSb = document.getElementById('p4-iv1-Sb');
const oSt = document.getElementById('p4-iv1-St');
const oV = document.getElementById('p4-iv1-V');
const oPhi = document.getElementById('p4-iv1-phi');
const oCnt = document.getElementById('p4-iv1-cnt');
if(!svg) return;
const PI = 3.14;
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 R = +elR.value, h = +elH.value;
vR.textContent = R.toFixed(1);
vH.textContent = h.toFixed(1);
const mesh = G3D.coneMesh(R, h, 32);
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 l = Math.sqrt(R*R + h*h);
const So = PI*R*R;
const Sb = PI*R*l;
const St = PI*R*(R + l);
const V = PI*R*R*h/3;
const phi = 360*R/l;
oL.textContent = l.toFixed(2);
oSo.textContent = So.toFixed(2);
oSb.textContent = Sb.toFixed(2);
oSt.textContent = St.toFixed(2);
oV.textContent = V.toFixed(2);
oPhi.textContent = phi.toFixed(1) + '°';
const key = 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, 'p4-iv1');
bumpProgress('p4', 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('p4-iv1');
if(host) host.appendChild(note);
setTimeout(function(){ try{ note.remove(); }catch(e){} }, 3000);
}
}
draw();
G3D.attachOrbit(svg, scene, draw);
[elR, elH].forEach(function(el){ el.addEventListener('input', draw); });
document.querySelectorAll('#p4-iv1 .g3d-tools .btn').forEach(function(b){
b.addEventListener('click', function(){ G3D.presetView(scene, b.dataset.view, draw); });
});
})();
/* IV2 — Развёртка (2D-сектор) */
(function(){
const svg = document.getElementById('p4-iv2-svg');
const elR = document.getElementById('p4-iv2-R');
const elH = document.getElementById('p4-iv2-h');
const vR = document.getElementById('p4-iv2-R-v');
const vH = document.getElementById('p4-iv2-h-v');
const oL = document.getElementById('p4-iv2-l');
const oArc = document.getElementById('p4-iv2-arc');
const oPhi = document.getElementById('p4-iv2-phi');
const oCnt = document.getElementById('p4-iv2-cnt');
if(!svg) return;
const PI = 3.14;
const W = 380, H = 320;
const cx = 190, cy = 180;
const seen = new Set();
let xpGiven = false;
function draw(){
const R = +elR.value, h = +elH.value;
vR.textContent = R.toFixed(1);
vH.textContent = h.toFixed(1);
const l = Math.sqrt(R*R + h*h);
const arc = 2*PI*R;
const phiDeg = 360*R/l;
const phiRad = phiDeg * Math.PI / 180;
/* масштаб: подгоняем радиус сектора к ~120px на SVG */
const scale = 120 / l;
const rPix = l * scale;
/* строим сектор: центр (cx,cy), от угла -phi/2 до +phi/2 (отсчёт от вертикали вверх) */
const a0 = -Math.PI/2 - phiRad/2;
const a1 = -Math.PI/2 + phiRad/2;
const x0 = cx + rPix*Math.cos(a0), y0 = cy + rPix*Math.sin(a0);
const x1 = cx + rPix*Math.cos(a1), y1 = cy + rPix*Math.sin(a1);
const largeArc = phiRad > Math.PI ? 1 : 0;
let s = '';
/* сетка для контекста */
s += '<rect x="0" y="0" width="'+W+'" height="'+H+'" fill="#fafafa"/>';
/* пунктирный круг полного радиуса l (показывает «целое» откуда вырезан сектор) */
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+rPix.toFixed(2)+'" fill="none" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3 3"/>';
/* заливка сектора */
s += '<path d="M '+cx+' '+cy+' L '+x0.toFixed(2)+' '+y0.toFixed(2)
+ ' A '+rPix.toFixed(2)+' '+rPix.toFixed(2)+' 0 '+largeArc+' 1 '+x1.toFixed(2)+' '+y1.toFixed(2)
+ ' Z" fill="rgba(5,150,105,.18)" stroke="#047857" stroke-width="2" stroke-linejoin="round"/>';
/* радиус-метка слева (l) */
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+x0.toFixed(2)+'" y2="'+y0.toFixed(2)+'" stroke="#0f172a" stroke-width="1.4"/>';
const midR1x = (cx + x0)/2, midR1y = (cy + y0)/2;
s += '<text x="'+(midR1x-18).toFixed(2)+'" y="'+(midR1y).toFixed(2)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#047857">l='+l.toFixed(2)+'</text>';
/* радиус справа */
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+x1.toFixed(2)+'" y2="'+y1.toFixed(2)+'" stroke="#0f172a" stroke-width="1.4"/>';
/* подпись дуги */
const midA = -Math.PI/2;
const labelR = rPix + 16;
const labX = cx + labelR*Math.cos(midA), labY = cy + labelR*Math.sin(midA);
s += '<text x="'+labX.toFixed(2)+'" y="'+labY.toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#0f172a">дуга = 2πR ≈ '+arc.toFixed(2)+'</text>';
/* центр и подпись угла φ */
s += '<circle cx="'+cx+'" cy="'+cy+'" r="3" fill="#0f172a"/>';
/* дуга индикатор угла маленькая */
const angR = 26;
const ax0 = cx + angR*Math.cos(a0), ay0 = cy + angR*Math.sin(a0);
const ax1 = cx + angR*Math.cos(a1), ay1 = cy + angR*Math.sin(a1);
s += '<path d="M '+ax0.toFixed(2)+' '+ay0.toFixed(2)
+ ' A '+angR+' '+angR+' 0 '+largeArc+' 1 '+ax1.toFixed(2)+' '+ay1.toFixed(2)
+ '" fill="none" stroke="#ec4899" stroke-width="2"/>';
s += '<text x="'+cx+'" y="'+(cy+14).toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#ec4899">φ='+phiDeg.toFixed(1)+'°</text>';
svg.innerHTML = s;
oL.textContent = l.toFixed(2);
oArc.textContent = arc.toFixed(2);
oPhi.textContent = phiDeg.toFixed(1) + '°';
const key = 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, 'p4-iv2');
bumpProgress('p4', 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('p4-iv2');
if(host) host.appendChild(note);
setTimeout(function(){ try{ note.remove(); }catch(e){} }, 3000);
}
}
draw();
[elR, elH].forEach(function(el){ el.addEventListener('input', draw); });
})();
/* IV3 — Квикфайр сечений */
(function(){
const tasks = [
{ q:'Плоскость <b>перпендикулярна оси</b> конуса и пересекает его.', a:'круг' },
{ q:'<b>Осевое сечение</b> — плоскость проходит через ось конуса.', a:'треугольник' },
{ q:'Плоскость <b>параллельна основанию</b> и не совпадает с ним.', a:'круг' },
{ q:'Плоскость проходит через <b>апекс</b> и пересекает основание по хорде.', a:'треугольник' },
{ q:'Плоскость наклонена к основанию под углом 30°, не проходит через апекс, пересекает все образующие.', a:'эллипс' },
{ q:'Плоскость параллельна основанию и проходит через середину высоты.', a:'круг' }
];
const list = document.getElementById('p4-iv3-list');
const scoreEl = document.getElementById('p4-iv3-score');
const solved = new Set();
let xpGiven = false;
const NAMES = {'круг':'Круг', 'треугольник':'Равнобедренный треугольник', 'эллипс':'Эллипс'};
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> '+t.q+'</div>'
+ '<div style="display:flex;gap:6px;flex-wrap:wrap">'
+ '<button class="btn" data-i="'+i+'" data-v="круг">Круг</button>'
+ '<button class="btn" data-i="'+i+'" data-v="треугольник">Равнобедренный треугольник</button>'
+ '<button class="btn" data-i="'+i+'" data-v="эллипс">Эллипс</button>'
+ '</div>'
+ '<div class="feedback" id="p4-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('p4-iv3-fb-'+i);
if(solved.has(i)) return;
if(v === t.a){
feedback(fb, true, '&#10003; Верно — это '+NAMES[t.a]+'.');
solved.add(i);
scoreEl.textContent = solved.size;
if(solved.size === tasks.length && !xpGiven){
xpGiven = true;
addXp(15, 'p4-iv3');
bumpProgress('p4', 25);
}
} else {
feedback(fb, false, '&#10007; Не то. Подумай ещё раз — где плоскость пересекает образующие?');
}
});
});
})();
/* IV4 — Тренажёр V, S, l, угол развёртки */
(function(){
const tasks = [
{ q:'Конус: $R=3$, $h=4$. Найди $V$ (с $\\pi\\approx 3{,}14$).', a:37.68, tol:0.1 },
{ q:'Тот же конус ($R=3$, $h=4$). Образующая $l=\\,?$', a:5, tol:0.05 },
{ q:'Тот же конус ($R=3$, $h=4$). $S_{бок}=\\,?$ (с $\\pi\\approx 3{,}14$)', a:47.1, tol:0.1 },
{ q:'Тот же конус ($R=3$, $h=4$). $S_{полн}=\\,?$ (с $\\pi\\approx 3{,}14$)', a:75.36, tol:0.1 },
{ q:'Конус: $R=6$, $l=10$. Высота $h=\\,?$', a:8, tol:0.05 },
{ q:'Конус $R=3$, $l=6$. Угол развёртки $\\varphi=\\,?$ (в градусах)', a:180, tol:0.5 }
];
const list = document.getElementById('p4-iv4-list');
const scoreEl = document.getElementById('p4-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="p4-iv4-inp-'+i+'" placeholder="число" style="width:140px">'
+ '<button class="btn primary" data-i="'+i+'">Проверить</button>'
+ '</div>'
+ '<div class="feedback" id="p4-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('p4-iv4-inp-'+i);
const fb = document.getElementById('p4-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, 'p4-iv4');
bumpProgress('p4', 25);
setTimeout(function(){ achievement('p4_done'); }, 400);
}
}
} else {
feedback(fb, false, '&#10007; Не точно. Пересчитай аккуратно (используй $\\pi\\approx 3{,}14$).');
}
});
});
})();
wireReadBtn('p4');
}
/* ===== STUB BUILDER — единый для всех параграфов раздела (Phase 0) ===== */
function buildStub(id){