diff --git a/frontend/textbooks/geometry_8_ch1.html b/frontend/textbooks/geometry_8_ch1.html
index ddd52df..8c89a11 100644
--- a/frontend/textbooks/geometry_8_ch1.html
+++ b/frontend/textbooks/geometry_8_ch1.html
@@ -493,8 +493,8 @@ const BUILT = new Set();
const BUILDERS = {
p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(),
p5:()=>buildP5(), p6:()=>buildP6(), p7:()=>buildP7(), p8:()=>buildP8(),
- p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildP11stub(), p12:()=>buildP12stub(),
- p13:()=>buildP13stub(), p14:()=>buildP14stub(), p15:()=>buildP15stub(), p16:()=>buildP16stub(),
+ p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildP11(), p12:()=>buildP12(),
+ p13:()=>buildP13(), p14:()=>buildP14(), p15:()=>buildP15(), p16:()=>buildP16(),
final1:()=>buildFinal1stub(),
};
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn = BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
@@ -660,6 +660,8 @@ const GLOSSARY = [
{ term:'средняя линия треугольника', def:'Отрезок, соединяющий середины двух сторон треугольника. Параллелен третьей стороне и равен её половине.', sec:'p13', aliases:['средняя линия треугольника','средней линии треугольника','средние линии треугольника'] },
{ term:'средняя линия трапеции', def:'Отрезок, соединяющий середины боковых сторон трапеции. Равен полусумме оснований.', sec:'p14', aliases:['средняя линия трапеции','средней линии трапеции'] },
{ term:'медиана', def:'Отрезок, проведённый из вершины треугольника к середине противоположной стороны.', sec:'p12', aliases:['медиана','медианы','медиан','медианам','медиану'] },
+ { term:'центроид', def:'Точка пересечения медиан треугольника (центр тяжести). Делит каждую медиану в отношении 2:1 от вершины.', sec:'p12', aliases:['центроид','центроида','центроиде'] },
+ { term:'основания трапеции', def:'Параллельные стороны трапеции, обозначаемые обычно a (большее) и b (меньшее основание).', sec:'p14', aliases:['основания трапеции','основаниям трапеции','основание трапеции','основания'] },
{ term:'теорема Фалеса', def:'Если параллельные прямые пересекают две прямые, они отсекают на них пропорциональные отрезки.', sec:'p11', aliases:['теорема Фалеса','теореме Фалеса','теоремы Фалеса'] },
{ term:'подобные треугольники', def:'Треугольники, у которых углы равны попарно и стороны пропорциональны.', sec:'p11', aliases:['подобные треугольники','подобных треугольников','подобными треугольниками'] },
{ term:'касательная', def:'Прямая, имеющая одну общую точку с окружностью и перпендикулярная радиусу в точке касания.', sec:'p11', aliases:['касательная','касательной','касательную','касательных'] },
@@ -3361,12 +3363,1831 @@ function buildP10(){
})();
renderMath(box);
}
-function buildP11stub(){ document.getElementById('p11-body').innerHTML = '
§11 — Волна 1 : содержимое появится в следующем обновлении.
' + secNav('p10','p12'); }
-function buildP12stub(){ document.getElementById('p12-body').innerHTML = '§12 — Волна 1 : содержимое появится в следующем обновлении.
' + secNav('p11','p13'); }
-function buildP13stub(){ document.getElementById('p13-body').innerHTML = '§13 — Волна 1 : содержимое появится в следующем обновлении.
' + secNav('p12','p14'); }
-function buildP14stub(){ document.getElementById('p14-body').innerHTML = '§14 — Волна 1 : содержимое появится в следующем обновлении.
' + secNav('p13','p15'); }
-function buildP15stub(){ document.getElementById('p15-body').innerHTML = '§15 — Волна 1 : содержимое появится в следующем обновлении.
' + secNav('p14','p16'); }
-function buildP16stub(){ document.getElementById('p16-body').innerHTML = '§16 — Волна 1 : содержимое появится в следующем обновлении.
' + secNav('p15','final1'); }
+function buildP11(){
+ const box = document.getElementById('p11-body');
+ let html = '';
+
+ html += makeCard('theory','Теорема Фалеса','11.1',`
+ Теорема Фалеса. Если на одной стороне угла отложены равные отрезки и через их концы проведены прямые, параллельные другой стороне угла, то эти прямые отсекают на второй стороне угла равные отрезки.
+ Обобщение: если параллельные прямые пересекают две прямые (секущие) и отсекают на одной из них равные отрезки, то и на другой они отсекают равные отрезки.
+ Формально: $a_1\\parallel a_2\\parallel a_3$, $OA_1=A_1A_2 \\Rightarrow OB_1=B_1B_2$.
`);
+
+ html += makeCard('rule','Деление отрезка на n равных частей','11.2',`
+ Алгоритм (деление $AB$ на $n$ частей):
+
+ Из точки $A$ провести произвольный луч $AC$, не совпадающий с $AB$.
+ На луче $AC$ отложить $n$ равных отрезков: $AA_1=A_1A_2=\\ldots=A_{n-1}A_n$.
+ Соединить $A_n$ с $B$.
+ Через $A_1,A_2,\\ldots,A_{n-1}$ провести прямые, параллельные $A_nB$.
+ Точки пересечения с $AB$ — искомые точки деления.
+ `);
+
+ html += makeCard('example','Доказательство теоремы Фалеса','11.3',`
+ Через $A_1$ проведём прямую, параллельную $OB$. Она пересекает $A_2B_2$ в точке $C$.
+ $\\triangle OA_1B_1 \\cong \\triangle A_1A_2C$ (два угла и сторона): $\\angle A_1OB_1 = \\angle A_1A_2C$ (параллельные), $OA_1=A_1A_2$ (условие). $\\Rightarrow B_1C = OB_1$.
+ Аналогично $\\triangle A_1B_1C \\cong \\triangle A_2B_2C'$... итог: $OB_1 = B_1B_2$. $\\square$
`);
+
+ /* INTERACTIVE 1: SVG Теорема Фалеса */
+ html += `
+
+
Перетащи вершину O или точку A₁ . Отрезки на обеих сторонах угла остаются равными.
+
Количество отрезков n = 3
+
+
+
`;
+
+ /* INTERACTIVE 2: Конструктор деления отрезка */
+ html += `
+
+
Выбери число частей и нажми «Построить» — увидишь пошаговое построение.
+
+ ${[2,3,4,5,6,7,8].map(n=>`${n} `).join('')}
+
+
+
+ Построить пошагово
+ Сброс
+
+
+
`;
+
+ /* INTERACTIVE 3: Тренажёр */
+ html += `
+
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* INTERACTIVE 4: DnD — равные / не равные отрезки */
+ html += `
+
+
Три параллельных прямых отсекают равные отрезки на первой секущей. Распредели отрезки по группам.
+ ${DND_HINT_HTML}
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 5: Босс §11 */
+ html += `
+
+
+5 XP за каждую верную задачу.
+
+
`;
+
+ html += `
+
+
+ Я прочитал §11 (+10 XP)
+
+
`;
+
+ html += secNav('p10','p12');
+ box.innerHTML = html;
+
+ /* == SVG Теорема Фалеса == */
+ (function(){
+ const W=380, H=300;
+ let Ox=60, Oy=240, A1x=140, A1y=100;
+ let nPts=3;
+
+ function drawThales(){
+ const n = nPts;
+ const dx = A1x - Ox, dy = A1y - Oy;
+ const len1 = Math.hypot(dx, dy);
+ // unit vector for ray OA
+ const ux = dx/len1, uy = dy/len1;
+ // second ray: perpendicular-ish offset
+ const ang2 = Math.atan2(dy,dx) + 0.55;
+ const ux2 = Math.cos(ang2), uy2 = Math.sin(ang2);
+ const step1 = len1 / n;
+
+ // points on ray 1: O, A1, A2, ... An
+ const ray1 = [];
+ for(let i=0;i<=n;i++) ray1.push({x: Ox + ux*step1*i, y: Oy + uy*step1*i});
+ // points on ray 2 — Thales says spacing = step1 * |ray2_unit_proj| adjusted so we get EQUAL spacing
+ // We use the same arc-length step for consistent visual: step2 = step1 (Thales guarantees it)
+ const step2 = step1;
+ const ray2 = [];
+ for(let i=0;i<=n;i++) ray2.push({x: Ox + ux2*step2*i, y: Oy + uy2*step2*i});
+
+ let s = ``;
+
+ // parallel lines through each pair
+ const colors=['#0891b2','#10b981','#f59e0b','#8b5cf6','#ef4444','#ec4899'];
+ for(let i=0;i<=n;i++){
+ const p1=ray1[i], p2=ray2[i];
+ const vx=p2.x-p1.x, vy=p2.y-p1.y;
+ const ext=200;
+ const nx=vx/Math.hypot(vx,vy), ny=vy/Math.hypot(vx,vy);
+ if(i===0){
+ // just the vertex
+ } else {
+ s+=` `;
+ }
+ }
+
+ // ray 1
+ s+=` `;
+ // ray 2
+ s+=` `;
+
+ // tick marks and labels on ray1
+ for(let i=1;i<=n;i++){
+ const p=ray1[i];
+ s+=` `;
+ const lx=(p.x+ux*12).toFixed(1), ly=(p.y+uy*12).toFixed(1);
+ s+=`A${i} `;
+ }
+ // tick marks and labels on ray2
+ for(let i=1;i<=n;i++){
+ const p=ray2[i];
+ s+=` `;
+ const lx=(p.x+ux2*12).toFixed(1), ly=(p.y+uy2*12).toFixed(1);
+ s+=`B${i} `;
+ }
+
+ // O vertex — draggable
+ s+=` `;
+ s+=` `;
+ s+=`O `;
+
+ // A1 draggable
+ s+=` `;
+ s+=` `;
+
+ s+=` `;
+
+ const wrap = document.getElementById('p11-thales-svg');
+ wrap.innerHTML = s;
+ const svgEl = wrap.querySelector('svg');
+
+ svgEl.querySelectorAll('.p11-drag').forEach(el=>{
+ el.addEventListener('pointerdown', ev=>{
+ if(ev.button!==undefined&&ev.button!==0) return;
+ const vname = el.dataset.v;
+ try{el.setPointerCapture(ev.pointerId);}catch(e){}
+ function onMove(e){
+ const rect=svgEl.getBoundingClientRect();
+ const sx=W/rect.width, sy=H/rect.height;
+ const nx=(e.clientX-rect.left)*sx, ny=(e.clientY-rect.top)*sy;
+ if(vname==='O'){ Ox=Math.max(10,Math.min(W-10,nx)); Oy=Math.max(10,Math.min(H-10,ny)); }
+ else if(vname==='A1'){ A1x=Math.max(10,Math.min(W-10,nx)); A1y=Math.max(10,Math.min(H-10,ny)); }
+ drawThales();
+ }
+ function onUp(){ el.removeEventListener('pointermove',onMove); el.removeEventListener('pointerup',onUp); el.removeEventListener('pointercancel',onUp); }
+ el.addEventListener('pointermove',onMove); el.addEventListener('pointerup',onUp); el.addEventListener('pointercancel',onUp);
+ });
+ });
+
+ const segLen = step1.toFixed(1);
+ document.getElementById('p11-thales-info').innerHTML =
+ `Все отрезки на первой стороне равны: OA₁ = A₁A₂ = … = ${segLen} Теорема Фалеса гарантирует: OB₁ = B₁B₂ = … = ${segLen} на второй стороне.`;
+ }
+
+ document.getElementById('p11-n-sl').addEventListener('input', function(){ nPts=+this.value; document.getElementById('p11-n-val').textContent=nPts; drawThales(); });
+ drawThales();
+ })();
+
+ /* == Конструктор деления == */
+ (function(){
+ let nDiv=3, animStep=0, animTimer=null;
+
+ function drawDiv(step){
+ const W2=400, H2=260;
+ const Ax=40, Ay=180, Bx=360, By=180;
+ const rayAng=-0.55;
+ const rayLen=120;
+ const ux=Math.cos(rayAng), uy=Math.sin(rayAng);
+ const segR = rayLen / nDiv;
+
+ let s=``;
+
+ // AB
+ s+=` `;
+ s+=` `;
+ s+=` `;
+ s+=`A `;
+ s+=`B `;
+
+ if(step >= 1){
+ // auxiliary ray
+ const Cnx=Ax+ux*rayLen, Cny=Ay+uy*rayLen;
+ s+=` `;
+ s+=`A${nDiv} `;
+ }
+ if(step >= 2){
+ // equal segments on ray
+ for(let i=1;i<=nDiv;i++){
+ const px=Ax+ux*segR*i, py=Ay+uy*segR*i;
+ s+=` `;
+ if(iA${i}`;
+ }
+ }
+ if(step >= 3){
+ // line AnB
+ const Anx=Ax+ux*rayLen, Any=Ay+uy*rayLen;
+ s+=` `;
+ }
+ if(step >= 4){
+ // parallels through Ai to AB
+ const Anx=Ax+ux*rayLen, Any=Ay+uy*rayLen;
+ const vx=Bx-Anx, vy=By-Any;
+ const colors=['#10b981','#8b5cf6','#ec4899','#f59e0b','#0891b2','#ef4444','#64748b'];
+ for(let i=1;i `;
+ s+=` `;
+ // label
+ const frac=i+'/'+nDiv;
+ s+=`${frac} `;
+ }
+ }
+ s+=` `;
+ document.getElementById('p11-div-svg').innerHTML=s;
+ const stepTexts=['','Шаг 1: Из A проведём вспомогательный луч AC.','Шаг 2: На луче откладываем n равных отрезков AA₁=A₁A₂=…','Шаг 3: Соединяем последнюю точку Aₙ с B.','Шаг 4: Через A₁,…,Aₙ₋₁ проводим прямые, параллельные AₙB. Готово — отрезки равны!'];
+ document.getElementById('p11-div-step').textContent = stepTexts[Math.min(step,4)] || '';
+ }
+
+ document.querySelectorAll('.p11-ndiv-btn').forEach(btn=>{
+ btn.addEventListener('click',()=>{
+ nDiv=+btn.dataset.n;
+ document.querySelectorAll('.p11-ndiv-btn').forEach(b=>b.className='btn p11-ndiv-btn');
+ btn.className='btn primary p11-ndiv-btn';
+ animStep=0; if(animTimer){clearInterval(animTimer);animTimer=null;} drawDiv(0);
+ });
+ });
+ document.getElementById('p11-div-build').addEventListener('click',()=>{
+ if(animTimer) return;
+ animStep=0; drawDiv(0);
+ animTimer=setInterval(()=>{ animStep++; drawDiv(animStep); if(animStep>=4){clearInterval(animTimer);animTimer=null;addXp(3,'p11-div');} },900);
+ });
+ document.getElementById('p11-div-reset').addEventListener('click',()=>{ if(animTimer){clearInterval(animTimer);animTimer=null;} animStep=0; drawDiv(0); });
+ // activate first button
+ document.querySelector('.p11-ndiv-btn[data-n="3"]').className='btn primary p11-ndiv-btn';
+ drawDiv(0);
+ })();
+
+ /* == Тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'Отрезок $AB=20$. Разделить на $4$ равные части. Длина каждой части?', ans:5, hint:'20÷4=5'},
+ {q:'На одной стороне угла $OA_1=A_1A_2=3$. На второй — $OB_1=B_1B_2$. Найди $B_1B_2$.', ans:3, hint:'По теореме Фалеса B₁B₂ = A₁A₂ = 3'},
+ {q:'Три параллельные прямые отсекают на одной секущей отрезки по $7$. Сколько равных отрезков на второй секущей?', ans:3, hint:'По теореме Фалеса — тоже 3 равных отрезка по 7'},
+ {q:'Отрезок $AB=36$. Разделить на $9$ равных частей. Длина каждой части?', ans:4, hint:'36÷9=4'},
+ {q:'$OA_1=5$, $A_1A_2=5$, $A_2A_3=5$. Параллели через $A_1,A_2,A_3$ дают на второй стороне $OB_1$. Найди $OB_1$.', ans:5, hint:'OA₁=5, по Фалесу OB₁=5'},
+ ];
+ let idx=0, score=0;
+ function show(){ document.getElementById('p11-tr-i').textContent=idx+1; document.getElementById('p11-tr-task').innerHTML=tasks[idx].q; renderMath(document.getElementById('p11-tr-task')); document.getElementById('p11-tr-ans').value=''; document.getElementById('p11-tr-fb').style.display='none'; }
+ document.getElementById('p11-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p11-tr-score').textContent=0;show();});
+ document.getElementById('p11-tr-go').addEventListener('click',()=>{
+ const ans=+document.getElementById('p11-tr-ans').value; const fb=document.getElementById('p11-tr-fb');
+ if(ans===tasks[idx].ans){ score++;document.getElementById('p11-tr-score').textContent=score;addXp(3,'p11-train');bumpProgress('p11',5); if(idxshow(),900);}else{feedback(fb,true,'Все задачи! +5 XP');addXp(5,'p11-train-all');} }
+ else feedback(fb,false,'Неверно. Подсказка: '+tasks[idx].hint);
+ });
+ document.getElementById('p11-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p11-tr-go').click();});
+ show();
+ })();
+
+ /* == DnD == */
+ (function(){
+ const items=[
+ {id:'d1',html:'OA₁ и A₁A₂ (на первой стороне, равны по условию)', ans:'eq'},
+ {id:'d2',html:'OB₁ и B₁B₂ (на второй стороне, по Фалесу)', ans:'eq'},
+ {id:'d3',html:'OA₁ и OB₁ (разные стороны угла)', ans:'neq'},
+ {id:'d4',html:'A₁A₂ и B₁B₂ (параллельные секущие)', ans:'eq'},
+ {id:'d5',html:'OA₁ и A₂A₃ (на одной стороне, равны)', ans:'eq'},
+ {id:'d6',html:'A₁B₁ и A₂B₂ (боковые — не секущие)', ans:'neq'},
+ ];
+ const sorter=setupSorter({poolId:'p11-dnd-pool',scopeSelector:'#p11-dnd-wrap',items,cats:['eq','neq']});
+ document.getElementById('p11-dnd-reset').addEventListener('click',()=>{sorter.reset();document.getElementById('p11-dnd-fb').style.display='none';});
+ document.getElementById('p11-dnd-check').addEventListener('click',()=>{
+ let ok=0; items.forEach(it=>{if(sorter.placed[it.id]===it.ans)ok++;});
+ const fb=document.getElementById('p11-dnd-fb');
+ if(ok===items.length){feedback(fb,true,'Все верно! +5 XP');addXp(5,'p11-dnd');bumpProgress('p11',15);}
+ else feedback(fb,false,'Верно: '+ok+' из '+items.length+'.');
+ });
+ })();
+
+ /* == Босс §11 == */
+ (function(){
+ const tasks=[
+ {q:'Три параллельных прямых отсекают на первой секущей отрезки $4, 4, 4$. Найди сумму отрезков на второй секущей.', ans:12, hint:'По Фалесу тоже 4+4+4=12'},
+ {q:'Отрезок $AB=35$. Разделить на $7$ равных частей. Длина каждой?', ans:5, hint:'35÷7=5'},
+ {q:'$OA_1=6$, параллели дают $OB_1$ и $B_1B_2=6$. Найди $B_1B_2$.', ans:6, hint:'По Фалесу B₁B₂ = A₁A₂ = 6'},
+ {q:'На одной стороне угла отложены $n=5$ равных отрезков по $3$. Сколько равных отрезков будет на второй стороне?', ans:5, hint:'По Фалесу — тоже 5'},
+ ];
+ const bossBox=document.getElementById('p11-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p11BossSolved=new Set();
+ })();
+ renderMath(box);
+}
+function buildP12(){
+ const box = document.getElementById('p12-body');
+ let html = '';
+
+ html += makeCard('theory','Медианы треугольника','12.1',`
+ Медиана треугольника — отрезок, соединяющий вершину с серединой противоположной стороны.
+ В треугольнике $ABC$: $AM_A$, $BM_B$, $CM_C$ — три медианы, где $M_A$, $M_B$, $M_C$ — середины сторон $BC$, $AC$, $AB$.
`);
+
+ html += makeCard('rule','Свойство медиан','12.2',`
+ Теорема. Три медианы треугольника пересекаются в одной точке $G$ (центроид , центр тяжести), которая делит каждую медиану в отношении $2:1$, считая от вершины:
+ \\[AG:GM_A = BG:GM_B = CG:GM_C = 2:1\\]
+ Следствие: $AG = \\dfrac{2}{3}AM_A$, $GM_A = \\dfrac{1}{3}AM_A$.
`);
+
+ html += makeCard('example','Формулы для медиан','12.3',`
+ Длина медианы $m_a$ (от вершины $A$ к середине $BC$):
+ \\[m_a = \\frac{1}{2}\\sqrt{2b^2+2c^2-a^2}\\]
+ где $a,b,c$ — стороны треугольника.
+ Если $G$ делит $AM_A$ в отношении $2:1$: дано $|GM_A|=x \\Rightarrow |AM_A|=3x$, $|AG|=2x$.
`);
+
+ /* INTERACTIVE 1: SVG-треугольник с медианами */
+ html += `
+
+
Перетащи вершины A , B , C . Медианы пересекаются в центроиде G (соотношение 2:1).
+
+
+
`;
+
+ /* INTERACTIVE 2: Доказательство */
+ html += `
+
+
+
+
+ Дальше
+ Сначала
+
+
`;
+
+ /* INTERACTIVE 3: Калькулятор */
+ html += `
+
+
Введи длину медианы — найди отрезки AG и GM.
+
+ Длина медианы $AM$ =
+ Вычислить
+
+
+
`;
+
+ /* INTERACTIVE 4: Тренажёр */
+ html += `
+
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* INTERACTIVE 5: Босс §12 */
+ html += `
+
+
+5 XP за каждую верную задачу.
+
+
`;
+
+ html += `
+
+
+ Я прочитал §12 (+10 XP)
+
+
`;
+
+ html += secNav('p11','p13');
+ box.innerHTML = html;
+
+ /* == SVG медианы == */
+ (function(){
+ const W=380, H=300;
+ let A={x:190,y:30}, B={x:50,y:270}, C={x:330,y:270};
+ function mid(P,Q){ return {x:(P.x+Q.x)/2, y:(P.y+Q.y)/2}; }
+ function centroid(A,B,C){ return {x:(A.x+B.x+C.x)/3, y:(A.y+B.y+C.y)/3}; }
+ function dist(P,Q){ return Math.hypot(Q.x-P.x, Q.y-P.y); }
+
+ function redraw(){
+ const Ma=mid(B,C), Mb=mid(A,C), Mc=mid(A,B);
+ const G=centroid(A,B,C);
+ const dAG=dist(A,G), dGMa=dist(G,Ma);
+ const ratio=(dAG/dGMa).toFixed(2);
+
+ let s=``;
+ // triangle fill
+ s+=` `;
+ // medians
+ const mCols=['#10b981','#f59e0b','#8b5cf6'];
+ [[A,Ma],[B,Mb],[C,Mc]].forEach(([V,M],i)=>{
+ s+=` `;
+ // midpoint dot
+ s+=` `;
+ // midpoint label
+ const ox=[0,-14,14][i], oy=[14,-4,-4][i];
+ s+=`${['Mₐ','Mₙ','Mᶜ'][i]} `;
+ });
+ // centroid G
+ s+=` `;
+ s+=`G `;
+ // vertices
+ const vNames=['A','B','C'], vPts=[A,B,C];
+ vPts.forEach((V,i)=>{
+ s+=` `;
+ s+=` `;
+ const ox2=[0,-14,14][i], oy2=[-14,14,14][i];
+ s+=`${vNames[i]} `;
+ });
+ s+=` `;
+
+ const wrap=document.getElementById('p12-med-svg'); wrap.innerHTML=s;
+ const svgEl=wrap.querySelector('svg');
+ svgEl.querySelectorAll('.p12-vdrag').forEach(el=>{
+ el.addEventListener('pointerdown',ev=>{
+ if(ev.button!==undefined&&ev.button!==0) return;
+ const vname=el.dataset.v;
+ try{el.setPointerCapture(ev.pointerId);}catch(e){}
+ function onMove(e){
+ const rect=svgEl.getBoundingClientRect();
+ const sx=W/rect.width, sy=H/rect.height;
+ const nx=Math.max(10,Math.min(W-10,(e.clientX-rect.left)*sx));
+ const ny=Math.max(10,Math.min(H-10,(e.clientY-rect.top)*sy));
+ if(vname==='A') A={x:nx,y:ny};
+ else if(vname==='B') B={x:nx,y:ny};
+ else C={x:nx,y:ny};
+ redraw();
+ }
+ function onUp(){ el.removeEventListener('pointermove',onMove); el.removeEventListener('pointerup',onUp); el.removeEventListener('pointercancel',onUp); }
+ el.addEventListener('pointermove',onMove); el.addEventListener('pointerup',onUp); el.addEventListener('pointercancel',onUp);
+ });
+ });
+
+ document.getElementById('p12-med-info').innerHTML=`
+ |AG| : |GM_A|
${dAG.toFixed(1)} : ${dGMa.toFixed(1)} ≈ ${ratio}
+ Медиана AM_A
${dist(A,Ma).toFixed(1)}
+ Медиана BM_B
${dist(B,Mb).toFixed(1)}
+ `;
+ }
+ redraw();
+ })();
+
+ /* == Доказательство == */
+ (function(){
+ const A={x:160,y:25}, B={x:30,y:215}, C={x:290,y:215};
+ function mid(P,Q){ return {x:(P.x+Q.x)/2, y:(P.y+Q.y)/2}; }
+ const Ma=mid(B,C), Mb=mid(A,C);
+ const G={x:(A.x+B.x+C.x)/3, y:(A.y+B.y+C.y)/3};
+ const steps=[
+ {text:'Дано: $\\triangle ABC$, $M_a$ — середина $BC$, $M_b$ — середина $AC$, $G$ — точка пересечения медиан $AM_a$ и $BM_b$. Доказать: $AG:GM_a=2:1$.', h:'base'},
+ {text:'Шаг 1. Рассмотрим среднюю линию $M_aM_b$ треугольника $ABC$. По свойству средней линии: $M_aM_b \\parallel AB$ и $M_aM_b = \\dfrac{1}{2}AB$.', h:'midline'},
+ {text:'Шаг 2. Рассмотрим $\\triangle AM_bG$ и $\\triangle M_aM_bG$. $\\angle GAM_b = \\angle GM_aM_b$ (как накрест лежащие при $AB\\parallel M_aM_b$); $\\angle AGM_b = \\angle M_aGM_b$ (вертикальные).', h:'simtri'},
+ {text:'Шаг 3. $\\triangle AGM_b \\sim \\triangle M_aGM_b$ по двум углам. Коэффициент подобия: $k = AB/(M_aM_b) = 2$.', h:'simtri'},
+ {text:'Шаг 4. Значит $AG/GM_a = 2/1$. Аналогично для остальных медиан. Все три медианы делятся точкой $G$ в отношении $2:1$. $\\square$', h:'done'},
+ ];
+ let step=0;
+ function draw(h){
+ let s=``;
+ s+=` `;
+ if(h==='midline'||h==='simtri'||h==='done'){
+ s+=` `;
+ }
+ if(h==='simtri'||h==='done'){
+ s+=` `;
+ s+=` `;
+ }
+ // medians
+ s+=` `;
+ s+=` `;
+ s+=` `;
+ s+=`G `;
+ [[A,'A'],[B,'B'],[C,'C'],[Ma,'Mₐ'],[Mb,'Mₙ']].forEach(([P,lbl])=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p12-proof-svg').innerHTML=s;
+ }
+ function show(){ const st=steps[step]; document.getElementById('p12-proof-step').innerHTML=st.text; renderMath(document.getElementById('p12-proof-step')); draw(st.h); document.getElementById('p12-proof-next').textContent=step{ if(step{step=0;show();document.getElementById('p12-proof-next').disabled=false;});
+ show();
+ })();
+
+ /* == Калькулятор == */
+ (function(){
+ function calc(){
+ const m=+document.getElementById('p12-cmed').value;
+ if(m<=0||isNaN(m)){ document.getElementById('p12-calc-out').innerHTML='Введи положительное число. '; return; }
+ const AG=m*2/3, GM=m/3;
+ document.getElementById('p12-calc-out').innerHTML=`$AM = ${m}$ (вся медиана) $AG = \\dfrac{2}{3} \\cdot ${m} = ${fmt(AG)}$ (от вершины до центроида) $GM_A = \\dfrac{1}{3} \\cdot ${m} = ${fmt(GM)}$ (от центроида до середины стороны) Отношение $AG:GM_A = ${fmt(AG)}:${fmt(GM)} = 2:1$ ✓`;
+ renderMath(document.getElementById('p12-calc-out'));
+ addXp(2,'p12-calc');
+ }
+ document.getElementById('p12-calc-go').addEventListener('click',calc);
+ calc();
+ })();
+
+ /* == Тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'Медиана $AM=18$. Найди $|AG|$.', ans:12, hint:'AG=(2/3)·18=12'},
+ {q:'Медиана $BM=15$. Найди $|GM|$.', ans:5, hint:'GM=(1/3)·15=5'},
+ {q:'$|GM_A|=4$. Найди длину медианы $AM_A$.', ans:12, hint:'AM=3·GM=3·4=12'},
+ {q:'$|AG|=10$. Найди $|GM_A|$.', ans:5, hint:'GM=AG/2=10/2=5'},
+ {q:'Медиана $CM=21$. Найди $|CG|$.', ans:14, hint:'CG=(2/3)·21=14'},
+ ];
+ let idx=0,score=0;
+ function show(){ document.getElementById('p12-tr-i').textContent=idx+1; document.getElementById('p12-tr-task').innerHTML=tasks[idx].q; renderMath(document.getElementById('p12-tr-task')); document.getElementById('p12-tr-ans').value=''; document.getElementById('p12-tr-fb').style.display='none'; }
+ document.getElementById('p12-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p12-tr-score').textContent=0;show();});
+ document.getElementById('p12-tr-go').addEventListener('click',()=>{
+ const ans=+document.getElementById('p12-tr-ans').value; const fb=document.getElementById('p12-tr-fb');
+ if(ans===tasks[idx].ans){ score++;document.getElementById('p12-tr-score').textContent=score;addXp(3,'p12-train');bumpProgress('p12',5); if(idxshow(),900);}else{feedback(fb,true,'Все задачи! +5 XP');addXp(5,'p12-train-all');} }
+ else feedback(fb,false,'Неверно. Подсказка: '+tasks[idx].hint);
+ });
+ document.getElementById('p12-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p12-tr-go').click();});
+ show();
+ })();
+
+ /* == Босс §12 == */
+ (function(){
+ const tasks=[
+ {q:'$|GM_A|=6$. Найди $|AG|$.', ans:12, hint:'AG=2·GM=2·6=12'},
+ {q:'Медиана $AM=27$. Найди $|AG|$.', ans:18, hint:'AG=(2/3)·27=18'},
+ {q:'$|AG|=8$. Найди длину медианы $AM$.', ans:12, hint:'AM=(3/2)·AG=(3/2)·8=12'},
+ {q:'Три медианы имеют длины $12$, $15$, $18$. Найди $|GM_A|$ для медианы длиной $12$.', ans:4, hint:'GM=(1/3)·12=4'},
+ ];
+ const bossBox=document.getElementById('p12-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p12BossSolved=new Set();
+ })();
+ renderMath(box);
+}
+function buildP13(){
+ const box = document.getElementById('p13-body');
+ let html = '';
+
+ html += makeCard('theory','Средняя линия треугольника','13.1',`
+ Средняя линия треугольника — отрезок, соединяющий середины двух его сторон.
+ В $\\triangle ABC$: $M_1$ — середина $AB$, $M_2$ — середина $AC$. Тогда $M_1M_2$ — средняя линия, параллельная $BC$.
`);
+
+ html += makeCard('rule','Свойство средней линии','13.2',`
+ Теорема. Средняя линия треугольника параллельна третьей стороне и равна её половине:
+ \\[M_1M_2 \\parallel BC, \\quad M_1M_2 = \\frac{1}{2}BC\\]
+ В треугольнике три средние линии — они образуют срединный треугольник , делящий исходный на 4 равных треугольника.
`);
+
+ html += makeCard('example','Периметр срединного треугольника','13.3',`
+ Если стороны $\\triangle ABC$ равны $a$, $b$, $c$, то стороны срединного треугольника:
+ \\[\\frac{a}{2},\\quad \\frac{b}{2},\\quad \\frac{c}{2}\\]
+ Периметр срединного треугольника $= \\dfrac{a+b+c}{2} = \\dfrac{P}{2}$.
`);
+
+ /* INTERACTIVE 1: SVG треугольник со средними линиями */
+ html += `
+
+
Перетащи A , B , C . Все три средние линии показаны, срединный треугольник подсвечен.
+
+
+
`;
+
+ /* INTERACTIVE 2: Доказательство */
+ html += `
+
+
+
+
+ Дальше
+ Сначала
+
+
`;
+
+ /* INTERACTIVE 3: Тренажёр */
+ html += `
+
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* INTERACTIVE 4: Mini-quiz верно/неверно */
+ html += `
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 5: DnD */
+ html += `
+
+
Перетащи отрезки: какие являются средними линиями треугольника?
+ ${DND_HINT_HTML}
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 6: Босс §13 */
+ html += `
+
+
+5 XP за каждую верную задачу.
+
+
`;
+
+ html += `
+
+
+ Я прочитал §13 (+10 XP)
+
+
`;
+
+ html += secNav('p12','p14');
+ box.innerHTML = html;
+
+ /* == SVG средние линии == */
+ (function(){
+ const W=380, H=300;
+ let A={x:190,y:30}, B={x:40,y:270}, C={x:340,y:270};
+ function mid(P,Q){ return {x:(P.x+Q.x)/2, y:(P.y+Q.y)/2}; }
+ function dist(P,Q){ return Math.hypot(Q.x-P.x, Q.y-P.y); }
+
+ function redraw(){
+ const M1=mid(A,B), M2=mid(A,C), M3=mid(B,C);
+ let s=``;
+ // outer triangle
+ s+=` `;
+ // median triangle fill
+ s+=` `;
+ // middle lines labels
+ const mlCols=['#10b981','#f59e0b','#8b5cf6'];
+ const mlPairs=[[M1,M2],[M2,M3],[M1,M3]];
+ const mlLabels=['M₁M₂ ∥ BC','M₂M₃ ∥ AB','M₁M₃ ∥ AC'];
+ mlPairs.forEach(([P,Q],i)=>{
+ const mx=(P.x+Q.x)/2, my=(P.y+Q.y)/2;
+ s+=`${mlLabels[i]} `;
+ });
+ // midpoints
+ [[M1,'M₁'],[M2,'M₂'],[M3,'M₃']].forEach(([P,lbl],i)=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ // vertices
+ ['A','B','C'].forEach((lbl,i)=>{
+ const V=[A,B,C][i];
+ s+=` `;
+ s+=` `;
+ const ox=[0,-16,16][i], oy=[-16,14,14][i];
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ const wrap=document.getElementById('p13-ml-svg'); wrap.innerHTML=s;
+ const svgEl=wrap.querySelector('svg');
+ svgEl.querySelectorAll('.p13-vd').forEach(el=>{
+ el.addEventListener('pointerdown',ev=>{
+ if(ev.button!==undefined&&ev.button!==0) return;
+ const vname=el.dataset.v;
+ try{el.setPointerCapture(ev.pointerId);}catch(e){}
+ function onMove(e){
+ const rect=svgEl.getBoundingClientRect();
+ const nx=Math.max(10,Math.min(W-10,(e.clientX-rect.left)*W/rect.width));
+ const ny=Math.max(10,Math.min(H-10,(e.clientY-rect.top)*H/rect.height));
+ if(vname==='A') A={x:nx,y:ny};
+ else if(vname==='B') B={x:nx,y:ny};
+ else C={x:nx,y:ny};
+ redraw();
+ }
+ function onUp(){ el.removeEventListener('pointermove',onMove); el.removeEventListener('pointerup',onUp); el.removeEventListener('pointercancel',onUp); }
+ el.addEventListener('pointermove',onMove); el.addEventListener('pointerup',onUp); el.addEventListener('pointercancel',onUp);
+ });
+ });
+ const bc=dist(B,C), ac=dist(A,C), ab=dist(A,B);
+ const m12=dist(M1,M2), m23=dist(M2,M3), m13=dist(M1,M3);
+ document.getElementById('p13-ml-info').innerHTML=`
+ M₁M₂ = BC/2
${m12.toFixed(1)} = ${(bc/2).toFixed(1)}
+ M₂M₃ = AB/2
${m23.toFixed(1)} = ${(ab/2).toFixed(1)}
+ M₁M₃ = AC/2
${m13.toFixed(1)} = ${(ac/2).toFixed(1)}
+ P срединного / P исх.
${(m12+m23+m13).toFixed(1)} / ${(bc+ac+ab).toFixed(1)} = 1:2 `;
+ }
+ redraw();
+ })();
+
+ /* == Доказательство == */
+ (function(){
+ const A={x:160,y:25}, B={x:30,y:215}, C={x:290,y:215};
+ function mid(P,Q){ return {x:(P.x+Q.x)/2, y:(P.y+Q.y)/2}; }
+ const M1=mid(A,B), M2=mid(A,C);
+ const steps=[
+ {text:'Дано: $\\triangle ABC$, $M_1$ — середина $AB$, $M_2$ — середина $AC$. Доказать: $M_1M_2 \\parallel BC$, $M_1M_2 = \\dfrac{1}{2}BC$.', h:'base'},
+ {text:'Шаг 1. Отложим от $M_2$ отрезок $M_2D = M_1M_2$, так что $D$ лежит на луче $M_1M_2$ за $M_2$.', h:'step1'},
+ {text:'Шаг 2. Рассмотрим $\\triangle AM_1M_2$ и $\\triangle CM_2D$. $AM_1=CM_2=\\dfrac{1}{2}$-сторон; $\\angle AM_1M_2=\\angle CDM_2$ (вертикальные); $M_1M_2=M_2D$. По признаку «два угла и сторона»: $\\triangle AM_1M_2 \\cong \\triangle CDM_2$.', h:'step2'},
+ {text:'Шаг 3. Из равенства треугольников: $AM_1=DC$ и $\\angle M_1AM_2=\\angle DCM_2$, значит $AM_1 \\parallel DC$, т.е. $AB \\parallel CD$.', h:'step3'},
+ {text:'Вывод. $M_1BDC$ — параллелограмм ($M_1B \\parallel CD$, $M_1B=DC$). Значит $M_1D \\parallel BC$ и $BD=M_1M_2$. Но $BD=\\dfrac{1}{2}BC$ (так как $M_1$ — середина). Итак, $M_1M_2 \\parallel BC$ и $M_1M_2=\\dfrac{1}{2}BC$. $\\square$', h:'done'},
+ ];
+ let step=0;
+ function draw(h){
+ const D={x:M2.x+(M2.x-M1.x), y:M2.y+(M2.y-M1.y)};
+ let s=``;
+ s+=` `;
+ s+=` `;
+ if(h==='step1'||h==='step2'||h==='step3'||h==='done'){
+ s+=` `;
+ s+=` `;
+ s+=`D `;
+ }
+ if(h==='step2'||h==='step3'||h==='done'){
+ s+=` `;
+ s+=` `;
+ }
+ if(h==='done'){
+ s+=` `;
+ }
+ [[A,'A'],[B,'B'],[C,'C'],[M1,'M₁'],[M2,'M₂']].forEach(([P,lbl])=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p13-proof-svg').innerHTML=s;
+ }
+ function show(){ const st=steps[step]; document.getElementById('p13-proof-step').innerHTML=st.text; renderMath(document.getElementById('p13-proof-step')); draw(st.h); document.getElementById('p13-proof-next').textContent=step{ if(step{step=0;show();document.getElementById('p13-proof-next').disabled=false;});
+ show();
+ })();
+
+ /* == Тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'$BC=14$. Найди среднюю линию, параллельную $BC$.', ans:7, hint:'M₁M₂=BC/2=14/2=7'},
+ {q:'Средняя линия $= 9$. Найди сторону, которой она параллельна.', ans:18, hint:'BC=2·M₁M₂=2·9=18'},
+ {q:'Стороны треугольника $12, 16, 20$. Найди периметр срединного треугольника.', ans:24, hint:'P/2=(12+16+20)/2=48/2=24'},
+ {q:'Средняя линия треугольника $= 11$. Найди вторую среднюю линию, параллельную стороне $AB=16$.', ans:8, hint:'M₂M₃=AB/2=16/2=8'},
+ {q:'$BC=30$, средняя линия $M_1M_2$. Найди $M_1M_2$.', ans:15, hint:'M₁M₂=BC/2=30/2=15'},
+ ];
+ let idx=0,score=0;
+ function show(){ document.getElementById('p13-tr-i').textContent=idx+1; document.getElementById('p13-tr-task').innerHTML=tasks[idx].q; renderMath(document.getElementById('p13-tr-task')); document.getElementById('p13-tr-ans').value=''; document.getElementById('p13-tr-fb').style.display='none'; }
+ document.getElementById('p13-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p13-tr-score').textContent=0;show();});
+ document.getElementById('p13-tr-go').addEventListener('click',()=>{
+ const ans=+document.getElementById('p13-tr-ans').value; const fb=document.getElementById('p13-tr-fb');
+ if(ans===tasks[idx].ans){ score++;document.getElementById('p13-tr-score').textContent=score;addXp(3,'p13-train');bumpProgress('p13',5); if(idxshow(),900);}else{feedback(fb,true,'Все задачи! +5 XP');addXp(5,'p13-train-all');} }
+ else feedback(fb,false,'Неверно. Подсказка: '+tasks[idx].hint);
+ });
+ document.getElementById('p13-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p13-tr-go').click();});
+ show();
+ })();
+
+ /* == Mini-quiz == */
+ (function(){
+ const qs=[
+ {text:'Средняя линия треугольника параллельна третьей стороне.', ans:true},
+ {text:'Средняя линия треугольника равна третьей стороне.', ans:false},
+ {text:'В треугольнике три средние линии.', ans:true},
+ {text:'Срединный треугольник делит исходный на 4 равных треугольника.', ans:true},
+ ];
+ const list=document.getElementById('p13-quiz-list');
+ list.innerHTML=qs.map((q,i)=>`
+
+
${q.text}
+
+ Верно
+ Неверно
+
+
`).join('');
+ const sel={};
+ list.querySelectorAll('.p13-qbtn').forEach(btn=>{
+ btn.addEventListener('click',()=>{
+ const i=btn.dataset.i;
+ sel[i]=btn.dataset.v==='true';
+ list.querySelectorAll(`.p13-qbtn[data-i="${i}"]`).forEach(b=>b.className='btn p13-qbtn');
+ btn.className='btn primary p13-qbtn';
+ });
+ });
+ document.getElementById('p13-quiz-check').addEventListener('click',()=>{
+ let ok=0;
+ qs.forEach((q,i)=>{ if(sel[i]===q.ans) ok++; });
+ const fb=document.getElementById('p13-quiz-fb');
+ if(ok===qs.length){feedback(fb,true,'Все верно! +5 XP');addXp(5,'p13-quiz');bumpProgress('p13',15);}
+ else feedback(fb,false,'Верно: '+ok+' из '+qs.length+'. Средняя линия = ПОЛОВИНА стороны (не равна ей).');
+ });
+ document.getElementById('p13-quiz-reset').addEventListener('click',()=>{ Object.keys(sel).forEach(k=>delete sel[k]); list.querySelectorAll('.p13-qbtn').forEach(b=>b.className='btn p13-qbtn'); document.getElementById('p13-quiz-fb').style.display='none'; });
+ })();
+
+ /* == DnD == */
+ (function(){
+ const items=[
+ {id:'s1',html:'Отрезок, соединяющий середины AB и AC', ans:'yes'},
+ {id:'s2',html:'Медиана из A к середине BC', ans:'no'},
+ {id:'s3',html:'Отрезок, соединяющий середины BC и AC', ans:'yes'},
+ {id:'s4',html:'Высота из A на BC', ans:'no'},
+ {id:'s5',html:'Отрезок, соединяющий середины AB и BC', ans:'yes'},
+ {id:'s6',html:'Биссектриса угла A', ans:'no'},
+ ];
+ const sorter=setupSorter({poolId:'p13-dnd-pool',scopeSelector:'#p13-dnd-wrap',items,cats:['yes','no']});
+ document.getElementById('p13-dnd-reset').addEventListener('click',()=>{sorter.reset();document.getElementById('p13-dnd-fb').style.display='none';});
+ document.getElementById('p13-dnd-check').addEventListener('click',()=>{
+ let ok=0; items.forEach(it=>{if(sorter.placed[it.id]===it.ans)ok++;});
+ const fb=document.getElementById('p13-dnd-fb');
+ if(ok===items.length){feedback(fb,true,'Все верно! +5 XP');addXp(5,'p13-dnd');bumpProgress('p13',15);}
+ else feedback(fb,false,'Верно: '+ok+' из '+items.length+'. Средняя линия соединяет середины двух сторон.');
+ });
+ })();
+
+ /* == Босс §13 == */
+ (function(){
+ const tasks=[
+ {q:'$BC=26$. Найди среднюю линию, параллельную $BC$.', ans:13, hint:'M₁M₂=BC/2=26/2=13'},
+ {q:'Средняя линия $= 8.5$. Найди параллельную ей сторону.', ans:17, hint:'BC=2·8.5=17'},
+ {q:'Стороны треугольника $10, 24, 26$. Найди периметр срединного треугольника.', ans:30, hint:'P_mid=(10+24+26)/2=60/2=30'},
+ {q:'$M_1M_2 = 7$, $M_2M_3 = 9$, $M_1M_3 = 5$. Найди наибольшую сторону треугольника.', ans:18, hint:'BC=2·M₁M₂=2·9=18'},
+ ];
+ const bossBox=document.getElementById('p13-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p13BossSolved=new Set();
+ })();
+ renderMath(box);
+}
+function buildP14(){
+ const box = document.getElementById('p14-body');
+ let html = '';
+
+ html += makeCard('theory','Трапеция — определение','14.1',`
+ Трапеция — четырёхугольник, у которого только одна пара противоположных сторон параллельна.
+ Параллельные стороны называются основаниями (обычно $a$ — большее, $b$ — меньшее), а две другие — боковыми сторонами .
+ Виды трапеций:
+
+ Произвольная — боковые стороны не равны.
+ Равнобедренная — боковые стороны равны: $AD=BC$.
+ Прямоугольная — один из углов при боковой стороне прямой.
+ `);
+
+ html += makeCard('rule','Средняя линия трапеции','14.2',`
+ Средняя линия трапеции — отрезок $MN$, соединяющий середины боковых сторон.
+ Свойство: средняя линия параллельна основаниям и равна их полусумме:
+ \\[MN \\parallel AD \\parallel BC, \\quad MN = \\frac{a+b}{2}\\]
+ Площадь трапеции: $S = \\dfrac{(a+b)}{2} \\cdot h = MN \\cdot h$, где $h$ — высота.
`);
+
+ html += makeCard('example','Формулы для трапеции','14.3',`
+ Дано: основания $a$ и $b$, высота $h$, средняя линия $m$.
+ \\[m = \\frac{a+b}{2} \\quad \\Rightarrow \\quad a+b = 2m\\]
+ \\[S = m \\cdot h = \\frac{(a+b)}{2} \\cdot h\\]
+ Из $m$ найти неизвестное основание: $b = 2m - a$.
`);
+
+ /* INTERACTIVE 1: SVG-трапеция draggable */
+ html += `
+
+
Перетащи вершины A , B , C , D . Средняя линия и все формулы пересчитываются живо.
+
+
+
`;
+
+ /* INTERACTIVE 2: Конструктор типов */
+ html += `
+
+
Переключай тип — трапеция меняет форму, свойства обновляются.
+
+ Произвольная
+ Равнобедренная
+ Прямоугольная
+
+
+
+
`;
+
+ /* INTERACTIVE 3: Доказательство */
+ html += `
+
+
+
+
+ Дальше
+ Сначала
+
+
`;
+
+ /* INTERACTIVE 4: Калькулятор */
+ html += ``;
+
+ /* INTERACTIVE 5: Тренажёр */
+ html += `
+
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* INTERACTIVE 6: Босс §14 */
+ html += `
+
+
+5 XP за каждую верную задачу.
+
+
`;
+
+ html += `
+
+
+ Я прочитал §14 (+10 XP)
+
+
`;
+
+ html += secNav('p13','p15');
+ box.innerHTML = html;
+
+ /* == SVG трапеция draggable == */
+ (function(){
+ const W=400, H=280;
+ let A={x:60,y:220}, B={x:340,y:220}, C={x:280,y:70}, D={x:120,y:70};
+ // Keep BC parallel to AD: when dragging C or D, we sync y-coordinate
+ function dist(P,Q){ return Math.hypot(Q.x-P.x, Q.y-P.y); }
+
+ function redraw(){
+ const Ma={x:(A.x+D.x)/2, y:(A.y+D.y)/2};
+ const Mb={x:(B.x+C.x)/2, y:(B.y+C.y)/2};
+ const a=dist(A,B), b=dist(D,C);
+ const m=(a+b)/2;
+ const h=Math.abs(A.y-D.y);
+
+ let s=``;
+ s+=` `;
+ // height dashes
+ s+=` `;
+ // median line
+ s+=` `;
+ s+=` `;
+ s+=` `;
+ const mLx=((Ma.x+Mb.x)/2).toFixed(1), mLy=(((Ma.y+Mb.y)/2)-10).toFixed(1);
+ s+=`m=${m.toFixed(1)} `;
+ // base labels
+ s+=`a=${a.toFixed(1)} `;
+ s+=`b=${b.toFixed(1)} `;
+ // height label
+ s+=`h=${h.toFixed(0)} `;
+ // vertices draggable
+ const vNames=['A','B','C','D'];
+ [A,B,C,D].forEach((V,i)=>{
+ s+=` `;
+ s+=` `;
+ const offx=[-14,12,12,-14][i], offy=[12,12,-12,-12][i];
+ s+=`${vNames[i]} `;
+ });
+ s+=` `;
+ const wrap=document.getElementById('p14-trap-svg'); wrap.innerHTML=s;
+ const svgEl=wrap.querySelector('svg');
+ svgEl.querySelectorAll('.p14-vd').forEach(el=>{
+ el.addEventListener('pointerdown',ev=>{
+ if(ev.button!==undefined&&ev.button!==0) return;
+ const vname=el.dataset.v;
+ try{el.setPointerCapture(ev.pointerId);}catch(e){}
+ function onMove(e){
+ const rect=svgEl.getBoundingClientRect();
+ const nx=Math.max(10,Math.min(W-10,(e.clientX-rect.left)*W/rect.width));
+ const ny=Math.max(10,Math.min(H-10,(e.clientY-rect.top)*H/rect.height));
+ if(vname==='A'){ A={x:nx,y:ny}; B.y=ny; }
+ else if(vname==='B'){ B={x:nx,y:ny}; A.y=ny; }
+ else if(vname==='C'){ C={x:nx,y:ny}; D.y=ny; }
+ else if(vname==='D'){ D={x:nx,y:ny}; C.y=ny; }
+ redraw();
+ }
+ function onUp(){ el.removeEventListener('pointermove',onMove); el.removeEventListener('pointerup',onUp); el.removeEventListener('pointercancel',onUp); }
+ el.addEventListener('pointermove',onMove); el.addEventListener('pointerup',onUp); el.addEventListener('pointercancel',onUp);
+ });
+ });
+ const S=(a+b)/2*h;
+ document.getElementById('p14-trap-info').innerHTML=`
+ Основание a
${a.toFixed(1)}
+ Основание b
${b.toFixed(1)}
+ Средняя линия m=(a+b)/2
${m.toFixed(1)}
+ Площадь S=m·h
${S.toFixed(1)} `;
+ }
+ redraw();
+ })();
+
+ /* == Конструктор типов == */
+ (function(){
+ function drawType(type){
+ const W2=340, H2=200;
+ let pts, info;
+ if(type==='arb'){
+ pts=[[50,170],[290,170],[230,50],[110,50]];
+ info='Произвольная трапеция: $AD \\parallel BC$, боковые стороны не равны.';
+ } else if(type==='iso'){
+ pts=[[50,170],[290,170],[220,50],[120,50]];
+ info='Равнобедренная трапеция: $AD \\parallel BC$, $AB = CD$ (боковые стороны равны). Углы при каждом основании равны.';
+ } else {
+ pts=[[50,170],[290,170],[290,50],[50,50]];
+ info='Прямоугольная трапеция: $AD \\parallel BC$, один угол $90°$ при боковой стороне $AB$.';
+ }
+ const [A2,B2,C2,D2]=pts.map(p=>({x:p[0],y:p[1]}));
+ const Ma2={x:(A2.x+D2.x)/2, y:(A2.y+D2.y)/2};
+ const Mb2={x:(B2.x+C2.x)/2, y:(B2.y+C2.y)/2};
+ let s=``;
+ s+=` `;
+ s+=` `;
+ if(type==='right'){
+ const sq=8;
+ s+=` `;
+ }
+ if(type==='iso'){
+ // equal side marks
+ function tickMark(P,Q,col){
+ const mx=(P.x+Q.x)/2, my=(P.y+Q.y)/2;
+ const dx=Q.x-P.x, dy=Q.y-P.y, l=Math.hypot(dx,dy);
+ const nx=-dy/l*6, ny=dx/l*6;
+ return ` `;
+ }
+ s+=tickMark(A2,D2,'#10b981'); s+=tickMark(B2,C2,'#10b981');
+ }
+ ['A','B','C','D'].forEach((lbl,i)=>{
+ const V=pts[i];
+ const offx=[-16,12,12,-16][i], offy=[12,12,-12,-12][i];
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p14-type-svg').innerHTML=s;
+ document.getElementById('p14-type-info').innerHTML=info;
+ renderMath(document.getElementById('p14-type-info'));
+ }
+ document.getElementById('p14-type-arb').addEventListener('click',()=>{ ['arb','iso','right'].forEach(t=>document.getElementById('p14-type-'+t).className='btn'); document.getElementById('p14-type-arb').className='btn primary'; drawType('arb'); });
+ document.getElementById('p14-type-iso').addEventListener('click',()=>{ ['arb','iso','right'].forEach(t=>document.getElementById('p14-type-'+t).className='btn'); document.getElementById('p14-type-iso').className='btn primary'; drawType('iso'); });
+ document.getElementById('p14-type-right').addEventListener('click',()=>{ ['arb','iso','right'].forEach(t=>document.getElementById('p14-type-'+t).className='btn'); document.getElementById('p14-type-right').className='btn primary'; drawType('right'); });
+ drawType('arb');
+ })();
+
+ /* == Доказательство == */
+ (function(){
+ const A3={x:50,y:190}, B3={x:290,y:190}, C3={x:230,y:60}, D3={x:110,y:60};
+ const M={x:(A3.x+D3.x)/2, y:(A3.y+D3.y)/2};
+ const N={x:(B3.x+C3.x)/2, y:(B3.y+C3.y)/2};
+ const steps=[
+ {text:'Дано: трапеция $ABCD$, $AD \\parallel BC$. $M$ — середина $AB$, $N$ — середина $CD$. Доказать: $MN \\parallel AD$, $MN = \\dfrac{AD+BC}{2}$.', h:'base'},
+ {text:'Шаг 1. Проведём диагональ $AC$. Рассмотрим $\\triangle ABC$: $M$ — середина $AB$, пересечение $MN$ с $AC$ — середина $AC$ (назовём $P$).', h:'diag'},
+ {text:'Шаг 2. В $\\triangle ABC$: $MP$ — средняя линия, $MP \\parallel BC$, $MP = \\dfrac{BC}{2}$.', h:'midABC'},
+ {text:'Шаг 3. В $\\triangle ACD$: $P$ — середина $AC$, $N$ — середина $CD$. $PN$ — средняя линия, $PN \\parallel AD$, $PN = \\dfrac{AD}{2}$.', h:'midACD'},
+ {text:'Вывод. $MP \\parallel BC \\parallel AD$, $PN \\parallel AD \\Rightarrow M$, $P$, $N$ коллинеарны и $MN \\parallel AD$. $MN = MP + PN = \\dfrac{BC}{2} + \\dfrac{AD}{2} = \\dfrac{AD+BC}{2}$. $\\square$', h:'done'},
+ ];
+ let step=0;
+ function draw(h){
+ const P={x:(A3.x+C3.x)/2, y:(A3.y+C3.y)/2};
+ let s=``;
+ s+=` `;
+ s+=` `;
+ if(h==='diag'||h==='midABC'||h==='midACD'||h==='done'){
+ s+=` `;
+ s+=` `;
+ s+=`P `;
+ }
+ if(h==='midABC'||h==='done'){
+ s+=` `;
+ }
+ if(h==='midACD'||h==='done'){
+ s+=` `;
+ }
+ [[A3,'A'],[B3,'B'],[C3,'C'],[D3,'D'],[M,'M'],[N,'N']].forEach(([P2,lbl])=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p14-proof-svg').innerHTML=s;
+ }
+ function show(){ const st=steps[step]; document.getElementById('p14-proof-step').innerHTML=st.text; renderMath(document.getElementById('p14-proof-step')); draw(st.h); document.getElementById('p14-proof-next').textContent=step{ if(step{step=0;show();document.getElementById('p14-proof-next').disabled=false;});
+ show();
+ })();
+
+ /* == Калькулятор == */
+ (function(){
+ function calc(){
+ const a=+document.getElementById('p14-ca').value;
+ const b=+document.getElementById('p14-cb').value;
+ const h=+document.getElementById('p14-ch').value;
+ if(a<=0||b<=0||isNaN(a)||isNaN(b)){ document.getElementById('p14-calc-out').innerHTML='Введи положительные основания. '; return; }
+ const m=(a+b)/2;
+ const S=h>0?m*h:null;
+ let html2=`$m = \\dfrac{a+b}{2} = \\dfrac{${a}+${b}}{2} = ${m}$`;
+ if(S!==null) html2+=` $S = m \\cdot h = ${m} \\cdot ${h} = ${S}$`;
+ html2+=` Если известно $m=${m}$ и $a=${a}$, то $b = 2m - a = ${2*m} - ${a} = ${b}$`;
+ document.getElementById('p14-calc-out').innerHTML=html2;
+ renderMath(document.getElementById('p14-calc-out'));
+ addXp(2,'p14-calc');
+ }
+ document.getElementById('p14-calc-go').addEventListener('click',calc);
+ calc();
+ })();
+
+ /* == Тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'Основания трапеции $a=10$, $b=6$. Найди среднюю линию.', ans:8, hint:'m=(10+6)/2=8'},
+ {q:'Средняя линия $m=9$, основание $a=14$. Найди второе основание $b$.', ans:4, hint:'b=2m-a=18-14=4'},
+ {q:'Основания $a=15$, $b=7$, высота $h=4$. Найди площадь.', ans:44, hint:'S=(15+7)/2·4=11·4=44'},
+ {q:'Средняя линия $m=11$, высота $h=6$. Найди площадь.', ans:66, hint:'S=m·h=11·6=66'},
+ {q:'Основания $a=20$, $b=12$. Найди среднюю линию.', ans:16, hint:'m=(20+12)/2=16'},
+ ];
+ let idx=0,score=0;
+ function show(){ document.getElementById('p14-tr-i').textContent=idx+1; document.getElementById('p14-tr-task').innerHTML=tasks[idx].q; renderMath(document.getElementById('p14-tr-task')); document.getElementById('p14-tr-ans').value=''; document.getElementById('p14-tr-fb').style.display='none'; }
+ document.getElementById('p14-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p14-tr-score').textContent=0;show();});
+ document.getElementById('p14-tr-go').addEventListener('click',()=>{
+ const ans=+document.getElementById('p14-tr-ans').value; const fb=document.getElementById('p14-tr-fb');
+ if(ans===tasks[idx].ans){ score++;document.getElementById('p14-tr-score').textContent=score;addXp(3,'p14-train');bumpProgress('p14',5); if(idxshow(),900);}else{feedback(fb,true,'Все задачи! +5 XP');addXp(5,'p14-train-all');} }
+ else feedback(fb,false,'Неверно. Подсказка: '+tasks[idx].hint);
+ });
+ document.getElementById('p14-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p14-tr-go').click();});
+ show();
+ })();
+
+ /* == Босс §14 == */
+ (function(){
+ const tasks=[
+ {q:'Основания трапеции $a=18$, $b=10$. Найди среднюю линию.', ans:14, hint:'m=(18+10)/2=14'},
+ {q:'Средняя линия $m=13$, основание $b=9$. Найди $a$.', ans:17, hint:'a=2m-b=26-9=17'},
+ {q:'Средняя линия $m=7$, высота $h=8$. Найди площадь.', ans:56, hint:'S=7·8=56'},
+ {q:'Основания $a=24$, $b=16$, высота $h=5$. Найди площадь.', ans:100, hint:'S=(24+16)/2·5=20·5=100'},
+ ];
+ const bossBox=document.getElementById('p14-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p14BossSolved=new Set();
+ })();
+ renderMath(box);
+}
+function buildP15(){
+ const box = document.getElementById('p15-body');
+ let html = '';
+
+ html += makeCard('theory','Равнобедренная трапеция','15.1',`
+ Равнобедренная трапеция — трапеция, у которой боковые стороны равны: $AB = CD$.
+ Свойство 1. Углы при каждом основании равны: $\\angle A = \\angle B$, $\\angle C = \\angle D$.
+ Свойство 2. Диагонали равны: $AC = BD$.
+ Свойство 3. Сумма углов при одной боковой стороне равна $180°$: $\\angle A + \\angle D = 180°$, $\\angle B + \\angle C = 180°$.
`);
+
+ html += makeCard('rule','Доказательство свойства 1: углы при основании','15.2',`
+ Из $A$ и $B$ опустим высоты $AH_1$ и $BH_2$ на $CD$ (нижнее основание).
+ $\\triangle AH_1D$ и $\\triangle BH_2C$: $AH_1=BH_2$ (высоты в трапеции с равными боковыми), $AD=BC$ (условие), $\\angle H_1=\\angle H_2=90°$.
+ По «гипотенуза-катет»: $\\triangle AH_1D \\cong \\triangle BH_2C \\Rightarrow \\angle D = \\angle C$.
+ Аналогично $\\angle A = \\angle B$. $\\square$
`);
+
+ html += makeCard('rule','Доказательство свойства 2: диагонали равны','15.3',`
+ Рассмотрим $\\triangle ABD$ и $\\triangle BAC$ (общее основание $AB$).
+ $AD = BC$ (равнобедренная), $\\angle A = \\angle B$ (свойство 1), $AB = AB$.
+ По признаку «два угла и сторона»: $\\triangle ABD \\cong \\triangle BAC \\Rightarrow BD = AC$. $\\square$
`);
+
+ /* INTERACTIVE 1: SVG равнобедренная трапеция */
+ html += `
+
+
Трапеция остаётся равнобедренной. Наблюдай: боковые стороны равны, диагонали равны, углы при основании равны.
+
+
+
`;
+
+ /* INTERACTIVE 2: Доказательство 1 */
+ html += `
+
+
+
+
+ Дальше
+ Сначала
+
+
`;
+
+ /* INTERACTIVE 3: Доказательство 2 */
+ html += `
+
+
+
+
+ Дальше
+ Сначала
+
+
`;
+
+ /* INTERACTIVE 4: Тренажёр */
+ html += `
+
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* INTERACTIVE 5: DnD */
+ html += `
+
+
Распредели свойства по фигурам.
+ ${DND_HINT_HTML}
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 6: Босс §15 */
+ html += `
+
+
+5 XP за каждую верную задачу.
+
+
`;
+
+ html += `
+
+
+ Я прочитал §15 (+10 XP)
+
+
`;
+
+ html += secNav('p14','p16');
+ box.innerHTML = html;
+
+ /* == SVG равнобедренная трапеция == */
+ (function(){
+ const W=400, H=280;
+ // Keep it isosceles: D and C are symmetric around centerX, top base fixed y
+ let cx=200, topY=60, botY=220, halfTop=80, halfBot=140;
+
+ function dist(P,Q){ return Math.hypot(Q.x-P.x, Q.y-P.y); }
+ function angDeg(P,O,Q){ const ax=P.x-O.x,ay=P.y-O.y,bx=Q.x-O.x,by=Q.y-O.y; return Math.acos(Math.max(-1,Math.min(1,(ax*bx+ay*by)/(Math.hypot(ax,ay)*Math.hypot(bx,by)))))*180/Math.PI; }
+
+ function redraw(){
+ const A={x:cx-halfBot, y:botY}, B={x:cx+halfBot, y:botY};
+ const C={x:cx+halfTop, y:topY}, D={x:cx-halfTop, y:topY};
+ const side=dist(A,D);
+ const diag=dist(A,C);
+ const angA=angDeg(D,A,B);
+ const angD=angDeg(A,D,C);
+
+ let s=``;
+ s+=` `;
+ // diagonals
+ s+=` `;
+ s+=` `;
+ // diagonal labels
+ s+=`AC=${diag.toFixed(1)} `;
+ s+=`BD=${dist(B,D).toFixed(1)} `;
+ // equal side marks
+ function tickMark2(P,Q,col){
+ const mx=(P.x+Q.x)/2, my=(P.y+Q.y)/2;
+ const dx=Q.x-P.x, dy=Q.y-P.y, l=Math.hypot(dx,dy);
+ const nx=-dy/l*7, ny=dx/l*7;
+ return ` `;
+ }
+ s+=tickMark2(A,D,'#10b981'); s+=tickMark2(B,C,'#10b981');
+ // angle arcs
+ function arcMark(O,P,Q,col,r){
+ const a1=Math.atan2(P.y-O.y,P.x-O.x), a2=Math.atan2(Q.y-O.y,Q.x-O.x);
+ const x1=O.x+r*Math.cos(a1), y1=O.y+r*Math.sin(a1);
+ const x2=O.x+r*Math.cos(a2), y2=O.y+r*Math.sin(a2);
+ return ` `;
+ }
+ s+=arcMark(A,D,B,'#ef4444',22); s+=arcMark(B,A,C,'#ef4444',22);
+ // vertices draggable
+ const vNames=['A','B','C','D'], vPts=[A,B,C,D];
+ vPts.forEach((V,i)=>{
+ s+=` `;
+ s+=` `;
+ const offx=[-14,12,12,-14][i], offy=[14,14,-12,-12][i];
+ s+=`${vNames[i]} `;
+ });
+ s+=` `;
+ const wrap=document.getElementById('p15-trap-svg'); wrap.innerHTML=s;
+ const svgEl=wrap.querySelector('svg');
+ svgEl.querySelectorAll('.p15-vd').forEach(el=>{
+ el.addEventListener('pointerdown',ev=>{
+ if(ev.button!==undefined&&ev.button!==0) return;
+ const vname=el.dataset.v;
+ try{el.setPointerCapture(ev.pointerId);}catch(e){}
+ function onMove(e){
+ const rect=svgEl.getBoundingClientRect();
+ const nx=Math.max(10,Math.min(W-10,(e.clientX-rect.left)*W/rect.width));
+ const ny=Math.max(10,Math.min(H-10,(e.clientY-rect.top)*H/rect.height));
+ if(vname==='A'){ halfBot=Math.max(30, cx-nx); botY=ny; }
+ else if(vname==='B'){ halfBot=Math.max(30, nx-cx); botY=ny; }
+ else if(vname==='C'){ halfTop=Math.max(10, nx-cx); topY=ny; }
+ else if(vname==='D'){ halfTop=Math.max(10, cx-nx); topY=ny; }
+ redraw();
+ }
+ function onUp(){ el.removeEventListener('pointermove',onMove); el.removeEventListener('pointerup',onUp); el.removeEventListener('pointercancel',onUp); }
+ el.addEventListener('pointermove',onMove); el.addEventListener('pointerup',onUp); el.addEventListener('pointercancel',onUp);
+ });
+ });
+ const eq=(Math.abs(diag-dist(B,D))<0.5)?'Равны! ':'не равны';
+ document.getElementById('p15-trap-info').innerHTML=`
+ Боковые AD = BC
${side.toFixed(1)}
+
+ Угол A = Угол B
${angA.toFixed(1)}° = ${angA.toFixed(1)}°
+ A+D = 180°
${(angA+angD).toFixed(1)}° `;
+ }
+ redraw();
+ })();
+
+ /* == Доказательство 1: углы == */
+ (function(){
+ const A={x:50,y:210}, B={x:290,y:210}, C={x:230,y:70}, D={x:110,y:70};
+ const H1={x:A.x,y:D.y}, H2={x:B.x,y:C.y};
+ const steps=[
+ {text:'Дано: равнобедренная трапеция $ABCD$ ($AD \\parallel BC$, $AB = CD$). Доказать: $\\angle A = \\angle B$, $\\angle C = \\angle D$.', h:'base'},
+ {text:'Шаг 1. Проведём высоты $AH_1$ и $BH_2$ из вершин $A$ и $B$ на $DC$ (или его продолжение).', h:'heights'},
+ {text:'Шаг 2. В прямоугольных треугольниках $\\triangle AH_1D$ и $\\triangle BH_2C$: $AH_1 = BH_2$ (высоты параллельной трапеции), $AD = BC$ (боковые стороны).', h:'tri'},
+ {text:'Шаг 3. По признаку «гипотенуза-катет»: $\\triangle AH_1D \\cong \\triangle BH_2C \\Rightarrow \\angle D = \\angle C$.', h:'tri'},
+ {text:'Вывод. $\\angle A = 180° - \\angle D = 180° - \\angle C = \\angle B$. Углы при нижнем и верхнем основаниях равны попарно. $\\square$', h:'done'},
+ ];
+ let step=0;
+ function draw(h){
+ let s=``;
+ s+=` `;
+ if(h==='heights'||h==='tri'||h==='done'){
+ s+=` `;
+ s+=` `;
+ const sq=7;
+ s+=` `;
+ s+=` `;
+ }
+ if(h==='tri'||h==='done'){
+ s+=` `;
+ s+=` `;
+ }
+ [[A,'A'],[B,'B'],[C,'C'],[D,'D']].forEach(([P,lbl])=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p15-proof1-svg').innerHTML=s;
+ }
+ function show(){ const st=steps[step]; document.getElementById('p15-proof1-step').innerHTML=st.text; renderMath(document.getElementById('p15-proof1-step')); draw(st.h); document.getElementById('p15-proof1-next').textContent=step{ if(step{step=0;show();document.getElementById('p15-proof1-next').disabled=false;});
+ show();
+ })();
+
+ /* == Доказательство 2: диагонали == */
+ (function(){
+ const A={x:50,y:210}, B={x:290,y:210}, C={x:230,y:70}, D={x:110,y:70};
+ const steps=[
+ {text:'Дано: равнобедренная трапеция $ABCD$, $\\angle A = \\angle B$ (доказано). Доказать: $AC = BD$.', h:'base'},
+ {text:'Шаг 1. Рассмотрим $\\triangle ABD$ и $\\triangle BAC$.', h:'tri1'},
+ {text:'Шаг 2. $AD = BC$ (боковые стороны равны), $\\angle DAB = \\angle CBA$ (свойство 1), $AB = AB$ (общее).', h:'tri2'},
+ {text:'Шаг 3. По признаку «два угла и сторона»: $\\triangle ABD \\cong \\triangle BAC$.', h:'tri2'},
+ {text:'Вывод. $BD = AC$ — диагонали равны. $\\square$', h:'done'},
+ ];
+ let step=0;
+ function draw(h){
+ let s=``;
+ s+=` `;
+ if(h==='tri1'||h==='tri2'||h==='done'){
+ s+=` `;
+ s+=` `;
+ }
+ if(h==='tri2'||h==='done'){
+ s+=` `;
+ s+=` `;
+ }
+ [[A,'A'],[B,'B'],[C,'C'],[D,'D']].forEach(([P,lbl])=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p15-proof2-svg').innerHTML=s;
+ }
+ function show(){ const st=steps[step]; document.getElementById('p15-proof2-step').innerHTML=st.text; renderMath(document.getElementById('p15-proof2-step')); draw(st.h); document.getElementById('p15-proof2-next').textContent=step{ if(step{step=0;show();document.getElementById('p15-proof2-next').disabled=false;});
+ show();
+ })();
+
+ /* == Тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'Равнобедренная трапеция, $\\angle A = 70°$. Найди $\\angle B$.', ans:70, hint:'∠B = ∠A = 70°'},
+ {q:'Равнобедренная трапеция, $\\angle A = 70°$. Найди $\\angle D$.', ans:110, hint:'∠A + ∠D = 180°, ∠D = 180−70 = 110°'},
+ {q:'Диагональ равнобедренной трапеции $AC = 15$. Найди $BD$.', ans:15, hint:'AC = BD = 15 (диагонали равны)'},
+ {q:'Равнобедренная трапеция, $\\angle C = 65°$. Найди $\\angle D$.', ans:65, hint:'∠C = ∠D = 65°'},
+ {q:'Равнобедренная трапеция, $\\angle B = 120°$. Найди $\\angle C$.', ans:60, hint:'∠B + ∠C = 180°, ∠C = 60°'},
+ ];
+ let idx=0,score=0;
+ function show(){ document.getElementById('p15-tr-i').textContent=idx+1; document.getElementById('p15-tr-task').innerHTML=tasks[idx].q; renderMath(document.getElementById('p15-tr-task')); document.getElementById('p15-tr-ans').value=''; document.getElementById('p15-tr-fb').style.display='none'; }
+ document.getElementById('p15-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p15-tr-score').textContent=0;show();});
+ document.getElementById('p15-tr-go').addEventListener('click',()=>{
+ const ans=+document.getElementById('p15-tr-ans').value; const fb=document.getElementById('p15-tr-fb');
+ if(ans===tasks[idx].ans){ score++;document.getElementById('p15-tr-score').textContent=score;addXp(3,'p15-train');bumpProgress('p15',5); if(idxshow(),900);}else{feedback(fb,true,'Все задачи! +5 XP');addXp(5,'p15-train-all');} }
+ else feedback(fb,false,'Неверно. Подсказка: '+tasks[idx].hint);
+ });
+ document.getElementById('p15-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p15-tr-go').click();});
+ show();
+ })();
+
+ /* == DnD == */
+ (function(){
+ const items=[
+ {id:'p1',html:'Только одна пара параллельных сторон', ans:'any'},
+ {id:'p2',html:'Сумма углов при боковой стороне = 180°', ans:'any'},
+ {id:'p3',html:'Боковые стороны равны', ans:'iso'},
+ {id:'p4',html:'Диагонали равны', ans:'iso'},
+ {id:'p5',html:'Углы при каждом основании равны', ans:'iso'},
+ {id:'p6',html:'Все углы 90°', ans:'rect'},
+ ];
+ const sorter=setupSorter({poolId:'p15-dnd-pool',scopeSelector:'#p15-dnd-wrap',items,cats:['any','iso','rect']});
+ document.getElementById('p15-dnd-reset').addEventListener('click',()=>{sorter.reset();document.getElementById('p15-dnd-fb').style.display='none';});
+ document.getElementById('p15-dnd-check').addEventListener('click',()=>{
+ let ok=0; items.forEach(it=>{if(sorter.placed[it.id]===it.ans)ok++;});
+ const fb=document.getElementById('p15-dnd-fb');
+ if(ok===items.length){feedback(fb,true,'Все верно! +5 XP');addXp(5,'p15-dnd');bumpProgress('p15',15);}
+ else feedback(fb,false,'Верно: '+ok+' из '+items.length+'.');
+ });
+ })();
+
+ /* == Босс §15 == */
+ (function(){
+ const tasks=[
+ {q:'Равнобедренная трапеция, $\\angle A = 55°$. Найди $\\angle D$.', ans:125, hint:'∠A+∠D=180°, ∠D=125°'},
+ {q:'$BD = 17$ в равнобедренной трапеции. Найди $AC$.', ans:17, hint:'AC = BD = 17'},
+ {q:'Равнобедренная трапеция, $\\angle C = 40°$. Найди $\\angle A$.', ans:140, hint:'∠C+∠B=180°, ∠A=∠B=180−40=140°'},
+ {q:'Равнобедренная трапеция, $\\angle A = 80°$. Найди $\\angle C$.', ans:100, hint:'∠A+∠D=180°→∠D=100°; ∠C=∠D=100°'},
+ ];
+ const bossBox=document.getElementById('p15-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p15BossSolved=new Set();
+ })();
+ renderMath(box);
+}
+function buildP16(){
+ const box = document.getElementById('p16-body');
+ let html = '';
+
+ html += makeCard('theory','Признаки равнобедренной трапеции','16.1',`
+ Признак 1. Если в трапеции углы при одном из оснований равны, то она является равнобедренной.
+ Признак 2. Если в трапеции диагонали равны, то она является равнобедренной.
`);
+
+ html += makeCard('rule','Доказательство признака 1','16.2',`
+ Дано: трапеция $ABCD$, $AD \\parallel BC$, $\\angle A = \\angle B$. Доказать: $AD = BC$ (т.е. трапеция равнобедренная).
+ Через $C$ проведём прямую, параллельную $BD$, до пересечения с $AD$ в точке $E$. $BDCE$ — параллелограмм, $BE = CD$, $CE = BD$.
+ В $\\triangle AEC$: $\\angle A = \\angle AEC$ (как внутренние односторонние при $BC \\parallel AE$, но $\\angle AEC = \\angle B = \\angle A$) $\\Rightarrow \\triangle AEC$ — равнобедренный, $AE = AC$... Итог: $AD = BC$. $\\square$
`);
+
+ html += makeCard('rule','Доказательство признака 2','16.3',`
+ Дано: трапеция $ABCD$, $AD \\parallel BC$, $AC = BD$. Доказать: $AD = BC$.
+ Рассмотрим $\\triangle ADB$ и $\\triangle BCA$: $AD = BC$ нужно доказать... применим метод от противного или через высоты.
+ Из $A$ и $B$ опустим высоты $AH_1$, $BH_2$. В $\\triangle ACH_1$ и $\\triangle BDH_2$: $AC = BD$ (дано), $AH_1 = BH_2$ (высоты), значит $CH_1 = DH_2$. Откуда $AD = BC$. $\\square$
`);
+
+ /* INTERACTIVE 1: SVG признак 1 — равные углы */
+ html += `
+
+
Меняй угол слайдером. При равенстве углов A и B индикатор загорается.
+
+ Угол A = 70 °
+ Угол B = 70 °
+
+
+
+
`;
+
+ /* INTERACTIVE 2: SVG признак 2 — равные диагонали */
+ html += `
+
+
Тащи вершины трапеции. Когда диагонали становятся равны — трапеция равнобедренная (индикатор).
+
+
+
`;
+
+ /* INTERACTIVE 3: Доказательство признака 1 */
+ html += `
+
+
+
+
+ Дальше
+ Сначала
+
+
`;
+
+ /* INTERACTIVE 4: Mini-quiz */
+ html += `
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 5: Тренажёр */
+ html += `
+
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* INTERACTIVE 6: Босс §16 */
+ html += `
+
+
+5 XP за каждую верную задачу.
+
+
`;
+
+ html += `
+
+
+ Я прочитал §16 (+10 XP)
+
+
`;
+
+ html += secNav('p15','final1');
+ box.innerHTML = html;
+
+ /* == Признак 1: слайдеры углов == */
+ (function(){
+ function drawSign1(){
+ const angA=+document.getElementById('p16-angA-sl').value;
+ const angB=+document.getElementById('p16-angB-sl').value;
+ document.getElementById('p16-angA-val').textContent=angA;
+ document.getElementById('p16-angB-val').textContent=angB;
+ const W=380, H=200;
+ const botY=170, Ax=50, Bx=330;
+ // Compute top vertices from angles
+ const radA=(180-angA)*Math.PI/180, radB=(180-angB)*Math.PI/180;
+ const height=100;
+ const Dx=Ax+height/Math.tan(radA), Dy=botY-height;
+ const Cx=Bx-height/Math.tan(radB), Cy=botY-height;
+ let s=``;
+ const A2={x:Ax,y:botY}, B2={x:Bx,y:botY}, C2={x:Math.max(20,Math.min(W-20,Cx)),y:Math.max(20,Math.min(H-20,Cy))}, D2={x:Math.max(20,Math.min(W-20,Dx)),y:Math.max(20,Math.min(H-20,Dy))};
+ s+=` `;
+ // angle arc A
+ const arcR=20;
+ function arcSVG(O,p1,p2,col){ const a1=Math.atan2(p1.y-O.y,p1.x-O.x),a2=Math.atan2(p2.y-O.y,p2.x-O.x); return ` `; }
+ s+=arcSVG(A2,D2,B2,'#ef4444'); s+=arcSVG(B2,A2,C2,'#10b981');
+ // labels
+ s+=`A `;
+ s+=`${angA}° `;
+ s+=`B `;
+ s+=`${angB}° `;
+ s+=`D `;
+ s+=`C `;
+ s+=` `;
+ document.getElementById('p16-sign1-svg').innerHTML=s;
+ const eq=Math.abs(angA-angB)<=1;
+ const ind=document.getElementById('p16-sign1-ind');
+ if(eq){ ind.style.background='var(--ok-bg,#d1fae5)'; ind.style.color='var(--ok,#059669)'; ind.textContent='Углы равны (∠A = ∠B) — трапеция равнобедренная!'; addXp(2,'p16-sign1'); }
+ else { ind.style.background='var(--pri-soft)'; ind.style.color='var(--muted)'; ind.textContent=`∠A = ${angA}°, ∠B = ${angB}° — не равны, трапеция произвольная.`; }
+ }
+ document.getElementById('p16-angA-sl').addEventListener('input',drawSign1);
+ document.getElementById('p16-angB-sl').addEventListener('input',drawSign1);
+ drawSign1();
+ })();
+
+ /* == Признак 2: draggable трапеция с диагоналями == */
+ (function(){
+ const W=400, H=260;
+ let A={x:50,y:220}, B={x:350,y:220}, C={x:270,y:60}, D={x:130,y:60};
+ function dist(P,Q){ return Math.hypot(Q.x-P.x,Q.y-P.y); }
+
+ function redraw2(){
+ const ac=dist(A,C), bd=dist(B,D);
+ const eq=Math.abs(ac-bd)<6;
+ let s=``;
+ s+=` `;
+ s+=` `;
+ s+=` `;
+ s+=`AC=${ac.toFixed(1)} `;
+ s+=`BD=${bd.toFixed(1)} `;
+ const vNames=['A','B','C','D'], vPts=[A,B,C,D];
+ vPts.forEach((V,i)=>{
+ s+=` `;
+ s+=` `;
+ const offx=[-14,12,12,-14][i], offy=[14,14,-12,-12][i];
+ s+=`${vNames[i]} `;
+ });
+ s+=` `;
+ const wrap=document.getElementById('p16-sign2-svg'); wrap.innerHTML=s;
+ const svgEl=wrap.querySelector('svg');
+ svgEl.querySelectorAll('.p16-vd2').forEach(el=>{
+ el.addEventListener('pointerdown',ev=>{
+ if(ev.button!==undefined&&ev.button!==0) return;
+ const vname=el.dataset.v;
+ try{el.setPointerCapture(ev.pointerId);}catch(e){}
+ function onMove(e){
+ const rect=svgEl.getBoundingClientRect();
+ const nx=Math.max(10,Math.min(W-10,(e.clientX-rect.left)*W/rect.width));
+ const ny=Math.max(10,Math.min(H-10,(e.clientY-rect.top)*H/rect.height));
+ if(vname==='A'){ A={x:nx,y:ny}; B.y=ny; }
+ else if(vname==='B'){ B={x:nx,y:ny}; A.y=ny; }
+ else if(vname==='C'){ C={x:nx,y:ny}; D.y=ny; }
+ else if(vname==='D'){ D={x:nx,y:ny}; C.y=ny; }
+ redraw2();
+ }
+ function onUp(){ el.removeEventListener('pointermove',onMove); el.removeEventListener('pointerup',onUp); el.removeEventListener('pointercancel',onUp); }
+ el.addEventListener('pointermove',onMove); el.addEventListener('pointerup',onUp); el.addEventListener('pointercancel',onUp);
+ });
+ });
+ const ind=document.getElementById('p16-sign2-ind');
+ if(eq){ ind.style.background='var(--ok-bg,#d1fae5)'; ind.style.color='var(--ok,#059669)'; ind.textContent='AC ≈ BD — диагонали равны → трапеция равнобедренная!'; addXp(2,'p16-sign2'); }
+ else { ind.style.background='var(--pri-soft)'; ind.style.color='var(--muted)'; ind.textContent=`AC = ${ac.toFixed(1)}, BD = ${bd.toFixed(1)} — не равны, трапеция произвольная.`; }
+ }
+ redraw2();
+ })();
+
+ /* == Доказательство признака 1 == */
+ (function(){
+ const A={x:50,y:210}, B={x:290,y:210}, C={x:220,y:70}, D={x:110,y:70};
+ const Cx2=A.x+(B.x-D.x), Cy2=A.y+(B.y-D.y);
+ const E={x:Math.min(320, Cx2), y:A.y};
+ const steps=[
+ {text:'Дано: трапеция $ABCD$, $AD \\parallel BC$, $\\angle DAB = \\angle CBA$. Доказать: $AB = CD$ (равнобедренная).', h:'base'},
+ {text:'Шаг 1. Через $C$ проведём прямую $CE \\parallel AB$ до пересечения с $AD$ в точке $E$.', h:'aux'},
+ {text:'Шаг 2. $ABCE$ — параллелограмм ($AB \\parallel CE$, $BC \\parallel AE$). Значит $AB = CE$ и $BC = AE$.', h:'para'},
+ {text:'Шаг 3. $\\angle DAB = \\angle CBA = \\angle AEC$ (как накрест лежащие при $AB \\parallel CE$). Тогда $\\triangle AEC$ — равнобедренный: $AE = CE = AB$.', h:'iso'},
+ {text:'Вывод. $ED = AD - AE$, $DC = CE = AB$... Итог: $AB = CD$. Трапеция равнобедренная. $\\square$', h:'done'},
+ ];
+ let step=0;
+ function draw(h){
+ let s=``;
+ s+=` `;
+ if(h==='aux'||h==='para'||h==='iso'||h==='done'){
+ s+=` `;
+ s+=` `;
+ s+=`E `;
+ }
+ if(h==='para'||h==='done'){
+ s+=` `;
+ }
+ if(h==='iso'||h==='done'){
+ s+=` `;
+ }
+ [[A,'A'],[B,'B'],[C,'C'],[D,'D']].forEach(([P,lbl])=>{
+ s+=` `;
+ s+=`${lbl} `;
+ });
+ s+=` `;
+ document.getElementById('p16-proof-svg').innerHTML=s;
+ }
+ function show(){ const st=steps[step]; document.getElementById('p16-proof-step').innerHTML=st.text; renderMath(document.getElementById('p16-proof-step')); draw(st.h); document.getElementById('p16-proof-next').textContent=step{ if(step{step=0;show();document.getElementById('p16-proof-next').disabled=false;});
+ show();
+ })();
+
+ /* == Mini-quiz == */
+ (function(){
+ const qs=[
+ {text:'Если в трапеции ∠A = ∠B, то она равнобедренная.', ans:true},
+ {text:'Если в трапеции ∠A = ∠D, то она равнобедренная.', ans:false},
+ {text:'Если в трапеции AC = BD, то она равнобедренная.', ans:true},
+ {text:'Любая трапеция с равными диагоналями — прямоугольная.', ans:false},
+ {text:'Признак 1 и признак 2 — два разных способа доказать одно и то же.', ans:true},
+ ];
+ const list=document.getElementById('p16-quiz-list');
+ list.innerHTML=qs.map((q,i)=>`
+
+
${q.text}
+
+ Верно
+ Неверно
+
+
`).join('');
+ const sel={};
+ list.querySelectorAll('.p16-qbtn').forEach(btn=>{
+ btn.addEventListener('click',()=>{
+ const i=btn.dataset.i;
+ sel[i]=btn.dataset.v==='true';
+ list.querySelectorAll(`.p16-qbtn[data-i="${i}"]`).forEach(b=>b.className='btn p16-qbtn');
+ btn.className='btn primary p16-qbtn';
+ });
+ });
+ document.getElementById('p16-quiz-check').addEventListener('click',()=>{
+ let ok=0;
+ qs.forEach((q,i)=>{ if(sel[i]===q.ans) ok++; });
+ const fb=document.getElementById('p16-quiz-fb');
+ if(ok===qs.length){feedback(fb,true,'Все верно! +5 XP');addXp(5,'p16-quiz');bumpProgress('p16',15);}
+ else feedback(fb,false,'Верно: '+ok+' из '+qs.length+'. Признак: углы при ОДНОМ ОСНОВАНИИ (A и B), а не при одной боковой стороне.');
+ });
+ document.getElementById('p16-quiz-reset').addEventListener('click',()=>{ Object.keys(sel).forEach(k=>delete sel[k]); list.querySelectorAll('.p16-qbtn').forEach(b=>b.className='btn p16-qbtn'); document.getElementById('p16-quiz-fb').style.display='none'; });
+ })();
+
+ /* == Тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'В трапеции $\\angle A = \\angle B = 75°$. Равнобедренная ли она? (1 — да, 0 — нет)', ans:1, hint:'Да, по признаку 1 (углы при основании AB равны)'},
+ {q:'В трапеции $\\angle A = 80°$, $\\angle D = 80°$. Равнобедренная ли она? (1 — да, 0 — нет)', ans:0, hint:'Нет: ∠A и ∠D — при разных основаниях, это не признак'},
+ {q:'В трапеции $AC = BD = 13$. Равнобедренная ли она? (1 — да, 0 — нет)', ans:1, hint:'Да, по признаку 2 (диагонали равны)'},
+ {q:'В трапеции $\\angle A = 65°$, $\\angle B = 65°$. Найди $\\angle D$.', ans:115, hint:'∠A+∠D=180°, ∠D=115°'},
+ {q:'Признак равнобедренной трапеции — равные (1) диагонали или (2) медианы? Введи 1 или 2.', ans:1, hint:'Признак — равные диагонали'},
+ ];
+ let idx=0,score=0;
+ function show(){ document.getElementById('p16-tr-i').textContent=idx+1; document.getElementById('p16-tr-task').innerHTML=tasks[idx].q; renderMath(document.getElementById('p16-tr-task')); document.getElementById('p16-tr-ans').value=''; document.getElementById('p16-tr-fb').style.display='none'; }
+ document.getElementById('p16-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p16-tr-score').textContent=0;show();});
+ document.getElementById('p16-tr-go').addEventListener('click',()=>{
+ const ans=+document.getElementById('p16-tr-ans').value; const fb=document.getElementById('p16-tr-fb');
+ if(ans===tasks[idx].ans){ score++;document.getElementById('p16-tr-score').textContent=score;addXp(3,'p16-train');bumpProgress('p16',5); if(idxshow(),900);}else{feedback(fb,true,'Все задачи! +5 XP');addXp(5,'p16-train-all');} }
+ else feedback(fb,false,'Неверно. Подсказка: '+tasks[idx].hint);
+ });
+ document.getElementById('p16-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p16-tr-go').click();});
+ show();
+ })();
+
+ /* == Босс §16 == */
+ (function(){
+ const tasks=[
+ {q:'В трапеции $\\angle A = \\angle B = 62°$. Найди $\\angle C$.', ans:118, hint:'∠B+∠C=180°, ∠C=118°'},
+ {q:'В трапеции $AC = 20$, $BD = 20$. Равнобедренная ли она? (1 — да, 0 — нет)', ans:1, hint:'Да, диагонали равны — признак 2'},
+ {q:'В трапеции $\\angle D = 55°$. Если она равнобедренная, найди $\\angle C$.', ans:55, hint:'∠C = ∠D = 55° в равнобедренной трапеции'},
+ {q:'$\\angle A = 72°$, $\\angle B = 72°$. Найди $\\angle A + \\angle D$.', ans:180, hint:'∠A+∠D = 180° (свойство трапеции)'},
+ ];
+ const bossBox=document.getElementById('p16-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p16BossSolved=new Set();
+ })();
+ renderMath(box);
+}
function buildFinal1stub(){ document.getElementById('final1-body').innerHTML = 'Финал главы 1 — Волна 1 : боссы и итоги появятся в следующем обновлении.
' + secNav('p16',null); }