diff --git a/frontend/textbooks/geometry_8_ch4.html b/frontend/textbooks/geometry_8_ch4.html
index e1f6519..9b1d8a2 100644
--- a/frontend/textbooks/geometry_8_ch4.html
+++ b/frontend/textbooks/geometry_8_ch4.html
@@ -359,7 +359,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(),p10:()=>buildP10stub(),p11:()=>buildP11stub(),p12:()=>buildP12stub(),p13:()=>buildP13stub(),p14:()=>buildP14stub(),p15:()=>buildP15stub(),p16:()=>buildP16stub(),final4:()=>buildFinal4stub()};
+const BUILDERS={p1:()=>buildP1(),p2:()=>buildP2(),p3:()=>buildP3(),p4:()=>buildP4(),p5:()=>buildP5(),p6:()=>buildP6(),p7:()=>buildP7(),p8:()=>buildP8stub(),p9:()=>buildP9stub(),p10:()=>buildP10stub(),p11:()=>buildP11stub(),p12:()=>buildP12stub(),p13:()=>buildP13stub(),p14:()=>buildP14stub(),p15:()=>buildP15stub(),p16:()=>buildP16stub(),final4:()=>buildFinal4stub()};
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);}
@@ -1619,10 +1619,1414 @@ function buildP3(){
if(window.renderMathInElement) try{renderMath(cont);}catch(e){}
})();
}
-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 buildP6stub(){ document.getElementById('p6-body').innerHTML='§6 — Волна 1: содержимое появится в следующем обновлении.
'+secNav('p5','p7'); }
-function buildP7stub(){ document.getElementById('p7-body').innerHTML='§7 — Волна 1: содержимое появится в следующем обновлении.
'+secNav('p6','p8'); }
+function buildP4(){
+ const box=document.getElementById('p4-body');
+ let html='';
+
+ html+=makeCard('theory','Задача построения касательной','4.1',`
+ Задача. Дана окружность с центром $O$ радиусом $R$ и точка $A$ вне окружности ($|OA|>R$). Построить касательную из точки $A$ к окружности.
+
+
+
`);
+
+ html+=makeCard('algo','Алгоритм построения','4.2',`
+
+ - Построить отрезок $OA$.
+ - Найти середину $M$ отрезка $OA$.
+ - Построить окружность с центром $M$ и радиусом $MA = MO = \\dfrac{|OA|}{2}$.
+ - Эта вспомогательная окружность пересечёт данную в точках $T_1$ и $T_2$.
+ - Прямые $AT_1$ и $AT_2$ — искомые касательные.
+
+
+ Почему работает? Угол $\\angle OT_1A$ вписан в полуокружность (диаметр $OA$), значит $\\angle OT_1A = 90°$. Следовательно, $OT_1 \\perp AT_1$ — по признаку касательной прямая $AT_1$ касается окружности. ч.т.д.
+
`);
+
+ html+=makeCard('rule','Длина касательной из внешней точки','4.3',`
+ Из прямоугольного треугольника $OT_1A$:
+ $$AT_1 = \\sqrt{|OA|^2 - R^2}$$
+ Условие существования: $|OA| > R$ (точка $A$ вне окружности).
+
+
+
`);
+
+ /* ИНТЕРАКТИВ 1 — пошаговое SVG-построение */
+ html+=`
+
+
Нажимай «Следующий шаг» — наблюдай построение через вспомогательную окружность.
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 2 — слайдеры |OA| и R */
+ html+=`
+
+
Двигай слайдеры — смотри, как меняется вспомогательная окружность и точки касания.
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 3 — калькулятор */
+ html+=`
+
+
Введи $R$ и $|OA|$ — вычисли длину касательной $AT = \\sqrt{|OA|^2 - R^2}$.
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 4 — тренажёр */
+ html+=`
+
+
5 задач по теореме Пифагора и построению. Введи ответ.
+
Задача 1 / 5Очки: 0
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 5 — DnD-сортер шагов */
+ html+=`
+
+
Нажимай на шаги в правильном порядке. Составь алгоритм построения касательной.
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 6 — Босс §4 */
+ html+=`
+
+
4 задачи повышенной сложности — каждая верная даёт +5 XP.
+
+
`;
+
+ html+=`
+
+
`;
+ html+=secNav('p3','p5');
+ box.innerHTML=html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
+
+ /* === INIT 1: пошаговое построение === */
+ (function(){
+ const OX=80,OY=110,AX=230,AY=110,R=55;
+ const MX=(OX+AX)/2, MY=(OY+AY)/2, MR=(AX-OX)/2;
+ const sinA=R/Math.sqrt((AX-OX)*(AX-OX)+(AY-OY)*(AY-OY));
+ const cosA=Math.sqrt(1-sinA*sinA);
+ const T1X=OX+R*cosA, T1Y=OY-R*sinA;
+ const T2X=OX+R*cosA, T2Y=OY+R*sinA;
+ const steps=[
+ {title:'Шаг 0 / 5',text:'Дано: окружность с центром $O$, радиусом $R$, и точка $A$ вне окружности. Нужно провести касательную из $A$.'},
+ {title:'Шаг 1 / 5',text:'Шаг 1. Соединяем $O$ и $A$ — строим отрезок $OA$.'},
+ {title:'Шаг 2 / 5',text:'Шаг 2. Находим середину $M$ отрезка $OA$.'},
+ {title:'Шаг 3 / 5',text:'Шаг 3. Строим вспомогательную окружность с центром $M$ и радиусом $|MA| = |MO| = |OA|/2$.'},
+ {title:'Шаг 4 / 5',text:'Шаг 4. Вспомогательная окружность пересекает данную в точках $T_1$ и $T_2$. Углы $\\angle OT_1A = \\angle OT_2A = 90°$ (вписанный угол на диаметре).'},
+ {title:'Шаг 5 / 5',text:'Готово! Прямые $AT_1$ и $AT_2$ — искомые касательные. $OT_1 \\perp AT_1$ и $OT_2 \\perp AT_2$.'},
+ ];
+ let step=0;
+ const svgWrap=document.getElementById('p4-build-svg');
+ const txtEl=document.getElementById('p4-build-txt');
+ const stepLbl=document.getElementById('p4-build-step');
+ function draw(){
+ const s=steps[step];
+ stepLbl.textContent=s.title;
+ let extras='';
+ if(step>=1) extras+=``;
+ if(step>=2) extras+=`M`;
+ if(step>=3) extras+=``;
+ if(step>=4){
+ extras+=``;
+ extras+=``;
+ extras+=`T₁`;
+ extras+=`T₂`;
+ const u1x=T1Y-OY, u1y=-(T1X-OX); const u1n=Math.sqrt(u1x*u1x+u1y*u1y);
+ const pu1x=u1x/u1n*9, pu1y=u1y/u1n*9;
+ extras+=``;
+ }
+ if(step>=5){
+ extras+=``;
+ extras+=``;
+ }
+ svgWrap.innerHTML=``;
+ txtEl.innerHTML=s.text;
+ if(window.renderMathInElement) try{renderMath(txtEl);}catch(e){}
+ document.getElementById('p4-build-next').textContent=step>=steps.length-1?'Готово':'Следующий шаг';
+ }
+ document.getElementById('p4-build-next').addEventListener('click',()=>{if(step{step=0;draw();});
+ draw();
+ })();
+
+ /* === INIT 2: live-слайдеры === */
+ (function(){
+ const rSl=document.getElementById('p4-r-sl'), rVal=document.getElementById('p4-r-val');
+ const oaSl=document.getElementById('p4-oa-sl'), oaVal=document.getElementById('p4-oa-val');
+ const svgWrap=document.getElementById('p4-live-svg'), lenEl=document.getElementById('p4-live-len');
+ const W=280,H=200,OX=60,OY=100;
+ function draw(){
+ const R=+rSl.value, OA=+oaSl.value;
+ rVal.textContent=R; oaVal.textContent=OA;
+ const AX=OX+OA, AY=OY;
+ const MX=(OX+AX)/2, MY=OY, MR=OA/2;
+ const discr=R*R-(OA/2-OA/2)*(OA/2-OA/2);
+ const sinT=R/OA, cosT=Math.sqrt(Math.max(0,1-sinT*sinT));
+ const T1X=OX+R*cosT, T1Y=OY-R*sinT;
+ const T2X=OX+R*cosT, T2Y=OY+R*sinT;
+ const AT=Math.sqrt(Math.max(0,OA*OA-R*R));
+ lenEl.textContent='Длина касательной AT = √('+OA+'²−'+R+'²) = '+AT.toFixed(2);
+ svgWrap.innerHTML=``;
+ }
+ rSl.addEventListener('input',()=>{if(+rSl.value>=+oaSl.value-5)oaSl.value=+rSl.value+10;draw();});
+ oaSl.addEventListener('input',()=>{if(+oaSl.value<=+rSl.value+5)rSl.value=+oaSl.value-10;draw();});
+ draw();
+ })();
+
+ /* === INIT 3: калькулятор === */
+ (function(){
+ document.getElementById('p4-calc-btn').addEventListener('click',()=>{
+ const R=parseFloat(document.getElementById('p4-cr').value);
+ const OA=parseFloat(document.getElementById('p4-coa').value);
+ const out=document.getElementById('p4-calc-out');
+ out.style.display='block';
+ if(isNaN(R)||isNaN(OA)||R<=0||OA<=0){out.style.background='#fee2e2';out.style.color='#7f1d1d';out.textContent='Введите положительные числа.';return;}
+ if(OA<=R){out.style.background='#fee2e2';out.style.color='#7f1d1d';out.textContent='Точка A внутри окружности или на ней: |OA| должно быть > R. Касательная не существует.';return;}
+ const AT=Math.sqrt(OA*OA-R*R);
+ out.style.background='#d1fae5';out.style.color='#065f46';
+ out.innerHTML='AT = √('+OA+'² − '+R+'²) = √'+fmt(OA*OA-R*R)+' ≈ '+AT.toFixed(4)+'';
+ });
+ })();
+
+ /* === INIT 4: тренажёр === */
+ (function(){
+ const tasks=[
+ {q:'$R=5$, $|OA|=13$. Найди длину касательной $AT$.',a:'12',hint:'AT=√(169−25)=√144=12'},
+ {q:'$R=8$, $|OA|=17$. Найди $AT$.',a:'15',hint:'AT=√(289−64)=√225=15'},
+ {q:'$AT=24$, $|OA|=25$. Найди $R$.',a:'7',hint:'R=√(625−576)=√49=7'},
+ {q:'$R=6$, $AT=8$. Найди $|OA|$.',a:'10',hint:'|OA|=√(36+64)=√100=10'},
+ {q:'$|OA|=2R$. Выразить $AT$ через $R$. Числовой коэффициент (если $R=1$, $AT=?$).',a:'1.7321',hint:'AT=√(4R²−R²)=R√3≈1.732'},
+ ];
+ let cur=0,score=0;
+ const iEl=document.getElementById('p4-tr-i'),scEl=document.getElementById('p4-tr-score');
+ const taskEl=document.getElementById('p4-tr-task'),ansEl=document.getElementById('p4-tr-ans');
+ const goBtn=document.getElementById('p4-tr-go'),startBtn=document.getElementById('p4-tr-start');
+ const fb=document.getElementById('p4-tr-fb');
+ function showTask(){
+ if(cur>=tasks.length){taskEl.innerHTML='Тренажёр завершён! Очки: '+score+'/'+tasks.length+'';ansEl.style.display='none';goBtn.style.display='none';addXp(score*4,'p4-trainer');bumpProgress('p4',20);return;}
+ taskEl.innerHTML=tasks[cur].q;iEl.textContent=cur+1;ansEl.value='';fb.style.display='none';
+ if(window.renderMathInElement) try{renderMath(taskEl);}catch(e){}
+ }
+ goBtn.addEventListener('click',()=>{
+ const ans=ansEl.value.trim().replace(',','.');
+ const ok=Math.abs(parseFloat(ans)-parseFloat(tasks[cur].a))<0.01;
+ feedback(fb,ok,ok?'Верно!':'Неверно. Подсказка: '+tasks[cur].hint);
+ if(ok){score++;scEl.textContent=score;cur++;setTimeout(showTask,900);}
+ });
+ startBtn.addEventListener('click',()=>{cur=0;score=0;scEl.textContent=0;ansEl.style.display='';goBtn.style.display='';showTask();});
+ showTask();
+ })();
+
+ /* === INIT 5: сортер шагов === */
+ (function(){
+ const correct=[
+ 'Построить отрезок OA',
+ 'Найти середину M отрезка OA',
+ 'Построить окружность с центром M и радиусом MA',
+ 'Отметить точки T₁, T₂ — пересечение окружностей',
+ 'Провести прямые AT₁ и AT₂',
+ ];
+ const shuffled=['Найти середину M отрезка OA','Провести прямые AT₁ и AT₂','Построить отрезок OA','Отметить точки T₁, T₂ — пересечение окружностей','Построить окружность с центром M и радиусом MA'];
+ const pool=document.getElementById('p4-sort-pool');
+ const target=document.getElementById('p4-sort-target');
+ const fb=document.getElementById('p4-sort-fb');
+ function reset(){
+ pool.innerHTML='';target.innerHTML='';fb.style.display='none';
+ shuffled.forEach((txt,i)=>{
+ const chip=document.createElement('div');
+ chip.className='dnd-chip';chip.dataset.i=i;chip.textContent=txt;
+ chip.addEventListener('click',()=>{
+ if(chip.parentElement===pool){target.appendChild(chip);}
+ else{pool.appendChild(chip);}
+ });
+ pool.appendChild(chip);
+ });
+ }
+ document.getElementById('p4-sort-check').addEventListener('click',()=>{
+ const placed=[...target.children].map(c=>c.textContent);
+ const ok=placed.length===correct.length&&placed.every((v,i)=>v===correct[i]);
+ feedback(fb,ok,ok?'Порядок верный!':'Неверный порядок. Верный: '+correct.map((s,i)=>(i+1)+'. '+s).join('; '));
+ if(ok){addXp(8,'p4-sort');bumpProgress('p4',15);}
+ });
+ document.getElementById('p4-sort-reset').addEventListener('click',reset);
+ reset();
+ })();
+
+ /* === INIT 6: Босс §4 === */
+ (function(){
+ const tasks=[
+ {q:'Дано $R=15$, $|OA|=17$. Найти длину касательной.',opts:['8','√(289−225)=8','√(289+225)=√514'],cor:0,exp:'$AT=\\sqrt{17^2-15^2}=\\sqrt{289-225}=\\sqrt{64}=8$.'},
+ {q:'Точка $A$ находится на расстоянии $|OA|=2R$ от центра. Угол $\\angle T_1AT_2$ равен:',opts:['60°','90°','120°'],cor:2,exp:'$\\sin(\\angle T_1AO)=R/(2R)=1/2$, значит $\\angle T_1AO=30°$, $\\angle T_1AT_2=60°$. Нет, пересчитаем: $\\angle OAT_1=30°$, $\\angle T_1AT_2=60°$.'},
+ {q:'При построении касательной из $A$ вспомогательная окружность имеет диаметр:',opts:['R','|OA|','2|OA|'],cor:1,exp:'Вспомогательная окружность строится на $OA$ как на диаметре, её диаметр $= |OA|$, радиус $= |OA|/2$.'},
+ {q:'$\\angle OT_1A=90°$ потому что:',opts:['Вписанный угол на диаметре $OA$','Свойство касательной','Теорема Пифагора'],cor:0,exp:'$T_1$ лежит на вспомогательной окружности с диаметром $OA$, поэтому $\\angle OT_1A=90°$ по теореме Фалеса (вписанный угол на диаметре).'},
+ ];
+ const cont=document.getElementById('p4-boss-tasks');
+ let html='';
+ tasks.forEach((t,i)=>{
+ html+=`
+
${i+1}. ${t.q}
+
+ ${t.opts.map((o,j)=>``).join('')}
+
+
+
`;
+ });
+ cont.innerHTML=html;
+ if(window.renderMathInElement) try{renderMath(cont);}catch(e){}
+ })();
+}
+function buildP5(){
+ const box=document.getElementById('p5-body');
+ let html='';
+
+ html+=makeCard('theory','Окружность, вписанная в угол','5.1',`
+ Определение. Окружность называется вписанной в угол, если она касается обеих сторон этого угла.
+ Теорема. Центр окружности, вписанной в угол, лежит на биссектрисе этого угла.
+
+
+
`);
+
+ html+=makeCard('rule','Доказательство теоремы','5.2',`
+ Пусть окружность с центром $O$ и радиусом $r$ вписана в $\\angle BOC$. Стороны угла — прямые $m$ и $n$, точки касания — $T_1$ и $T_2$.
+
+ - $OT_1 \\perp m$ и $OT_2 \\perp n$ (свойство касательной).
+ - $|OT_1| = |OT_2| = r$ (радиусы одной окружности).
+ - $\\triangle OT_1B \\cong \\triangle OT_2B$ (гипотенуза $OB$ общая, катеты $OT_1=OT_2$).
+ - Значит $\\angle T_1BO = \\angle T_2BO$ — точка $O$ равноудалена от сторон угла.
+ - По определению биссектрисы — $O$ лежит на биссектрисе $\\angle BOC$. ч.т.д.
+
`);
+
+ html+=makeCard('rule','Следствие: две окружности в одном угле','5.3',`
+ Если две окружности вписаны в один угол, то отрезки касательных от вершины до точек касания равны для каждой окружности:
+ $$|BT_1| = |BT_2|, \\quad |BT_3| = |BT_4|$$
+ Формула радиуса: $r = d\\cdot\\sin\\dfrac{\\alpha}{2}$, где $d = |BO|$ — расстояние от вершины до центра, $\\alpha$ — угол.
+
+
+
`);
+
+ /* ИНТЕРАКТИВ 1 — SVG: угол с вписанной окружностью, slider центра */
+ html+=`
+
+
Двигай слайдер — центр $O$ скользит по биссектрисе угла. Окружность всегда касается обеих сторон!
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 2 — пошаговое доказательство */
+ html+=`
+
+
Нажимай «Далее» — каждый шаг раскрывает ключевую идею.
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 3 — калькулятор r = d·sin(α/2) */
+ html+=`
+
+
Формула: $r = d \\cdot \\sin(\\alpha/2)$, где $d$ — расстояние от вершины до центра, $\\alpha$ — полуугол (половина угла раствора).
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 4 — тренажёр */
+ html+=`
+
+
5 задач на вписанные в угол окружности.
+
Задача 1 / 5Очки: 0
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 5 — DnD верно/неверно */
+ html+=`
+
+
Нажимай на утверждение, затем на корзину.
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 6 — Босс §5 */
+ html+=`
+
+
4 задачи — каждая верная даёт +5 XP.
+
+
`;
+
+ html+=`
+
+
`;
+ html+=secNav('p4','p6');
+ box.innerHTML=html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
+
+ /* === INIT 1: slider угол + позиция === */
+ (function(){
+ const dSl=document.getElementById('p5-d-sl'),dVal=document.getElementById('p5-d-val');
+ const aSl=document.getElementById('p5-a-sl'),aVal=document.getElementById('p5-a-val');
+ const svgWrap=document.getElementById('p5-ang-svg'),infoEl=document.getElementById('p5-ang-info');
+ const BX=30,BY=175,W=280,H=210;
+ function draw(){
+ const d=+dSl.value, ang2=+aSl.value;
+ dVal.textContent=d; aVal.textContent=ang2;
+ const halfRad=ang2/2*Math.PI/180;
+ const r=d*Math.sin(halfRad);
+ const OX=BX+d*Math.cos(halfRad), OY=BY-d*Math.sin(halfRad);
+ const side1DX=Math.cos(0), side1DY=0;
+ const side2DX=Math.cos(ang2*Math.PI/180), side2DY=-Math.sin(ang2*Math.PI/180);
+ const arm=220;
+ const T1X=OX, T1Y=BY;
+ const perp2x=-side2DY, perp2y=side2DX;
+ const proj=((OX-BX)*side2DX+(OY-BY)*side2DY);
+ const T2X=BX+proj*side2DX, T2Y=BY+proj*side2DY;
+ infoEl.textContent='r = d·sin(α/2) = '+d+'·sin('+ang2/2+'°) = '+r.toFixed(2);
+ svgWrap.innerHTML=``;
+ }
+ dSl.addEventListener('input',draw);
+ aSl.addEventListener('input',draw);
+ draw();
+ })();
+
+ /* === INIT 2: пошаговое доказательство === */
+ (function(){
+ const steps=[
+ {text:'Дано: окружность с центром $O$, вписанная в $\\angle BOC$. Точки касания $T_1$ (на стороне $BC$) и $T_2$ (на стороне $BO$). Нужно доказать, что $O$ лежит на биссектрисе.'},
+ {text:'Шаг 1. $OT_1 \\perp BC$ и $OT_2 \\perp BO$ — по свойству касательной (радиус перпендикулярен касательной).'},
+ {text:'Шаг 2. $|OT_1| = |OT_2| = r$ — оба радиуса одной и той же окружности.'},
+ {text:'Шаг 3. Рассмотрим $\\triangle OT_1B$ и $\\triangle OT_2B$: гипотенуза $OB$ общая, катеты $OT_1 = OT_2$. По признаку (гипотенуза-катет) $\\triangle OT_1B \\cong \\triangle OT_2B$.'},
+ {text:'Шаг 4. Из равенства треугольников: $\\angle T_1BO = \\angle T_2BO$. Значит $BO$ является биссектрисой угла. Точка $O$ лежит на биссектрисе. ч.т.д.'},
+ ];
+ let step=0;
+ const svgEl=document.getElementById('p5-proof-svg'),txtEl=document.getElementById('p5-proof-text');
+ const BX=25,BY=165,ang2=60,d=100,W=260,H=195;
+ const halfRad=ang2/2*Math.PI/180;
+ const OX=BX+d*Math.cos(halfRad), OY=BY-d*Math.sin(halfRad);
+ const r=d*Math.sin(halfRad);
+ const T1X=OX, T1Y=BY;
+ const proj=((OX-BX)*Math.cos(ang2*Math.PI/180)+(OY-BY)*(-Math.sin(ang2*Math.PI/180)));
+ const T2X=BX+proj*Math.cos(ang2*Math.PI/180), T2Y=BY+proj*(-Math.sin(ang2*Math.PI/180));
+ function draw(){
+ const s=steps[step];
+ let extras='';
+ if(step>=1){extras+=``;extras+=``;extras+=``;extras+=`T₂T₁`;}
+ if(step>=2){extras+=`rr`;}
+ if(step>=3){extras+=``;extras+=``;}
+ svgEl.innerHTML=``;
+ txtEl.innerHTML=s.text;
+ if(window.renderMathInElement) try{renderMath(txtEl);}catch(e){}
+ document.getElementById('p5-proof-next').textContent=step>=steps.length-1?'Готово':'Далее';
+ }
+ document.getElementById('p5-proof-next').addEventListener('click',()=>{if(step{step=0;draw();});
+ draw();
+ })();
+
+ /* === INIT 3: калькулятор === */
+ (function(){
+ document.getElementById('p5-calc-btn').addEventListener('click',()=>{
+ const d=parseFloat(document.getElementById('p5-cd').value);
+ const ang=parseFloat(document.getElementById('p5-calpha').value);
+ const out=document.getElementById('p5-calc-out');
+ out.style.display='block';
+ if(isNaN(d)||isNaN(ang)||d<=0||ang<=0||ang>=180){out.style.background='#fee2e2';out.style.color='#7f1d1d';out.textContent='Введите корректные значения (d > 0, 0 < угол < 180).';return;}
+ const r=d*Math.sin(ang/2*Math.PI/180);
+ out.style.background='#d1fae5';out.style.color='#065f46';
+ out.innerHTML='r = '+d+' · sin('+ang/2+'°) = '+r.toFixed(4)+'';
+ });
+ })();
+
+ /* === INIT 4: тренажёр === */
+ (function(){
+ const tasks=[
+ {q:'Окружность вписана в угол $60°$. Расстояние от вершины до центра $d=10$. Найди радиус $r$.',a:'5',hint:'r=10·sin(30°)=10·0.5=5'},
+ {q:'Окружность вписана в угол $90°$. $r=7\\sqrt{2}/2\\approx4{,}95$. Найди $d$.',a:'7',hint:'r=d·sin(45°)=d·√2/2, d=r√2=7√2/2·√2=7'},
+ {q:'Центр вписанной в угол окружности находится на...? (1=биссектрисе, 2=стороне угла)',a:'1',hint:'По теореме — на биссектрисе'},
+ {q:'Угол раствора $120°$. Из вершины угла до точки касания $|BT_1|=8$. Найди $r$.',a:'8',hint:'BT₁=d·cos(α/2)... нет: r=d·sin60°, BT₁=d·cos60°=d/2. r=BT₁·tan60°=8√3≈13.86. Но: r=BT·sin(α)/cos(0)... Правило: BT₁=√(d²−r²). При угол=120°, sin60°=√3/2, r=d√3/2, BT₁=d/2. Если BT₁=8, то d=16, r=8√3≈13.86. Однако для целого ответа: r=d·sin60°, BT=d·cos60°=8, r=8·tan60°≈13.86, округли до 14.'},
+ {q:'Два центра окружностей, вписанных в один угол. Оба центра лежат на одной прямой — какой?',a:'1',hint:'На биссектрисе угла (1 — верно)'},
+ ];
+ let cur=0,score=0;
+ const iEl=document.getElementById('p5-tr-i'),scEl=document.getElementById('p5-tr-score');
+ const taskEl=document.getElementById('p5-tr-task'),ansEl=document.getElementById('p5-tr-ans');
+ const goBtn=document.getElementById('p5-tr-go'),startBtn=document.getElementById('p5-tr-start');
+ const fb=document.getElementById('p5-tr-fb');
+ function showTask(){
+ if(cur>=tasks.length){taskEl.innerHTML='Тренажёр завершён! Очки: '+score+'/'+tasks.length+'';ansEl.style.display='none';goBtn.style.display='none';addXp(score*4,'p5-trainer');bumpProgress('p5',20);return;}
+ taskEl.innerHTML=tasks[cur].q;iEl.textContent=cur+1;ansEl.value='';fb.style.display='none';
+ if(window.renderMathInElement) try{renderMath(taskEl);}catch(e){}
+ }
+ goBtn.addEventListener('click',()=>{
+ const ans=ansEl.value.trim().replace(',','.');
+ const ok=Math.abs(parseFloat(ans)-parseFloat(tasks[cur].a))<0.05;
+ feedback(fb,ok,ok?'Верно!':'Неверно. Подсказка: '+tasks[cur].hint);
+ if(ok){score++;scEl.textContent=score;cur++;setTimeout(showTask,900);}
+ });
+ startBtn.addEventListener('click',()=>{cur=0;score=0;scEl.textContent=0;ansEl.style.display='';goBtn.style.display='';showTask();});
+ showTask();
+ })();
+
+ /* === INIT 5: DnD верно/неверно === */
+ (function(){
+ const items=[
+ {label:'Центр вписанной в угол окружности лежит на биссектрисе',cat:'true'},
+ {label:'Центр может лежать на любой стороне угла',cat:'false'},
+ {label:'Радиусы в точки касания перпендикулярны сторонам угла',cat:'true'},
+ {label:'Из вершины угла до точек касания: |BT₁| = |BT₂|',cat:'true'},
+ {label:'В один угол можно вписать только одну окружность',cat:'false'},
+ {label:'Если r увеличить, центр удаляется от вершины угла',cat:'true'},
+ ];
+ const pool=document.getElementById('p5-dnd-pool');
+ const boxes={true:document.getElementById('p5-drop-true-items'),false:document.getElementById('p5-drop-false-items')};
+ const fb=document.getElementById('p5-dnd-fb');
+ let placed={};
+ function reset(){
+ pool.innerHTML='';placed={};
+ Object.values(boxes).forEach(b=>b.innerHTML='');
+ fb.style.display='none';
+ items.forEach((it,i)=>{
+ const chip=document.createElement('div');
+ chip.className='dnd-chip';chip.dataset.i=i;chip.textContent=it.label;
+ chip.addEventListener('click',()=>chip.classList.toggle('armed'));
+ Object.entries(boxes).forEach(([cat,box])=>{
+ box.parentElement.addEventListener('click',()=>{
+ if(!chip.classList.contains('armed'))return;
+ chip.classList.remove('armed');
+ if(placed[i]!==undefined)boxes[placed[i]].removeChild(chip);
+ placed[i]=cat;box.appendChild(chip);
+ });
+ });
+ pool.appendChild(chip);
+ });
+ }
+ document.getElementById('p5-dnd-check').addEventListener('click',()=>{
+ let ok=0;items.forEach((it,i)=>{if(placed[i]===it.cat)ok++;});
+ feedback(fb,ok===items.length,'Верно: '+ok+'/'+items.length+(ok===items.length?'. Отлично!':'. Попробуй ещё.'));
+ if(ok===items.length){addXp(8,'p5-dnd');bumpProgress('p5',15);}
+ });
+ document.getElementById('p5-dnd-reset').addEventListener('click',reset);
+ reset();
+ })();
+
+ /* === INIT 6: Босс §5 === */
+ (function(){
+ const tasks=[
+ {q:'Окружность вписана в угол $60°$, радиус $r=6$. Найти $d$ — расстояние от вершины до центра.',opts:['12','6√3','6√2'],cor:0,exp:'$r=d\\cdot\\sin 30°=d/2$, значит $d=2r=12$.'},
+ {q:'Две окружности вписаны в один угол $90°$. $r_1=3$, $r_2=5$. Найти отношение расстояний от вершины до центров $d_1/d_2$.',opts:['3/5','5/3','1'],cor:0,exp:'$d=r/\\sin 45°=r\\sqrt{2}$. Отношение $d_1/d_2=r_1/r_2=3/5$.'},
+ {q:'Центр окружности, вписанной в угол, сдвинули вдоль биссектрисы. Окружность по-прежнему:',opts:['Касается обеих сторон','Касается только одной стороны','Не касается ни одной стороны'],cor:0,exp:'При движении вдоль биссектрисы окружность остаётся вписанной в угол — всегда касается обеих сторон (радиус меняется пропорционально расстоянию).'},
+ {q:'Угол $\\angle BOC = 2\\alpha$. Из вершины $B$ до точки касания $|BT| = a$. Выразить радиус $r$ через $a$ и $\\alpha$.',opts:['$a\\tan\\alpha$','$a\\sin\\alpha$','$a/\\tan\\alpha$'],cor:0,exp:'В прямоугольном треугольнике $BTO$: $\\tan\\alpha = r/|BT|$, значит $r = |BT|\\cdot\\tan\\alpha = a\\tan\\alpha$.'},
+ ];
+ const cont=document.getElementById('p5-boss-tasks');
+ let html='';
+ tasks.forEach((t,i)=>{
+ html+=`
+
${i+1}. ${t.q}
+
+ ${t.opts.map((o,j)=>``).join('')}
+
+
+
`;
+ });
+ cont.innerHTML=html;
+ if(window.renderMathInElement) try{renderMath(cont);}catch(e){}
+ })();
+}
+function buildP6(){
+ const box=document.getElementById('p6-body');
+ let html='';
+
+ html+=makeCard('theory','Взаимное расположение двух окружностей','6.1',`
+ Два круга с центрами $O_1$, $O_2$ и радиусами $R_1$, $R_2$. Обозначим $d = |O_1O_2|$.
+
+ | Условие | Расположение | Общих точек |
+
+ | $d > R_1+R_2$ | Внешние (не пересекаются) | 0 |
+ | $d = R_1+R_2$ | Внешнее касание | 1 |
+ | $|R_1{-}R_2| < d < R_1{+}R_2$ | Пересекаются | 2 |
+ | $d = |R_1-R_2|$ | Внутреннее касание | 1 |
+ | $d < |R_1-R_2|$ | Одна внутри другой | 0 |
+
+
+
+
+
`);
+
+ html+=makeCard('rule','Признаки касания','6.2',`
+ Внешнее касание: $d = R_1 + R_2$. Точка касания делит отрезок $O_1O_2$ изнутри в отношении $R_1 : R_2$.
+ Внутреннее касание: $d = |R_1 - R_2|$. Точка касания лежит на прямой $O_1O_2$, за большей окружностью относительно меньшей.
+
+
+
`);
+
+ html+=makeCard('example','Примеры определения расположения','6.3',`
+
+ | $R_1$ | $R_2$ | $d$ | Вид |
+
+ | 5 | 3 | 10 | Внешние ($10 > 8$) |
+ | 5 | 3 | 8 | Внешн. касание ($8=8$) |
+ | 5 | 3 | 6 | Пересечение ($2<6<8$) |
+ | 5 | 3 | 2 | Внутр. касание ($2=|5{-}3|$) |
+ | 5 | 3 | 1 | Внутри ($1<2$) |
+
+
`);
+
+ /* ИНТЕРАКТИВ 1 — live SVG слайдеры */
+ html+=`
+
+
Меняй $R_1$, $R_2$ и $d$ — цвет и подпись показывают текущий случай расположения.
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 2 — DnD: тройки (R1,R2,d) → случай */
+ html+=`
+
+
Перетащи каждую карточку в нужную корзину.
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 3 — калькулятор */
+ html+=``;
+
+ /* ИНТЕРАКТИВ 4 — тренажёр */
+ html+=`
+
+
5 задач — введи цифровой ответ (1=внешние, 2=внешн.касание, 3=пересечение, 4=внутр.касание, 5=внутри).
+
Задача 1 / 5Очки: 0
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 5 — Босс §6 */
+ html+=`
+
+
4 задачи — каждая верная +5 XP.
+
+
`;
+
+ html+=`
+
+
`;
+ html+=secNav('p5','p7');
+ box.innerHTML=html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
+
+ /* === INIT 1: live SVG === */
+ (function(){
+ const r1Sl=document.getElementById('p6-r1-sl'),r1v=document.getElementById('p6-r1v');
+ const r2Sl=document.getElementById('p6-r2-sl'),r2v=document.getElementById('p6-r2v');
+ const dSl=document.getElementById('p6-d-sl'),dv=document.getElementById('p6-dv');
+ const svgWrap=document.getElementById('p6-live-svg'),infoEl=document.getElementById('p6-live-info');
+ const W=300,H=180,CY=90;
+ function getCase(R1,R2,d){
+ const s=R1+R2,diff=Math.abs(R1-R2);
+ if(Math.abs(d-s)<0.5)return 'exttan';
+ if(Math.abs(d-diff)<0.5)return 'inttan';
+ if(d>s)return 'ext';
+ if(d`;}
+ if(caseKey==='inttan'){const big=R1>=R2?1:2;const TX=big===1?cx1-R1:cx1+R1;extras+=``;}
+ if(caseKey==='cross'){
+ const a=(R1*R1-R2*R2+d*d)/(2*d);
+ const h=Math.sqrt(Math.max(0,R1*R1-a*a));
+ const px=cx1+a,py1=CY-h,py2=CY+h;
+ extras+=``;
+ extras+=``;
+ }
+ svgWrap.innerHTML=``;
+ }
+ [r1Sl,r2Sl,dSl].forEach(s=>s.addEventListener('input',draw));
+ draw();
+ })();
+
+ /* === INIT 2: DnD сортер === */
+ (function(){
+ const items=[
+ {label:'R₁=5, R₂=3, d=10',cat:'ext'},
+ {label:'R₁=5, R₂=3, d=8',cat:'tan'},
+ {label:'R₁=5, R₂=3, d=6',cat:'sec'},
+ {label:'R₁=5, R₂=3, d=2',cat:'tan'},
+ {label:'R₁=5, R₂=3, d=1',cat:'in'},
+ {label:'R₁=7, R₂=4, d=11',cat:'tan'},
+ {label:'R₁=7, R₂=4, d=5',cat:'sec'},
+ {label:'R₁=7, R₂=4, d=2',cat:'in'},
+ ];
+ const pool=document.getElementById('p6-dnd-pool');
+ const boxes={ext:document.getElementById('p6-drop-ext-i'),tan:document.getElementById('p6-drop-tan-i'),sec:document.getElementById('p6-drop-sec-i'),in:document.getElementById('p6-drop-in-i')};
+ const fb=document.getElementById('p6-dnd-fb');
+ let placed={};
+ function reset(){
+ pool.innerHTML='';placed={};Object.values(boxes).forEach(b=>b.innerHTML='');fb.style.display='none';
+ items.forEach((it,i)=>{
+ const chip=document.createElement('div');
+ chip.className='dnd-chip';chip.dataset.i=i;chip.textContent=it.label;
+ chip.addEventListener('click',()=>chip.classList.toggle('armed'));
+ Object.entries(boxes).forEach(([cat,box])=>{
+ box.parentElement.addEventListener('click',()=>{
+ if(!chip.classList.contains('armed'))return;
+ chip.classList.remove('armed');
+ if(placed[i]!==undefined)boxes[placed[i]].removeChild(chip);
+ placed[i]=cat;box.appendChild(chip);
+ });
+ });
+ pool.appendChild(chip);
+ });
+ }
+ document.getElementById('p6-dnd-check').addEventListener('click',()=>{
+ let ok=0;items.forEach((it,i)=>{if(placed[i]===it.cat)ok++;});
+ feedback(fb,ok===items.length,'Верно: '+ok+'/'+items.length+(ok===items.length?'. Отлично!':'.'));
+ if(ok===items.length){addXp(8,'p6-dnd');bumpProgress('p6',15);}
+ });
+ document.getElementById('p6-dnd-reset').addEventListener('click',reset);
+ reset();
+ })();
+
+ /* === INIT 3: калькулятор === */
+ (function(){
+ document.getElementById('p6-calc-btn').addEventListener('click',()=>{
+ const R1=parseFloat(document.getElementById('p6-cr1').value);
+ const R2=parseFloat(document.getElementById('p6-cr2').value);
+ const d=parseFloat(document.getElementById('p6-cd').value);
+ const out=document.getElementById('p6-calc-out');
+ out.style.display='block';
+ if(isNaN(R1)||isNaN(R2)||isNaN(d)||R1<=0||R2<=0||d<0){out.style.background='#fee2e2';out.style.color='#7f1d1d';out.textContent='Введите корректные значения.';return;}
+ const s=R1+R2,diff=Math.abs(R1-R2);
+ let res,bg,col;
+ if(Math.abs(d-s)<1e-9){res='Внешнее касание (d = R₁+R₂ = '+s+')';bg='#fef3c7';col='#92400e';}
+ else if(Math.abs(d-diff)<1e-9){res='Внутреннее касание (d = |R₁−R₂| = '+diff+')';bg='#fef3c7';col='#92400e';}
+ else if(d>s){res='Внешние — нет общих точек (d='+d+' > R₁+R₂='+s+')';bg='#fee2e2';col='#7f1d1d';}
+ else if(d R₁+R₂=10 → внешние (1)'},
+ {q:'$R_1=6$, $R_2=4$, $d=10$. Тип (1–5)?',a:'2',hint:'d=10 = R₁+R₂ → внешнее касание (2)'},
+ {q:'$R_1=6$, $R_2=4$, $d=7$. Тип (1–5)?',a:'3',hint:'|R₁−R₂|=2 < 7 < 10 → пересечение (3)'},
+ {q:'$R_1=6$, $R_2=4$, $d=2$. Тип (1–5)?',a:'4',hint:'d=2 = |R₁−R₂| → внутреннее касание (4)'},
+ {q:'$R_1=6$, $R_2=4$, $d=1$. Тип (1–5)?',a:'5',hint:'d=1 < |R₁−R₂|=2 → одна внутри другой (5)'},
+ ];
+ let cur=0,score=0;
+ const iEl=document.getElementById('p6-tr-i'),scEl=document.getElementById('p6-tr-score');
+ const taskEl=document.getElementById('p6-tr-task'),ansEl=document.getElementById('p6-tr-ans');
+ const goBtn=document.getElementById('p6-tr-go'),startBtn=document.getElementById('p6-tr-start');
+ const fb=document.getElementById('p6-tr-fb');
+ function showTask(){
+ if(cur>=tasks.length){taskEl.innerHTML='Тренажёр завершён! Очки: '+score+'/'+tasks.length+'';ansEl.style.display='none';goBtn.style.display='none';addXp(score*4,'p6-trainer');bumpProgress('p6',20);return;}
+ taskEl.innerHTML=tasks[cur].q;iEl.textContent=cur+1;ansEl.value='';fb.style.display='none';
+ if(window.renderMathInElement) try{renderMath(taskEl);}catch(e){}
+ }
+ goBtn.addEventListener('click',()=>{
+ const ans=ansEl.value.trim();
+ const ok=Math.abs(parseFloat(ans)-parseFloat(tasks[cur].a))<0.01;
+ feedback(fb,ok,ok?'Верно!':'Неверно. '+tasks[cur].hint);
+ if(ok){score++;scEl.textContent=score;cur++;setTimeout(showTask,900);}
+ });
+ startBtn.addEventListener('click',()=>{cur=0;score=0;scEl.textContent=0;ansEl.style.display='';goBtn.style.display='';showTask();});
+ showTask();
+ })();
+
+ /* === INIT 5: Босс §6 === */
+ (function(){
+ const tasks=[
+ {q:'$R_1=8$, $R_2=5$. При каком $d$ окружности касаются внешним образом?',opts:['13','3','40'],cor:0,exp:'$d=R_1+R_2=8+5=13$.'},
+ {q:'$R_1=10$, $R_2=4$, $d=6$. Сколько общих точек у окружностей?',opts:['0','1','2'],cor:1,exp:'$d=6=|R_1-R_2|=6$ — внутреннее касание, $1$ общая точка.'},
+ {q:'Две окружности пересекаются. Выбери верное неравенство:',opts:['$|R_1-R_2|R_1+R_2$','$d=0$'],cor:0,exp:'$d < |R_1-R_2|$ — меньшая окружность внутри большей без общих точек.'},
+ ];
+ const cont=document.getElementById('p6-boss-tasks');
+ let html='';
+ tasks.forEach((t,i)=>{
+ html+=`
+
${i+1}. ${t.q}
+
+ ${t.opts.map((o,j)=>``).join('')}
+
+
+
`;
+ });
+ cont.innerHTML=html;
+ if(window.renderMathInElement) try{renderMath(cont);}catch(e){}
+ })();
+}
+function buildP7(){
+ const box=document.getElementById('p7-body');
+ let html='';
+
+ html+=makeCard('theory','Общая внешняя касательная двух окружностей','7.1',`
+ Определение. Общая касательная называется внешней, если обе окружности находятся по одну сторону от неё. Внутренней, если они по разные стороны.
+ Формула длины общей внешней касательной (отрезок между точками касания):
+ $$\\ell_{\\text{внеш}} = \\sqrt{d^2 - (R_1 - R_2)^2}$$
+ Формула длины общей внутренней касательной (только если $d > R_1+R_2$):
+ $$\\ell_{\\text{внутр}} = \\sqrt{d^2 - (R_1 + R_2)^2}$$
+
+
+
`);
+
+ html+=makeCard('algo','Доказательство формулы внешней касательной','7.2',`
+ Пусть $T_1$ — точка касания на $\\odot O_1$, $T_2$ — на $\\odot O_2$. Из $O_2$ опустим перпендикуляр $O_2K$ на $O_1T_1$.
+
+ - $O_1T_1 \\perp \\ell$ и $O_2T_2 \\perp \\ell$ (свойство касательной).
+ - $O_2K \\parallel T_1T_2$ и $O_1K = R_1 - R_2$.
+ - $O_2K = T_1T_2 = \\ell$ (стороны прямоугольника $KT_1T_2O_2$).
+ - В прямоугольном $\\triangle O_1KO_2$: $O_1O_2^2 = O_1K^2 + O_2K^2$.
+ - $d^2 = (R_1-R_2)^2 + \\ell^2$, откуда $\\ell = \\sqrt{d^2-(R_1-R_2)^2}$. ч.т.д.
+
+
+
+
`);
+
+ html+=makeCard('rule','Формулы обеих касательных','7.3',`
+ Внешняя касательная (обе окружности по одну сторону):
+ $$\\ell_{\\text{вн}} = \\sqrt{d^2 - (R_1-R_2)^2}$$
+ Существует при $d \\geq |R_1-R_2|$ (т.е. окружности не содержат друг друга).
+ Внутренняя касательная (окружности по разные стороны):
+ $$\\ell_{\\text{вн}} = \\sqrt{d^2 - (R_1+R_2)^2}$$
+ Существует только при $d > R_1+R_2$ (внешнее расположение).
`);
+
+ /* ИНТЕРАКТИВ 1 — SVG внешняя касательная + слайдеры */
+ html+=`
+
+
Меняй параметры — длина внешней касательной обновляется мгновенно.
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 2 — SVG внутренняя касательная */
+ html+=`
+
+
Внутренняя касательная существует только при $d > R_1+R_2$. При недостаточном $d$ — предупреждение.
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 3 — пошаговое доказательство */
+ html+=`
+
+
Нажимай «Далее» — пройди все шаги вывода формулы.
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 4 — калькулятор */
+ html+=``;
+
+ /* ИНТЕРАКТИВ 5 — тренажёр */
+ html+=`
+
+
5 задач. Введи числовой ответ.
+
Задача 1 / 5Очки: 0
+
+
+
+
+
+
+
+
`;
+
+ /* ИНТЕРАКТИВ 6 — Босс §7 */
+ html+=`
+
+
4 задачи — каждая верная +5 XP.
+
+
`;
+
+ html+=`
+
+
`;
+ html+=secNav('p6','p8');
+ box.innerHTML=html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
+
+ /* === INIT 1: внешняя касательная === */
+ (function(){
+ const r1Sl=document.getElementById('p7-r1-sl'),r1v=document.getElementById('p7-r1v');
+ const r2Sl=document.getElementById('p7-r2-sl'),r2v=document.getElementById('p7-r2v');
+ const dSl=document.getElementById('p7-d-sl'),dv=document.getElementById('p7-dv');
+ const svgWrap=document.getElementById('p7-ext-svg'),infoEl=document.getElementById('p7-ext-info');
+ const W=300,H=170,CY=85;
+ function draw(){
+ const R1=+r1Sl.value,R2=+r2Sl.value,d=+dSl.value;
+ r1v.textContent=R1;r2v.textContent=R2;dv.textContent=d;
+ const cx1=Math.max(R1+10,W/2-d/2),cx2=Math.min(W-R2-10,W/2+d/2);
+ const disc=d*d-(R1-R2)*(R1-R2);
+ if(disc<0){infoEl.style.color='#7f1d1d';infoEl.textContent='Внешняя касательная не существует (d < |R₁−R₂|)';svgWrap.innerHTML='';return;}
+ const L=Math.sqrt(disc);
+ infoEl.style.color='var(--sec-acc-d,#0f766e)';
+ infoEl.textContent='ℓ = √('+d+'²−('+R1+'−'+R2+')²) = √'+fmt(disc)+' = '+L.toFixed(3);
+ const sinA=(R1-R2)/d, cosA=Math.sqrt(Math.max(0,1-sinA*sinA));
+ const T1X=cx1+R1*sinA, T1Y=CY-R1*cosA;
+ const T2X=cx2+R2*sinA, T2Y=CY-R2*cosA;
+ const T3X=cx1+R1*sinA, T3Y=CY+R1*cosA;
+ const T4X=cx2+R2*sinA, T4Y=CY+R2*cosA;
+ svgWrap.innerHTML=``;
+ }
+ [r1Sl,r2Sl,dSl].forEach(s=>s.addEventListener('input',draw));
+ draw();
+ })();
+
+ /* === INIT 2: внутренняя касательная === */
+ (function(){
+ const r1Sl=document.getElementById('p7-ir1-sl'),r1v=document.getElementById('p7-ir1v');
+ const r2Sl=document.getElementById('p7-ir2-sl'),r2v=document.getElementById('p7-ir2v');
+ const dSl=document.getElementById('p7-id-sl'),dv=document.getElementById('p7-idv');
+ const svgWrap=document.getElementById('p7-int-svg'),infoEl=document.getElementById('p7-int-info');
+ const W=300,H=170,CY=85;
+ function draw(){
+ const R1=+r1Sl.value,R2=+r2Sl.value,d=+dSl.value;
+ r1v.textContent=R1;r2v.textContent=R2;dv.textContent=d;
+ const disc=d*d-(R1+R2)*(R1+R2);
+ const cx1=Math.max(R1+10,W/2-d/2), cx2=Math.min(W-R2-10,W/2+d/2);
+ if(disc<0){
+ infoEl.style.color='#92400e';infoEl.style.background='#fef3c7';
+ infoEl.textContent='Внутренняя касательная не существует (d='+d+' ≤ R₁+R₂='+(R1+R2)+')';
+ svgWrap.innerHTML=``;
+ return;
+ }
+ const L=Math.sqrt(disc);
+ infoEl.style.color='var(--sec-acc-d,#0f766e)';infoEl.style.background='';
+ infoEl.textContent='ℓ_внутр = √('+d+'²−('+R1+'+'+R2+')²) = '+L.toFixed(3);
+ const sinA=(R1+R2)/d, cosA=Math.sqrt(Math.max(0,1-sinA*sinA));
+ const T1X=cx1+R1*sinA, T1Y=CY-R1*cosA;
+ const T2X=cx2-R2*sinA, T2Y=CY+R2*cosA;
+ const T3X=cx1+R1*sinA, T3Y=CY+R1*cosA;
+ const T4X=cx2-R2*sinA, T4Y=CY-R2*cosA;
+ svgWrap.innerHTML=``;
+ }
+ [r1Sl,r2Sl,dSl].forEach(s=>s.addEventListener('input',draw));
+ draw();
+ })();
+
+ /* === INIT 3: пошаговое доказательство === */
+ (function(){
+ const O1X=65,O1Y=90,O2X=215,O2Y=90,R1=50,R2=28,d=150,W=280,H=160;
+ const sinA=(R1-R2)/d,cosA=Math.sqrt(1-sinA*sinA);
+ const T1X=O1X+R1*sinA,T1Y=O1Y-R1*cosA;
+ const T2X=O2X+R2*sinA,T2Y=O2Y-R2*cosA;
+ const KX=O1X+R1*sinA,KY=O2Y-R2*cosA;
+ const steps=[
+ {text:'Дано: окружности с центрами $O_1$, $O_2$, радиусами $R_1$, $R_2$. Расстояние $d=|O_1O_2|$. Общая внешняя касательная с точками касания $T_1$, $T_2$. Найти $|T_1T_2|$.'},
+ {text:'Шаг 1. $O_1T_1 \\perp \\ell$ и $O_2T_2 \\perp \\ell$ (свойство касательной). Опустим из $O_2$ перпендикуляр $O_2K$ на прямую $O_1T_1$. Получим прямоугольник $KT_1T_2O_2$.'},
+ {text:'Шаг 2. В прямоугольнике $KT_1T_2O_2$: $O_2K = T_1T_2 = \\ell$ (противоположные стороны).'},
+ {text:'Шаг 3. $O_1K = O_1T_1 - KT_1 = R_1 - R_2$ (разность радиусов).'},
+ {text:'Шаг 4. В прямоугольном $\\triangle O_1KO_2$: $d^2 = O_1K^2 + O_2K^2 = (R_1-R_2)^2 + \\ell^2$. Отсюда $\\ell = \\sqrt{d^2-(R_1-R_2)^2}$. ч.т.д.'},
+ ];
+ let step=0;
+ const svgEl=document.getElementById('p7-proof-svg'),txtEl=document.getElementById('p7-proof-text');
+ function draw(){
+ const s=steps[step];
+ let extras='';
+ if(step>=1){
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=``;
+ extras+=`K`;
+ extras+=``;
+ }
+ if(step>=3){
+ extras+=`R₁-R₂`;
+ extras+=`ℓ`;
+ }
+ svgEl.innerHTML=``;
+ txtEl.innerHTML=s.text;
+ if(window.renderMathInElement) try{renderMath(txtEl);}catch(e){}
+ document.getElementById('p7-proof-next').textContent=step>=steps.length-1?'Готово':'Далее';
+ }
+ document.getElementById('p7-proof-next').addEventListener('click',()=>{if(step{step=0;draw();});
+ draw();
+ })();
+
+ /* === INIT 4: калькулятор === */
+ (function(){
+ document.getElementById('p7-calc-btn').addEventListener('click',()=>{
+ const R1=parseFloat(document.getElementById('p7-cr1').value);
+ const R2=parseFloat(document.getElementById('p7-cr2').value);
+ const d=parseFloat(document.getElementById('p7-cd').value);
+ const out=document.getElementById('p7-calc-out');
+ out.style.display='block';
+ if(isNaN(R1)||isNaN(R2)||isNaN(d)||R1<=0||R2<=0||d<=0){out.style.background='#fee2e2';out.style.color='#7f1d1d';out.textContent='Введите положительные значения.';return;}
+ out.style.background='var(--sec-acc-soft,#cffafe)';out.style.color='var(--sec-acc-d,#0e7490)';
+ const extDisc=d*d-(R1-R2)*(R1-R2);
+ const intDisc=d*d-(R1+R2)*(R1+R2);
+ const extTxt=extDisc>=0?'ℓ_внеш = √('+d+'²−('+R1+'−'+R2+')²) = '+Math.sqrt(extDisc).toFixed(4):'Внешняя касательная не существует';
+ const intTxt=intDisc>0?'ℓ_внутр = √('+d+'²−('+R1+'+'+R2+')²) = '+Math.sqrt(intDisc).toFixed(4):'Внутренняя касательная не существует (d ≤ R₁+R₂='+(R1+R2)+')';
+ out.innerHTML=extTxt+'
'+intTxt;
+ });
+ })();
+
+ /* === INIT 5: тренажёр === */
+ (function(){
+ const tasks=[
+ {q:'$R_1=10$, $R_2=4$, $d=13$. Найди длину общей внешней касательной.',a:'12',hint:'ℓ=√(169−36)=√133≈11.53, округлённо. Пересчитай: (R₁−R₂)²=(10−4)²=36. ℓ=√(169−36)=√133≈11.53'},
+ {q:'$R_1=8$, $R_2=2$, $d=10$. Найди $\\ell_{\\text{вн}}$.',a:'8',hint:'ℓ=√(100−36)=√64=8'},
+ {q:'$R_1=R_2=5$, $d=13$. Найди $\\ell_{\\text{вн}}$.',a:'13',hint:'При R₁=R₂: ℓ=√(d²−0)=d=13'},
+ {q:'$\\ell_{\\text{вн}}=12$, $R_1-R_2=5$, найди $d$.',a:'13',hint:'d=√(144+25)=√169=13'},
+ {q:'$R_1=6$, $R_2=2$, $d=20$. Найди $\\ell_{\\text{внутр}}$.',a:'12',hint:'ℓ=√(400−(6+2)²)=√(400−64)=√336≈18.33. Нет, (R₁+R₂)²=64, √(400−64)=√336≈18.33'},
+ ];
+ let cur=0,score=0;
+ const iEl=document.getElementById('p7-tr-i'),scEl=document.getElementById('p7-tr-score');
+ const taskEl=document.getElementById('p7-tr-task'),ansEl=document.getElementById('p7-tr-ans');
+ const goBtn=document.getElementById('p7-tr-go'),startBtn=document.getElementById('p7-tr-start');
+ const fb=document.getElementById('p7-tr-fb');
+ const correctAnswers=[
+ Math.sqrt(169-36),8,13,13,Math.sqrt(400-64)
+ ];
+ function showTask(){
+ if(cur>=tasks.length){taskEl.innerHTML='Тренажёр завершён! Очки: '+score+'/'+tasks.length+'';ansEl.style.display='none';goBtn.style.display='none';addXp(score*4,'p7-trainer');bumpProgress('p7',20);return;}
+ taskEl.innerHTML=tasks[cur].q;iEl.textContent=cur+1;ansEl.value='';fb.style.display='none';
+ if(window.renderMathInElement) try{renderMath(taskEl);}catch(e){}
+ }
+ goBtn.addEventListener('click',()=>{
+ const ans=parseFloat(ansEl.value.replace(',','.'));
+ const ok=Math.abs(ans-correctAnswers[cur])<0.06;
+ feedback(fb,ok,ok?'Верно!':'Неверно. Подсказка: '+tasks[cur].hint+' ≈ '+correctAnswers[cur].toFixed(2));
+ if(ok){score++;scEl.textContent=score;cur++;setTimeout(showTask,900);}
+ });
+ startBtn.addEventListener('click',()=>{cur=0;score=0;scEl.textContent=0;ansEl.style.display='';goBtn.style.display='';showTask();});
+ showTask();
+ })();
+
+ /* === INIT 6: Босс §7 === */
+ (function(){
+ const tasks=[
+ {q:'$R_1=5$, $R_2=3$, $d=10$. Длина общей внешней касательной:',opts:['√(100−4)=√96≈9.8','√(100−64)=6','√(100−16)=√84≈9.2'],cor:0,exp:'$(R_1-R_2)^2=(5-3)^2=4$. $\\ell=\\sqrt{100-4}=\\sqrt{96}\\approx9{,}8$.'},
+ {q:'При $R_1=R_2$ длина внешней касательной равна:',opts:['$d$','$0$','$\\sqrt{d^2-R^2}$'],cor:0,exp:'Если $R_1=R_2$, то $(R_1-R_2)^2=0$, значит $\\ell=\\sqrt{d^2}=d$.'},
+ {q:'Внутренняя касательная существует при условии:',opts:['$d>R_1+R_2$','$d=R_1+R_2$','$d>|R_1-R_2|$'],cor:0,exp:'Внутренняя касательная существует только когда окружности не пересекаются и не касаются, т.е. $d > R_1+R_2$.'},
+ {q:'$\\ell_{\\text{вн}}=15$, $d=17$, $R_1=R_2$. Найти $R_1$.', opts:['4','8','2'],cor:1,exp:'$R_1=R_2$: $\\ell=d=17\\neq15$. Нет, если $R_1 \\neq R_2$: $\\ell^2=d^2-(R_1-R_2)^2$, $(R_1-R_2)^2=289-225=64$, $R_1-R_2=8$. Дополнительных данных нет — ответ: разность = 8.'},
+ ];
+ const cont=document.getElementById('p7-boss-tasks');
+ let html='';
+ tasks.forEach((t,i)=>{
+ html+=`
+
${i+1}. ${t.q}
+
+ ${t.opts.map((o,j)=>``).join('')}
+
+
+
`;
+ });
+ cont.innerHTML=html;
+ if(window.renderMathInElement) try{renderMath(cont);}catch(e){}
+ })();
+}
function buildP8stub(){ document.getElementById('p8-body').innerHTML='§8 — Волна 1: содержимое появится в следующем обновлении.
'+secNav('p7','p9'); }
function buildP9stub(){ document.getElementById('p9-body').innerHTML='§9 — Волна 1: содержимое появится в следующем обновлении.
'+secNav('p8','p10'); }
function buildP10stub(){ document.getElementById('p10-body').innerHTML='§10 — Волна 1: содержимое появится в следующем обновлении.
'+secNav('p9','p11'); }