feat(geom11 ch2 wave1): §3 «Пирамида» + 3D + калькулятор

This commit is contained in:
Maxim Dolgolyov
2026-05-29 14:21:23 +03:00
parent c2b5d73913
commit dd0a54d8ca
+370 -3
View File
@@ -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:"Главное правило: <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:'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',
'<p><b>Пирамида</b> — многогранник, у которого одна грань (<b>основание</b>) — многоугольник, а остальные грани (<b>боковые</b>) — треугольники с общей вершиной.</p>'
+ '<p><b>Элементы пирамиды:</b></p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li><b>Основание</b> — многоугольник.</li>'
+ '<li><b>Вершина пирамиды</b> (апекс) — общая точка боковых граней.</li>'
+ '<li><b>Боковые грани</b> — треугольники.</li>'
+ '<li><b>Боковые рёбра</b> — отрезки от апекса к вершинам основания.</li>'
+ '<li><b>Высота</b> $h$ — перпендикуляр из апекса к плоскости основания.</li>'
+ '</ul>'
+ '<p><b>Правильная пирамида</b> — пирамида, у которой:</p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li>Основание — <b>правильный</b> многоугольник.</li>'
+ '<li>Высота попадает в <b>центр основания</b>.</li>'
+ '</ul>'
+ '<p>Тогда автоматически выполняются <b>следствия:</b> все боковые рёбра равны, а боковые грани — равные равнобедренные треугольники.</p>'
+ '<p><b>Апофема</b> $l$ — высота боковой грани, опущенная из апекса к середине стороны основания. У правильной пирамиды все апофемы равны.</p>');
html += makeCard('rule', 'Площадь и объём', '§ 3.2',
'<p><b>Объём</b> (для любой пирамиды):</p>'
+ '<p style="text-align:center;margin:8px 0">$$V=\\dfrac{1}{3}\\,S_{осн}\\cdot h$$</p>'
+ '<p><b>Боковая поверхность правильной пирамиды:</b></p>'
+ '<p style="text-align:center;margin:8px 0">$$S_{бок}=\\dfrac{1}{2}\\,P_{осн}\\cdot l$$</p>'
+ '<p>где $l$ — апофема, $P_{осн}$ — периметр основания.</p>'
+ '<p><b>Полная поверхность:</b> $S_{полн}=S_{бок}+S_{осн}$.</p>'
+ '<p style="margin-top:10px"><b>Связь между элементами правильной $n$-угольной пирамиды.</b> Пусть $R$ — радиус описанной около основания окружности, $r$ — радиус вписанной, $b$ — боковое ребро, $l$ — апофема, $h$ — высота. Тогда:</p>'
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
+ '<li>$b^2=R^2+h^2$ — теорема Пифагора в треугольнике «апекс — центр — вершина основания».</li>'
+ '<li>$l^2=r^2+h^2$ — теорема Пифагора в треугольнике «апекс — центр — середина стороны».</li>'
+ '</ul>'
+ '<details class="spoiler"><summary>Пример: правильная 4-угольная пирамида $a=6$, $h=4$</summary><div class="spoiler-body">'
+ '<p>Основание — квадрат, $r=a/2=3$.</p>'
+ '<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_{осн}=a^2=36$, $P_{осн}=4a=24$.</li>'
+ '<li>$S_{бок}=\\dfrac{1}{2}\\cdot 24\\cdot 5=60$.</li>'
+ '<li>$S_{полн}=60+36=96$.</li>'
+ '<li>$V=\\dfrac{1}{3}\\cdot 36\\cdot 4=48$.</li>'
+ '</ul>'
+ '</div></details>');
html += makeCard('example', 'Усечённая пирамида', '§ 3.3',
'<p><b>Усечённая пирамида</b> — часть пирамиды, заключённая между основанием и плоскостью, параллельной основанию.</p>'
+ '<p>У неё <b>два основания</b> — подобные многоугольники в параллельных плоскостях. Боковые грани — <b>трапеции</b>.</p>'
+ '<p><b>Объём усечённой пирамиды:</b></p>'
+ '<p style="text-align:center;margin:8px 0">$$V=\\dfrac{1}{3}\\,h\\bigl(S_1+S_2+\\sqrt{S_1 S_2}\\bigr)$$</p>'
+ '<p>где $S_1,\\,S_2$ — площади оснований, $h$ — высота (расстояние между плоскостями оснований).</p>'
+ '<p><b>Боковая поверхность правильной усечённой пирамиды:</b></p>'
+ '<p style="text-align:center;margin:8px 0">$$S_{бок}=\\dfrac{1}{2}(P_1+P_2)\\cdot l$$</p>'
+ '<p>где $l$ — апофема (высота боковой трапеции), $P_1,\\,P_2$ — периметры оснований.</p>'
+ '<details class="spoiler"><summary>Пример: правильная 4-угольная усечённая, $a_1=6$, $a_2=4$, $h=3$</summary><div class="spoiler-body">'
+ '<p>$S_1=36$, $S_2=16$.</p>'
+ '<p>Апофема: $l=\\sqrt{h^2+\\left(\\dfrac{a_1-a_2}{2}\\right)^2}=\\sqrt{9+1}=\\sqrt{10}$.</p>'
+ '<p>$V=\\dfrac{1}{3}\\cdot 3\\cdot(36+16+\\sqrt{576})=36+16+24=76$.</p>'
+ '</div></details>');
/* === ИНТЕРАКТИВ 1 — 3D-конструктор === */
html += '<div class="wg" id="p3-iv1">'
+ '<div class="wg-header"><span class="wg-badge">3D · конструктор</span><div class="wg-title">Правильная $n$-угольная пирамида</div></div>'
+ '<div class="wg-help">Меняй число сторон основания $n$, радиус описанной окружности $R$ и высоту $h$. Вращай мышью или выбирай вид. После <b>4 разных конфигураций</b> — +10 XP.</div>'
+ '<div class="sliders">'
+ '<label>$n$ (сторон):<b id="p3-iv1-n-v">4</b><input type="range" id="p3-iv1-n" min="3" max="6" step="1" value="4"></label>'
+ '<label>$R$ (радиус):<b id="p3-iv1-R-v">1.8</b><input type="range" id="p3-iv1-R" min="1" max="4" step="0.1" value="1.8"></label>'
+ '<label>$h$ (высота):<b id="p3-iv1-h-v">2.6</b><input type="range" id="p3-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="p3-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>$a=$<b id="p3-iv1-a">—</b></span>'
+ '<span>$r=$<b id="p3-iv1-r">—</b></span>'
+ '<span>$P_{осн}=$<b id="p3-iv1-P">—</b></span>'
+ '<span>$S_{осн}=$<b id="p3-iv1-So">—</b></span>'
+ '<span>$l=$<b id="p3-iv1-l">—</b></span>'
+ '<span>$b=$<b id="p3-iv1-b">—</b></span>'
+ '<span>$S_{бок}=$<b id="p3-iv1-Sb">—</b></span>'
+ '<span>$S_{полн}=$<b id="p3-iv1-St">—</b></span>'
+ '<span>$V=$<b id="p3-iv1-V">—</b></span>'
+ '</div>'
+ '<div style="font-size:.78rem;color:var(--muted);margin-top:6px">Конфигураций изучено: <b id="p3-iv1-cnt">0</b> / 4</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 2 — Калькулятор V и S === */
html += '<div class="wg" id="p3-iv2">'
+ '<div class="wg-header"><span class="wg-badge">калькулятор</span><div class="wg-title">$V$ и $S_{бок}$ правильной пирамиды</div></div>'
+ '<div class="wg-help">Введи $n$ (число сторон основания, $3..8$), сторону $a$ и высоту $h$. Калькулятор покажет пошаговый разбор.</div>'
+ '<div class="sliders">'
+ '<label>$n$:<b id="p3-iv2-n-v">4</b><input type="range" id="p3-iv2-n" min="3" max="8" step="1" value="4"></label>'
+ '<label>$a$ (сторона):<b id="p3-iv2-a-v">6.0</b><input type="range" id="p3-iv2-a" min="1" max="10" step="0.1" value="6"></label>'
+ '<label>$h$ (высота):<b id="p3-iv2-h-v">4.0</b><input type="range" id="p3-iv2-h" min="1" max="12" step="0.1" value="4"></label>'
+ '</div>'
+ '<div class="actions"><button class="btn primary" id="p3-iv2-calc">Вычислить</button></div>'
+ '<div id="p3-iv2-out" style="margin-top:10px;font-size:.92rem;line-height:1.7"></div>'
+ '</div>';
/* === ИНТЕРАКТИВ 3 — «Найди элемент» (квикфайр) === */
html += '<div class="wg" id="p3-iv3">'
+ '<div class="wg-header"><span class="wg-badge">квикфайр · 6 заданий</span><div class="wg-title">Найди элемент пирамиды</div></div>'
+ '<div class="wg-help">Читай описание отрезка — выбирай, какой это элемент: высота $h$, апофема $l$ или боковое ребро $b$.</div>'
+ '<div id="p3-iv3-list"></div>'
+ '<div class="score-display" style="margin-top:10px">Верно: <b id="p3-iv3-score">0</b> / 6</div>'
+ '</div>';
/* === ИНТЕРАКТИВ 4 — Тренажёр V и S === */
html += '<div class="wg" id="p3-iv4">'
+ '<div class="wg-header"><span class="wg-badge">тренажёр · 6 задач</span><div class="wg-title">$V$, $l$, $S_{бок}$ и усечённая</div></div>'
+ '<div class="wg-help">Введи числовой ответ. Допуск $\\pm 0{,}05$ для дробных значений.</div>'
+ '<div id="p3-iv4-list"></div>'
+ '<div class="score-display" style="margin-top:10px">Решено: <b id="p3-iv4-score">0</b> / 6</div>'
+ '</div>';
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 = '&#10003; +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 = '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 14px">'
+ '<p><b>Правильная '+n+'-угольная пирамида: $a='+a.toFixed(2)+'$, $h='+h.toFixed(2)+'$</b></p>'
+ '<p>$r=\\dfrac{a}{2\\tan(\\pi/n)}\\approx '+r.toFixed(3)+'$ — радиус вписанной окружности основания.</p>'
+ '<p>$P_{осн}=n\\cdot a='+n+'\\cdot '+a.toFixed(2)+'='+P.toFixed(2)+'$</p>'
+ '<p>$S_{осн}=\\dfrac{1}{2}P\\cdot r\\approx '+So.toFixed(2)+'$</p>'
+ '<p>$l=\\sqrt{r^2+h^2}=\\sqrt{'+(r*r).toFixed(2)+'+'+(h*h).toFixed(2)+'}\\approx '+l.toFixed(3)+'$ — апофема.</p>'
+ '<p>$S_{бок}=\\dfrac{1}{2}P_{осн}\\cdot l\\approx '+Sb.toFixed(2)+'$</p>'
+ '<p>$S_{полн}=S_{бок}+S_{осн}\\approx '+St.toFixed(2)+'$</p>'
+ '<p style="font-weight:700;color:var(--pri2)">$V=\\dfrac{1}{3}S_{осн}\\cdot h\\approx '+V.toFixed(2)+'$</p>'
+ '</div>';
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 '<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="h">Высота $h$</button>'
+ '<button class="btn" data-i="'+i+'" data-v="l">Апофема $l$</button>'
+ '<button class="btn" data-i="'+i+'" data-v="b">Боковое ребро $b$</button>'
+ '</div>'
+ '<div class="feedback" id="p3-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('p3-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, 'p3-iv3');
bumpProgress('p3', 25);
}
} else {
feedback(fb, false, '&#10007; Не то. Подумай ещё раз.');
}
});
});
})();
/* 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 '<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="p3-iv4-inp-'+i+'" placeholder="число" style="width:140px">'
+ '<button class="btn primary" data-i="'+i+'">Проверить</button>'
+ '</div>'
+ '<div class="feedback" id="p3-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('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, '&#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, 'p3-iv4');
bumpProgress('p3', 25);
setTimeout(function(){ achievement('p3_done'); }, 400);
}
}
} else {
feedback(fb, false, '&#10007; Не точно. Пересчитай аккуратно.');
}
});
});
})();
wireReadBtn('p3');
}
/* ===== STUB BUILDER — единый для всех параграфов раздела (Phase 0) ===== */
function buildStub(id){