diff --git a/frontend/textbooks/geometry_8_ch3.html b/frontend/textbooks/geometry_8_ch3.html
index f8594e5..5c8297f 100644
--- a/frontend/textbooks/geometry_8_ch3.html
+++ b/frontend/textbooks/geometry_8_ch3.html
@@ -339,7 +339,7 @@ const PARAS=[
function buildParaSelector(){const g=document.getElementById('psel-grid');g.innerHTML='';PARAS.forEach(p=>{const card=document.createElement('div');card.className='psel-card'+(p.final?' final':'');card.dataset.id=p.id;card.dataset.progCard=p.id;card.innerHTML=`
${p.num}
${p.name}
`;card.addEventListener('click',()=>goTo(p.id));g.appendChild(card);});}
const BUILT=new Set();
-const BUILDERS={p1:()=>buildP1(),p2:()=>buildP2(),p3:()=>buildP3(),p4:()=>buildP4stub(),p5:()=>buildP5stub(),p6:()=>buildP6stub(),p7:()=>buildP7stub(),p8:()=>buildP8stub(),p9:()=>buildP9stub(),final3:()=>buildFinal3stub()};
+const BUILDERS={p1:()=>buildP1(),p2:()=>buildP2(),p3:()=>buildP3(),p4:()=>buildP4(),p5:()=>buildP5(),p6:()=>buildP6stub(),p7:()=>buildP7stub(),p8:()=>buildP8stub(),p9:()=>buildP9stub(),final3:()=>buildFinal3stub()};
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);document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));const el=document.getElementById('sec-'+id);if(el)el.classList.add('active');document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active',c.dataset.id===id));buildSidebar(id);window.scrollTo({top:0,behavior:'smooth'});if((STATE.progress[id]||0)<10)bumpProgress(id,10);if(window.renderMathInElement)setTimeout(()=>renderMath(el),0);setTimeout(()=>{try{wrapGlossary(el);}catch(e){}},60);markLastPara(id);}
@@ -1545,8 +1545,788 @@ function buildP3(){
renderMath(bossBox);
})();
}
-function buildP4stub(){ document.getElementById('p4-body').innerHTML='§4 — Волна 1 : содержимое появится в следующем обновлении.
'+secNav('p3','p5'); }
-function buildP5stub(){ document.getElementById('p5-body').innerHTML='§5 — Волна 1 : содержимое появится в следующем обновлении.
'+secNav('p4','p6'); }
+function buildP4(){
+ const box=document.getElementById('p4-body');
+ let html='';
+
+ /* ---- Theory cards ---- */
+ html+=makeCard('theory','Теорема о прямой, параллельной стороне треугольника','4.1',`
+ Теорема. Прямая, параллельная одной из сторон треугольника и пересекающая две другие стороны, отсекает треугольник, подобный исходному.
+ Формально: в $\\triangle ABC$ прямая $MN \\parallel BC$ пересекает $AB$ в точке $M$ и $AC$ в точке $N$. Тогда $\\triangle AMN \\sim \\triangle ABC$ с коэффициентом подобия:
+ $$k = \\dfrac{AM}{AB} = \\dfrac{AN}{AC} = \\dfrac{MN}{BC}$$
+
+
+
+
+
+
+
+
+
+
+ A
+ B
+ C
+ M
+ N
+ MN ∥ BC
+ △AMN ∼ △ABC
+
+ k=AM/AB
+
+
`);
+
+ html+=makeCard('rule','Следствие — теорема Фалеса для треугольника','4.2',`
+ Следствие. Если $MN \\parallel BC$ в $\\triangle ABC$, то:
+ $$\\dfrac{AM}{MB} = \\dfrac{AN}{NC}$$
+ То есть прямая, параллельная стороне треугольника, делит две другие стороны пропорционально.
+ Это прямое следствие обобщённой теоремы Фалеса: прямые $BC$, $MN$ и $A$ порождают пропорциональное деление сторон $AB$ и $AC$.
+
+
+
+
+
+
+
+ A
+ B
+ C
+ M
+ N
+
+ AM
+ MB
+
+ AN
+ NC
+ AM/MB = AN/NC
+
+
`);
+
+ html+=makeCard('example','Пример вычисления','4.3',`
+ Пример. В $\\triangle ABC$ прямая $MN \\parallel BC$. $AM = 6$, $AB = 10$, $BC = 15$. Найти $MN$.
+ Решение: коэффициент подобия $k = AM/AB = 6/10 = 0{,}6$.
+ $MN = k \\cdot BC = 0{,}6 \\cdot 15 = 9$.
+ Пример 2. $AM = 4$, $MB = 6$. Найти $AN/NC$.
+ По следствию: $AN/NC = AM/MB = 4/6 = 2/3$.
`);
+
+ /* ---- ИНТЕРАКТИВ 1: SVG треугольник со slider ---- */
+ html+=`
+
+
Перемещай прямую $MN \\parallel BC$ по высоте треугольника. Коэффициент подобия $k$ меняется — $\\triangle AMN \\sim \\triangle ABC$.
+
+ Положение $MN$ (t = AM/AB): 0.50
+
+
+
+
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 2: Пошаговое доказательство ---- */
+ html+=`
+
+
Нажимай «Далее» — каждый шаг раскрывает ключевую идею доказательства.
+
+
+
+ Далее
+ Сначала
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 3: Калькулятор ---- */
+ html+=`
+
+
Введи $AM$, $AB$, $BC$ → найди $MN = BC \\cdot AM/AB$. Или задай $MN$, $BC$ → найди $AM/AB$.
+
+
AM
+
AB
+
BC
+
Вычислить MN
+
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 4: Тренажёр ---- */
+ html+=`
+
+
5 задач на нахождение MN, AN, AM и отношений.
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 5: DnD-сортер ---- */
+ html+=`
+
+
Перетащи каждую карточку: «Пропорция верна» или «Пропорция неверна».
+
+
+
Проверить Сбросить
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 6: Босс §4 ---- */
+ html+=`
+
+
4 задачи — каждая верная даёт +5 XP.
+
+
`;
+
+ html+=`
+
+
+ Я прочитал §4 (+10 XP)
+
+
`;
+ html+=secNav('p3','p5');
+ box.innerHTML=html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
+
+ /* == INIT ИНТЕРАКТИВ 1: slider параллельной прямой == */
+ (function(){
+ const tSl=document.getElementById('p4-t-sl');
+ const tVal=document.getElementById('p4-t-val');
+ const svgWrap=document.getElementById('p4-par-svg');
+ const infoEl=document.getElementById('p4-par-info');
+ // base triangle: A=(140,15), B=(30,155), C=(250,155)
+ const Ax=140,Ay=15,Bx=30,By=155,Cx=250,Cy=155;
+ const BC=Math.hypot(Cx-Bx,Cy-By);
+ const AB=Math.hypot(Bx-Ax,By-Ay);
+ const AC=Math.hypot(Cx-Ax,Cy-Ay);
+ function draw(){
+ const t=+tSl.value/100;
+ tVal.textContent=t.toFixed(2);
+ const Mx=Ax+t*(Bx-Ax), My=Ay+t*(By-Ay);
+ const Nx=Ax+t*(Cx-Ax), Ny=Ay+t*(Cy-Ay);
+ const MN=Math.hypot(Nx-Mx,Ny-My);
+ const AM=Math.hypot(Mx-Ax,My-Ay);
+ const AN=Math.hypot(Nx-Ax,Ny-Ay);
+ const W=300, H=180;
+ let s=``;
+ // full triangle light
+ s+=` `;
+ // inner triangle AMN highlighted
+ s+=` `;
+ // MN line
+ s+=` `;
+ // points
+ s+=` `;
+ s+=` `;
+ // labels
+ s+=`A `;
+ s+=`B `;
+ s+=`C `;
+ s+=`M `;
+ s+=`N `;
+ // k badge
+ s+=`k = ${t.toFixed(2)} `;
+ s+=' ';
+ svgWrap.innerHTML=s;
+ infoEl.innerHTML=`$k = AM/AB = ${t.toFixed(3)}$. $MN = k \\cdot BC = ${t.toFixed(3)} \\cdot ${fmt(BC/10)} = ${fmt(MN/10)}$. $AM/MB = ${fmt(t/(1-t+1e-9))}$. По следствию: $AN/NC = AM/MB = ${fmt(t/(1-t+1e-9))}$.`;
+ renderMath(infoEl);
+ addXp(1,'p4-par');
+ }
+ tSl.addEventListener('input',draw);
+ draw();
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 2: пошаговое доказательство == */
+ (function(){
+ const steps=[
+ {desc:'Шаг 1. Дан $\\triangle ABC$. Прямая $MN \\parallel BC$ пересекает $AB$ в $M$ и $AC$ в $N$. Требуется доказать $\\triangle AMN \\sim \\triangle ABC$.',
+ svg:`A B C M N MN ∥ BC `},
+ {desc:'Шаг 2. $\\angle AMN = \\angle ABC$, так как $MN \\parallel BC$ и $AB$ — секущая: это соответственные углы при параллельных прямых и секущей.',
+ svg:`A B C M N ∠AMN = ∠ABC `},
+ {desc:'Шаг 3. Аналогично $\\angle ANM = \\angle ACB$: $MN \\parallel BC$ и $AC$ — секущая, получаем соответственные углы.',
+ svg:`A B C M N ∠ANM = ∠ACB `},
+ {desc:'Шаг 4. Угол $\\angle A$ общий у $\\triangle AMN$ и $\\triangle ABC$. Два угла $\\triangle AMN$ равны соответствующим двум углам $\\triangle ABC$ → по признаку ДД (два угла) $\\triangle AMN \\sim \\triangle ABC$.',
+ svg:`A B C M N △AMN ∼ △ABC `},
+ {desc:'Шаг 5. Из подобия следует пропорциональность сторон: $AM/AB = AN/AC = MN/BC = k$. Следствие: $AM/MB = AN/NC$ — прямое следствие теоремы Фалеса. Доказано. ',
+ svg:`AM/AB = AN/AC = MN/BC = k AM/MB = AN/NC QED ∎ `},
+ ];
+ let step=0;
+ const svgEl=document.getElementById('p4-proof-svg');
+ const descEl=document.getElementById('p4-proof-desc');
+ function show(){
+ svgEl.innerHTML=steps[step].svg;
+ descEl.innerHTML=steps[step].desc;
+ renderMath(descEl);
+ }
+ document.getElementById('p4-proof-next').addEventListener('click',()=>{
+ if(step{step=0;show();});
+ show();
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 3: калькулятор == */
+ (function(){
+ document.getElementById('p4-ccalc').addEventListener('click',()=>{
+ const AM=parseFloat(document.getElementById('p4-cAM').value);
+ const AB=parseFloat(document.getElementById('p4-cAB').value);
+ const BC=parseFloat(document.getElementById('p4-cBC').value);
+ const out=document.getElementById('p4-ccalc-out');
+ if(!isFinite(AM)||!isFinite(AB)||!isFinite(BC)||AM<=0||AB<=0||BC<=0||AM>AB){
+ out.style.display='block';out.innerHTML='Введи положительные числа; AM должно быть ≤ AB. ';return;
+ }
+ const k=AM/AB;
+ const MN=k*BC;
+ const AN_over_NC=AM/(AB-AM);
+ out.style.display='block';
+ out.innerHTML=`$k = AM/AB = ${fmt(AM)}/${fmt(AB)} = ${fmt(k)}$ $MN = k \\cdot BC = ${fmt(k)} \\cdot ${fmt(BC)} = ${fmt(MN)}$ Следствие: $AM/MB = AN/NC = ${fmt(AM)}/${fmt(AB-AM)} = ${fmt(AN_over_NC)}$`;
+ renderMath(out);
+ addXp(2,'p4-calc');bumpProgress('p4',5);
+ });
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 4: тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'В $\\triangle ABC$ прямая $MN \\parallel BC$, $AM=4$, $AB=10$, $BC=20$. Найди $MN$.',ans:8,hint:'k=4/10=0.4; MN=0.4·20=8.'},
+ {q:'В $\\triangle ABC$ прямая $MN \\parallel BC$, $AM=6$, $AB=9$, $BC=12$. Найди $MN$.',ans:8,hint:'k=6/9=2/3; MN=2/3·12=8.'},
+ {q:'$MN \\parallel BC$, $AM=5$, $MB=10$. Найди $AN/NC$.',ans:0.5,hint:'AN/NC = AM/MB = 5/10 = 0.5.'},
+ {q:'$MN \\parallel BC$, $k=0.4$, $BC=30$. Найди $MN$.',ans:12,hint:'MN=k·BC=0.4·30=12.'},
+ {q:'$MN \\parallel BC$, $AM=3$, $MB=6$. Чему равно $AN/AC$?',ans:0.333,hint:'AN/AC = AM/AB = 3/(3+6) = 1/3 ≈ 0.333.'},
+ ];
+ let idx=0,score=0;
+ function show(){
+ document.getElementById('p4-tr-i').textContent=idx+1;
+ const t=document.getElementById('p4-tr-task');
+ t.innerHTML=tasks[idx].q;
+ renderMath(t);
+ document.getElementById('p4-tr-ans').value='';
+ document.getElementById('p4-tr-fb').style.display='none';
+ }
+ document.getElementById('p4-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p4-tr-score').textContent=0;show();});
+ document.getElementById('p4-tr-go').addEventListener('click',()=>{
+ if(idx>=tasks.length)return;
+ const ans=+document.getElementById('p4-tr-ans').value;
+ const fb=document.getElementById('p4-tr-fb');
+ if(Math.abs(ans-tasks[idx].ans)<0.01){
+ score++;document.getElementById('p4-tr-score').textContent=score;
+ addXp(3,'p4-tr-'+idx);bumpProgress('p4',4);
+ if(idxshow(),900);}
+ else{feedback(fb,true,'Все задачи решены! +5 XP');addXp(5,'p4-tr-all');bumpProgress('p4',10);confetti();}
+ } else {
+ feedback(fb,false,'Неверно. '+tasks[idx].hint);
+ }
+ });
+ document.getElementById('p4-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p4-tr-go').click();});
+ show();
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 5: DnD-сортер == */
+ (function(){
+ const items=[
+ {text:'MN∥BC, AM=3, AB=6, AN=4, AC=8 → AM/MB=AN/NC',yes:true},
+ {text:'MN∥BC, AM=4, MB=6, AN=3, NC=5 → AM/MB≠AN/NC',yes:false},
+ {text:'MN∥BC, AM/AB=0.5, MN/BC=0.5',yes:true},
+ {text:'MN∥BC, AM=2, MB=4, AN=3, NC=6 → AM/MB=AN/NC',yes:true},
+ {text:'MN∥BC, k=1/3, MN=BC/2',yes:false},
+ ];
+ const pool=document.getElementById('p4-dnd-pool');
+ const yesBox=document.getElementById('p4-drop-yes-items');
+ const noBox=document.getElementById('p4-drop-no-items');
+ let dragging=null;
+ function makeChip(it,idx){
+ const chip=document.createElement('div');
+ chip.className='dnd-chip';
+ chip.dataset.idx=idx;
+ chip.textContent=it.text;
+ chip.draggable=true;
+ chip.addEventListener('dragstart',e=>{dragging=chip;chip.classList.add('dragging');e.dataTransfer.effectAllowed='move';});
+ chip.addEventListener('dragend',()=>{chip.classList.remove('dragging');dragging=null;});
+ return chip;
+ }
+ items.forEach((it,i)=>pool.appendChild(makeChip(it,i)));
+ [document.getElementById('p4-drop-yes'),document.getElementById('p4-drop-no'),pool].forEach(box=>{
+ box.addEventListener('dragover',e=>{e.preventDefault();box.classList.add('over');});
+ box.addEventListener('dragleave',()=>box.classList.remove('over'));
+ box.addEventListener('drop',e=>{
+ e.preventDefault();box.classList.remove('over');
+ if(!dragging)return;
+ const target=box===document.getElementById('p4-drop-yes')?yesBox:box===document.getElementById('p4-drop-no')?noBox:pool;
+ target.appendChild(dragging);
+ });
+ });
+ document.getElementById('p4-dnd-check').addEventListener('click',()=>{
+ const fb=document.getElementById('p4-dnd-fb');
+ const yChips=[...yesBox.querySelectorAll('.dnd-chip')];
+ const nChips=[...noBox.querySelectorAll('.dnd-chip')];
+ if(yChips.length+nChips.length{if(!items[+c.dataset.idx].yes)ok=false;});
+ nChips.forEach(c=>{if(items[+c.dataset.idx].yes)ok=false;});
+ if(ok){feedback(fb,true,'Верно! +5 XP');addXp(5,'p4-dnd');bumpProgress('p4',8);}
+ else{feedback(fb,false,'Есть ошибки. Проверь: MN∥BC → AM/MB=AN/NC и MN/BC=AM/AB.');}
+ });
+ document.getElementById('p4-dnd-reset').addEventListener('click',()=>{
+ [...yesBox.children].forEach(c=>pool.appendChild(c));
+ [...noBox.children].forEach(c=>pool.appendChild(c));
+ document.getElementById('p4-dnd-fb').style.display='none';
+ });
+ })();
+
+ /* == INIT: Босс §4 == */
+ (function(){
+ const tasks=[
+ {q:'A B C M N AM=5, AB=8, BC=24 $MN \\parallel BC$. $AM=5$, $AB=8$, $BC=24$. Найди $MN$.',ans:15,hint:'k=5/8; MN=5/8·24=15.'},
+ {q:'$MN \\parallel BC$, $AM=6$, $MB=9$. Найди $AN/NC$.',ans:0.667,hint:'AN/NC = AM/MB = 6/9 = 2/3 ≈ 0.667.'},
+ {q:'$MN \\parallel BC$, $MN=8$, $BC=12$. Найди $AM/AB$.',ans:0.667,hint:'AM/AB = MN/BC = 8/12 = 2/3 ≈ 0.667.'},
+ {q:'В $\\triangle ABC$ прямая $MN \\parallel BC$, $AM=3$, $AB=9$, $BC=18$. Найди $MN$.',ans:6,hint:'k=3/9=1/3; MN=1/3·18=6.'},
+ ];
+ const bossBox=document.getElementById('p4-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p4BossSolved=new Set();
+ renderMath(bossBox);
+ })();
+}
+
+function buildP5(){
+ const box=document.getElementById('p5-body');
+ let html='';
+
+ /* ---- Theory cards ---- */
+ html+=makeCard('theory','Первый признак подобия треугольников (ДД — два угла)','5.1',`
+ Теорема (1-й признак подобия). Если два угла одного треугольника соответственно равны двум углам другого треугольника, то такие треугольники подобны.
+ Если $\\angle A = \\angle A'$ и $\\angle B = \\angle B'$, то $\\triangle ABC \\sim \\triangle A'B'C'$.
+ Почему достаточно двух углов? Сумма углов треугольника равна $180°$. Если два угла равны, третий автоматически тоже равен: $\\angle C = 180° - \\angle A - \\angle B = 180° - \\angle A' - \\angle B' = \\angle C'$.
+
+
+
+
+ A
+ B
+ C
+
+
+
+
+
+ A'
+ B'
+ C'
+
+
+
+ ∠A=∠A', ∠B=∠B' → △ABC∼△A'B'C'
+
+
`);
+
+ html+=makeCard('rule','Следствие: прямоугольные треугольники','5.2',`
+ Следствие. Два прямоугольных треугольника подобны, если у них равны острые углы (достаточно одной пары).
+ Если $\\angle C = \\angle C' = 90°$ и $\\angle A = \\angle A'$, то $\\triangle ABC \\sim \\triangle A'B'C'$.
+ Это потому, что прямой угол — один из двух равных углов, а второй угол задан условием.
+
+
+
+
+
+
+ A
+ B
+ C
+ 90°
+
+
+
+
+ A'
+ B'
+ C'
+ ∠B=∠B'=90°, ∠A=∠A' → подобны
+
+
`);
+
+ html+=makeCard('example','Пример применения признака ДД','5.3',`
+ Пример 1. В $\\triangle ABC$: $\\angle A = 50°$, $\\angle B = 70°$. В $\\triangle A'B'C'$: $\\angle A' = 50°$, $\\angle C' = 60°$. Подобны ли треугольники?
+ $\\angle C = 180° - 50° - 70° = 60°$. $\\angle B' = 180° - 50° - 60° = 70°$. Углы: $50°, 70°, 60°$ в обоих. По признаку ДД: $\\triangle ABC \\sim \\triangle A'C'B'$.
+ Пример 2. Два прямоугольных треугольника, у одного острый угол $35°$, у другого $35°$. Подобны ли?
+ Да. Оба имеют $90°$ и $35°$ — два совпадающих угла.
`);
+
+ /* ---- ИНТЕРАКТИВ 1: SVG два треугольника, слайдер углов ---- */
+ html+=`
+
+
Задай углы $\\alpha$ и $\\beta$. Оба треугольника строятся с этими углами — они подобны. Коэффициент $k$ определяется масштабом.
+
+ Угол $\\alpha$: 50 °
+
+
+ Угол $\\beta$: 60 °
+
+
+ Масштаб $k$: 1.8
+
+
+
+
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 2: Пошаговое доказательство ---- */
+ html+=`
+
+
Нажимай «Далее» — шаг за шагом увидишь логику доказательства.
+
+
+
+ Далее
+ Сначала
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 3: Тренажёр ---- */
+ html+=`
+
+
5 задач на подобие по углам и нахождение сторон.
+
Задача 1 / 5 Очки: 0
+
+
+
+ Проверить
+ Начать
+
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 4: DnD-сортер ---- */
+ html+=`
+
+
Перетащи каждую пару треугольников в нужную колонку.
+
+
+
Проверить Сбросить
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 5: Калькулятор ---- */
+ html+=`
+
+
Введи два угла обоих треугольников и одну сторону первого — получи соответствующую сторону второго.
+
+
+
`;
+
+ /* ---- ИНТЕРАКТИВ 6: Босс §5 ---- */
+ html+=`
+
+
4 задачи — каждая верная даёт +5 XP.
+
+
`;
+
+ html+=`
+
+
+ Я прочитал §5 (+10 XP)
+
+
`;
+ html+=secNav('p4','p6');
+ box.innerHTML=html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
+
+ /* == INIT ИНТЕРАКТИВ 1: слайдер углов == */
+ (function(){
+ const aSl=document.getElementById('p5-a-sl');
+ const bSl=document.getElementById('p5-b-sl');
+ const kSl=document.getElementById('p5-k-sl');
+ const aVal=document.getElementById('p5-a-val');
+ const bVal=document.getElementById('p5-b-val');
+ const kVal=document.getElementById('p5-k-val');
+ const svgWrap=document.getElementById('p5-ang-svg');
+ const infoEl=document.getElementById('p5-ang-info');
+
+ function buildTriFromAngles(alpha,beta,baseLen,ox,oy){
+ // alpha at A (left), beta at B (right)
+ // base AB horizontal, C above
+ const gamma=Math.PI-alpha-beta;
+ if(gamma<=0.05) return null;
+ // by sine rule: a/sin(alpha) = base/sin(gamma)
+ const sinG=Math.sin(gamma);
+ const AB=baseLen;
+ const BC=AB*Math.sin(alpha)/sinG;
+ const AC=AB*Math.sin(beta)/sinG;
+ // place A at (ox,oy), B at (ox+AB,oy)
+ const Ax=ox, Ay=oy;
+ const Bx=ox+AB, By=oy;
+ // C: from A at angle alpha above AB
+ const Cx=Ax+AC*Math.cos(alpha);
+ const Cy=Ay-AC*Math.sin(alpha);
+ return {Ax,Ay,Bx,By,Cx,Cy,AB,BC,AC};
+ }
+
+ function draw(){
+ const alpha=+aSl.value*Math.PI/180;
+ const beta=+bSl.value*Math.PI/180;
+ const k=+kSl.value/10;
+ aVal.textContent=+aSl.value;
+ bVal.textContent=+bSl.value;
+ kVal.textContent=k.toFixed(1);
+
+ const gamma=(Math.PI-alpha-beta)*180/Math.PI;
+ if(gamma<=3){
+ svgWrap.innerHTML='Сумма углов превышает 180°. Уменьши углы.
';
+ infoEl.innerHTML='Сумма углов не должна превышать 180°.';
+ return;
+ }
+
+ const W=360, H=170;
+ const t1=buildTriFromAngles(alpha,beta,90,20,145);
+ if(!t1){svgWrap.innerHTML='';return;}
+ const t2=buildTriFromAngles(alpha,beta,90/k,240,145);
+ if(!t2){svgWrap.innerHTML='';return;}
+
+ let s=``;
+ // triangle 1
+ s+=` `;
+ s+=`A `;
+ s+=`B `;
+ s+=`C `;
+ // angle markers t1
+ s+=` `;
+ s+=` `;
+ // triangle 2 (smaller)
+ s+=` `;
+ s+=`A' `;
+ s+=`B' `;
+ s+=`C' `;
+ // angle markers t2
+ s+=` `;
+ s+=` `;
+ // label
+ s+=`k = ${k.toFixed(1)} `;
+ s+=' ';
+ svgWrap.innerHTML=s;
+ infoEl.innerHTML=`$\\alpha=${+aSl.value}°$, $\\beta=${+bSl.value}°$, $\\gamma=${fmt(gamma)}°$. Оба треугольника имеют одинаковые углы → по признаку ДД они подобны. Коэффициент подобия $k=${k.toFixed(1)}$.`;
+ renderMath(infoEl);
+ addXp(1,'p5-ang');
+ }
+ aSl.addEventListener('input',draw);
+ bSl.addEventListener('input',draw);
+ kSl.addEventListener('input',draw);
+ draw();
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 2: пошаговое доказательство == */
+ (function(){
+ const steps=[
+ {desc:'Шаг 1. Даны $\\triangle ABC$ и $\\triangle A\'B\'C\'$ с $\\angle A = \\angle A\'$ и $\\angle B = \\angle B\'$. Требуется доказать $\\triangle ABC \\sim \\triangle A\'B\'C\'$.',
+ svg:`A B C A' B' C' ∠A=∠A', ∠B=∠B' — условие `},
+ {desc:'Шаг 2. Из суммы углов треугольника: $\\angle C = 180° - \\angle A - \\angle B$ и $\\angle C\' = 180° - \\angle A\' - \\angle B\'$. Так как $\\angle A=\\angle A\'$ и $\\angle B=\\angle B\'$, получаем $\\angle C = \\angle C\'$.',
+ svg:`∠A+∠B+∠C = 180° ∠A'+∠B'+∠C' = 180° ∠C = 180°−∠A−∠B = 180°−∠A'−∠B' = ∠C' `},
+ {desc:'Шаг 3. На луче $A\'B\'$ откладываем отрезок $A\'M = AB$. Через $M$ проводим прямую, параллельную $B\'C\'$ — она встречает $A\'C\'$ в точке $N$. По теореме §4: $\\triangle A\'MN \\sim \\triangle A\'B\'C\'$.',
+ svg:`A' B' C' M N △A'MN∼△A'B'C' `},
+ {desc:'Шаг 4. Поскольку $A\'M = AB$ и углы треугольников $\\triangle A\'MN$ и $\\triangle ABC$ попарно равны (все три угла), а $\\angle A\' = \\angle A$ — вершины совпадают, то $\\triangle A\'MN \\cong \\triangle ABC$ (по условию и построению).',
+ svg:`A'M = AB, ∠A'=∠A, ∠B'=∠B → △A'MN ≅ △ABC (признак у-с-у) Следовательно MN = BC, A'N = AC `},
+ {desc:'Шаг 5. Итог: $\\triangle A\'MN \\cong \\triangle ABC$ и $\\triangle A\'MN \\sim \\triangle A\'B\'C\'$ — отсюда $\\triangle ABC \\sim \\triangle A\'B\'C\'$ (транзитивность подобия). Первый признак подобия доказан. ',
+ svg:`∠A=∠A', ∠B=∠B' → △ABC ∼ △A'B'C' Признак ДД — первый признак подобия QED ∎ `},
+ ];
+ let step=0;
+ const svgEl=document.getElementById('p5-proof-svg');
+ const descEl=document.getElementById('p5-proof-desc');
+ function show(){
+ svgEl.innerHTML=steps[step].svg;
+ descEl.innerHTML=steps[step].desc;
+ renderMath(descEl);
+ }
+ document.getElementById('p5-proof-next').addEventListener('click',()=>{
+ if(step{step=0;show();});
+ show();
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 3: тренажёр == */
+ (function(){
+ const tasks=[
+ {q:'В $\\triangle ABC$: $\\angle A=40°$, $\\angle B=70°$. В $\\triangle A\'B\'C\'$: $\\angle A\'=40°$, $\\angle B\'=70°$. Чему равен $\\angle C\'$?',ans:70,hint:'∠C\'=180−40−70=70°.'},
+ {q:'$\\triangle ABC \\sim \\triangle A\'B\'C\'$ по признаку ДД. $AB=12$, $A\'B\'=4$. Найди коэффициент подобия $k$.',ans:3,hint:'k=AB/A\'B\'=12/4=3.'},
+ {q:'$\\triangle ABC \\sim \\triangle A\'B\'C\'$, $k=2.5$. Сторона $a\'=6$. Найди $a$.',ans:15,hint:'a=k·a\'=2.5·6=15.'},
+ {q:'Два прямоугольных треугольника. В первом острый угол $37°$, во втором $37°$. Сторона при прямом угле в первом — $10$, во втором — $6$. Найди коэффициент подобия.',ans:1.667,hint:'k=10/6≈1.667.'},
+ {q:'$\\angle A=\\angle A\'=55°$, $\\angle B=\\angle B\'=65°$. Сторона $c=18$ в $\\triangle ABC$, $c\'=9$ в $\\triangle A\'B\'C\'$. Найди $k$.',ans:2,hint:'k=c/c\'=18/9=2.'},
+ ];
+ let idx=0,score=0;
+ function show(){
+ document.getElementById('p5-tr-i').textContent=idx+1;
+ const t=document.getElementById('p5-tr-task');
+ t.innerHTML=tasks[idx].q;
+ renderMath(t);
+ document.getElementById('p5-tr-ans').value='';
+ document.getElementById('p5-tr-fb').style.display='none';
+ }
+ document.getElementById('p5-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p5-tr-score').textContent=0;show();});
+ document.getElementById('p5-tr-go').addEventListener('click',()=>{
+ if(idx>=tasks.length)return;
+ const ans=+document.getElementById('p5-tr-ans').value;
+ const fb=document.getElementById('p5-tr-fb');
+ if(Math.abs(ans-tasks[idx].ans)<0.02){
+ score++;document.getElementById('p5-tr-score').textContent=score;
+ addXp(3,'p5-tr-'+idx);bumpProgress('p5',4);
+ if(idxshow(),900);}
+ else{feedback(fb,true,'Все задачи решены! +5 XP');addXp(5,'p5-tr-all');bumpProgress('p5',10);confetti();}
+ } else {
+ feedback(fb,false,'Неверно. '+tasks[idx].hint);
+ }
+ });
+ document.getElementById('p5-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p5-tr-go').click();});
+ show();
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 4: DnD-сортер == */
+ (function(){
+ const items=[
+ {text:'(50°,70°,60°) и (50°,70°,60°)',yes:true},
+ {text:'(40°,60°,80°) и (40°,50°,90°)',yes:false},
+ {text:'(90°,35°,55°) и (90°,35°,55°)',yes:true},
+ {text:'(30°,60°,90°) и (45°,45°,90°)',yes:false},
+ {text:'(80°,60°,40°) и (80°,40°,60°)',yes:true},
+ ];
+ const pool=document.getElementById('p5-dnd-pool');
+ const yesBox=document.getElementById('p5-drop-yes-items');
+ const noBox=document.getElementById('p5-drop-no-items');
+ let dragging=null;
+ function makeChip(it,idx){
+ const chip=document.createElement('div');
+ chip.className='dnd-chip';
+ chip.dataset.idx=idx;
+ chip.textContent=it.text;
+ chip.draggable=true;
+ chip.addEventListener('dragstart',e=>{dragging=chip;chip.classList.add('dragging');e.dataTransfer.effectAllowed='move';});
+ chip.addEventListener('dragend',()=>{chip.classList.remove('dragging');dragging=null;});
+ return chip;
+ }
+ items.forEach((it,i)=>pool.appendChild(makeChip(it,i)));
+ [document.getElementById('p5-drop-yes'),document.getElementById('p5-drop-no'),pool].forEach(box=>{
+ box.addEventListener('dragover',e=>{e.preventDefault();box.classList.add('over');});
+ box.addEventListener('dragleave',()=>box.classList.remove('over'));
+ box.addEventListener('drop',e=>{
+ e.preventDefault();box.classList.remove('over');
+ if(!dragging)return;
+ const target=box===document.getElementById('p5-drop-yes')?yesBox:box===document.getElementById('p5-drop-no')?noBox:pool;
+ target.appendChild(dragging);
+ });
+ });
+ document.getElementById('p5-dnd-check').addEventListener('click',()=>{
+ const fb=document.getElementById('p5-dnd-fb');
+ const yChips=[...yesBox.querySelectorAll('.dnd-chip')];
+ const nChips=[...noBox.querySelectorAll('.dnd-chip')];
+ if(yChips.length+nChips.length{if(!items[+c.dataset.idx].yes)ok=false;});
+ nChips.forEach(c=>{if(items[+c.dataset.idx].yes)ok=false;});
+ if(ok){feedback(fb,true,'Верно! +5 XP');addXp(5,'p5-dnd');bumpProgress('p5',8);}
+ else{feedback(fb,false,'Есть ошибки. Два треугольника подобны, если у них есть хотя бы две пары равных углов.');}
+ });
+ document.getElementById('p5-dnd-reset').addEventListener('click',()=>{
+ [...yesBox.children].forEach(c=>pool.appendChild(c));
+ [...noBox.children].forEach(c=>pool.appendChild(c));
+ document.getElementById('p5-dnd-fb').style.display='none';
+ });
+ })();
+
+ /* == INIT ИНТЕРАКТИВ 5: калькулятор == */
+ (function(){
+ document.getElementById('p5-ccalc').addEventListener('click',()=>{
+ const A1=parseFloat(document.getElementById('p5-cA1').value);
+ const B1=parseFloat(document.getElementById('p5-cB1').value);
+ const A2=parseFloat(document.getElementById('p5-cA2').value);
+ const B2=parseFloat(document.getElementById('p5-cB2').value);
+ const a1=parseFloat(document.getElementById('p5-ca1').value);
+ const out=document.getElementById('p5-ccalc-out');
+ if([A1,B1,A2,B2,a1].some(v=>!isFinite(v)||v<=0)){
+ out.style.display='block';out.innerHTML='Введи все поля. ';return;
+ }
+ const C1=180-A1-B1, C2=180-A2-B2;
+ if(C1<=0||C2<=0){
+ out.style.display='block';out.innerHTML='Сумма углов превышает 180°. ';return;
+ }
+ // check similarity: sort angles and compare
+ const ang1=[A1,B1,C1].sort((a,b)=>a-b);
+ const ang2=[A2,B2,C2].sort((a,b)=>a-b);
+ const similar=ang1.every((v,i)=>Math.abs(v-ang2[i])<0.5);
+ if(!similar){
+ out.style.display='block';out.innerHTML=`$\\angle C_1=${fmt(C1)}°$, $\\angle C_2=${fmt(C2)}°$. Наборы углов различаются — треугольники не подобны . Признак ДД не выполнен.`;
+ renderMath(out);return;
+ }
+ // find matching side using sine rule: a/sin(A)=b/sin(B)
+ // k = a1 / (2R sin(A)) ... use ratio of sides
+ // match angles: find which angle in t2 corresponds to A1
+ // simplification: by ratio of corresponding sides via sine rule
+ // a1/sin(A1) = a2/sin(A2) → a2 = a1*sin(A2*pi/180)/sin(A1*pi/180)
+ const a2=a1*Math.sin(A2*Math.PI/180)/Math.sin(A1*Math.PI/180);
+ const k=a1/a2;
+ out.style.display='block';
+ out.innerHTML=`Треугольники подобны по признаку ДД. $\\angle C_1 = ${fmt(C1)}°$, $\\angle C_2 = ${fmt(C2)}°$. По теореме синусов: $a_2 = a_1 \\cdot \\dfrac{\\sin \\angle A_2}{\\sin \\angle A_1} = ${fmt(a1)} \\cdot \\dfrac{${fmt(Math.sin(A2*Math.PI/180).toFixed(4))}}{${fmt(Math.sin(A1*Math.PI/180).toFixed(4))}} = ${fmt(a2)}$. Коэффициент подобия $k = a_1/a_2 = ${fmt(k)}$.`;
+ renderMath(out);
+ addXp(3,'p5-calc');bumpProgress('p5',5);
+ });
+ })();
+
+ /* == INIT: Босс §5 == */
+ (function(){
+ const tasks=[
+ {q:'A B C A\' B\' C\' ∠A=∠A\'=60°, ∠B=∠B\'=80° $\\angle A=\\angle A\'=60°$, $\\angle B=\\angle B\'=80°$. $AB=15$, $A\'B\'=5$. Найди $k$.',ans:3,hint:'k=AB/A\'B\'=15/5=3.'},
+ {q:'Два прямоугольных треугольника. Острый угол одного $42°$, другого $42°$. Подобны ли? Гипотенуза первого $13$, второго $6.5$. Найди $k$.',ans:2,hint:'Подобны (ДД). k=13/6.5=2.'},
+ {q:'$\\triangle ABC \\sim \\triangle A\'B\'C\'$ по ДД. $k=4$. Периметр $\\triangle A\'B\'C\'=18$. Найди периметр $\\triangle ABC$.',ans:72,hint:'P=k·P\'=4·18=72.'},
+ {q:'В $\\triangle ABC$ и $\\triangle DEF$: $\\angle A=\\angle D=55°$, $\\angle B=\\angle E=75°$. $BC=20$, $EF=8$. Найди $k=BC/EF$.',ans:2.5,hint:'k=20/8=2.5.'},
+ ];
+ const bossBox=document.getElementById('p5-boss-tasks');
+ bossBox.innerHTML=tasks.map((t,i)=>`
+
+
${t.q}
+
+
+ Проверить
+
+
+
`).join('');
+ window.p5BossSolved=new Set();
+ renderMath(bossBox);
+ })();
+}
function buildP6stub(){ document.getElementById('p6-body').innerHTML='§6 — Волна 1 : содержимое появится в следующем обновлении.
'+secNav('p5','p7'); }
function buildP7stub(){ document.getElementById('p7-body').innerHTML='§7 — Волна 1 : содержимое появится в следующем обновлении.
'+secNav('p6','p8'); }
function buildP8stub(){ document.getElementById('p8-body').innerHTML='§8 — Волна 1 : содержимое появится в следующем обновлении.
'+secNav('p7','p9'); }