diff --git a/frontend/textbooks/geometry_9_ch2.html b/frontend/textbooks/geometry_9_ch2.html
index 50689b7..9c420ad 100644
--- a/frontend/textbooks/geometry_9_ch2.html
+++ b/frontend/textbooks/geometry_9_ch2.html
@@ -1049,19 +1049,516 @@ function buildP8(){
wireReadBtn('p8');
}
-function buildP9(){ _stubBuilder('p9', '§9', 'Вписанные и описанные четырёхугольники', 'p8', 'final2'); }
-
-function buildFinal2(){
- const body = document.getElementById('final2-body');
+/* ===== §9 Вписанные и описанные четырёхугольники ===== */
+function buildP9(){
+ const box = document.getElementById('p9-body');
let html = '';
- html += makeCard('theory', 'Финал главы 2', '★', `
-
Итоговый раздел главы «Окружности» будет добавлен в следующих обновлениях.
- Раздел Phase 7.
`);
- html += readButton('final2');
+
+ html += makeCard('theory', 'Вписанный четырёхугольник', '9.1', `
+ Четырёхугольник вписан в окружность, если все его четыре вершины лежат на этой окружности.
+ Критерий вписанности: четырёхугольник вписан в окружность тогда и только тогда, когда сумма его противоположных углов равна $180°$:
+ $$\\alpha + \\gamma = 180°, \\qquad \\beta + \\delta = 180°$$
+ где $\\alpha, \\beta, \\gamma, \\delta$ — углы при последовательных вершинах $A, B, C, D$.
+ Какие фигуры всегда вписаны?
+ Прямоугольник всегда можно вписать в окружность: его диагональ — диаметр (углы по 90°, $90+90=180$). Произвольный параллелограмм — нельзя: его противоположные углы равны, и сумма $180°$ возможна только если все углы прямые — то есть только прямоугольник.
+
`);
+
+ html += makeCard('rule', 'Описанный четырёхугольник', '9.2', `
+ Четырёхугольник описан около окружности, если все его четыре стороны касаются окружности.
+ Критерий описанности: четырёхугольник описан около окружности тогда и только тогда, когда суммы его противоположных сторон равны:
+ $$a + c = b + d$$
+ где $a, b, c, d$ — последовательные стороны $AB, BC, CD, DA$.
+ Квадрат и ромб всегда описаны около окружности (центр — точка пересечения диагоналей). Произвольный прямоугольник — нет (только квадрат).
`);
+
+ html += makeCard('example', 'Свойства и примеры', '9.3', `
+
+ Квадрат: и вписан, и описан (правильный четырёхугольник).
+ Прямоугольник: вписан (диагональ = диаметр), описан только если квадрат.
+ Ромб: описан (диагонали — биссектрисы), вписан только если квадрат.
+ В произвольном вписанном четырёхугольнике: если $\\alpha = 80°$, то $\\gamma = 180° - 80° = 100°$.
+ В описанном со сторонами $3, 5, 7, d$: $3 + 7 = 5 + d \\Rightarrow d = 5$.
+ `);
+
+ /* IV1 — Вписанный четырёхугольник (SVG) */
+ html += ``;
+
+ /* IV2 — Калькулятор сторон описанного */
+ html += `
+
+
Введи три последовательные стороны описанного четырёхугольника $a, b, c$ — программа найдёт $d$ из условия $a + c = b + d$.
+
+ $a$ =
+
+ $b$ =
+
+ $c$ =
+
+ Найти $d$
+
+
+
+
`;
+
+ /* IV3 — «Вписан или описан?» */
+ html += `
+
+
Дана фигура — определи, можно ли её вписать в окружность, описать около окружности — или и то, и то.
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ Вписан
+ Описан
+ И то, и то
+
+
+
`;
+
+ /* IV4 — Тренажёр */
+ html += `
+
+
Реши задачу и введи число (целое или с десятичной точкой).
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ ответ =
+
+ Проверить
+ Заново
+
+
+
`;
+
+ html += secNav('p8', 'final2');
+ html += readButton('p9');
+
+ box.innerHTML = html;
+ renderMath(box);
+
+ /* IV1 — Вписанный четырёхугольник */
+ (function(){
+ const sA=document.getElementById('p9-iv1-a');
+ const sB=document.getElementById('p9-iv1-b');
+ const sC=document.getElementById('p9-iv1-c');
+ const sD=document.getElementById('p9-iv1-d');
+ const lA=document.getElementById('p9-iv1-aval');
+ const lB=document.getElementById('p9-iv1-bval');
+ const lC=document.getElementById('p9-iv1-cval');
+ const lD=document.getElementById('p9-iv1-dval');
+ const svg=document.getElementById('p9-iv1-svg');
+ const out=document.getElementById('p9-iv1-out');
+ const seen=new Set();
+ const cx=190, cy=190, R=140;
+ function pt(angDeg){
+ const a = deg2rad(angDeg);
+ return { x: cx + R*Math.cos(a), y: cy - R*Math.sin(a) };
+ }
+ function interiorAngle(prev, cur, next){
+ // Угол при cur между сторонами cur->prev и cur->next
+ const v1 = unitVec(cur, prev);
+ const v2 = unitVec(cur, next);
+ let cos = v1.x*v2.x + v1.y*v2.y;
+ if(cos>1) cos=1; if(cos<-1) cos=-1;
+ return Math.acos(cos) * 180 / Math.PI;
+ }
+ function draw(){
+ const aA=+sA.value, aB=+sB.value, aC=+sC.value, aD=+sD.value;
+ lA.textContent=aA; lB.textContent=aB; lC.textContent=aC; lD.textContent=aD;
+ const A=pt(aA), B=pt(aB), C=pt(aC), D=pt(aD);
+ const sides={
+ AB:Math.hypot(A.x-B.x,A.y-B.y),
+ BC:Math.hypot(B.x-C.x,B.y-C.y),
+ CD:Math.hypot(C.x-D.x,C.y-D.y),
+ DA:Math.hypot(D.x-A.x,D.y-A.y)
+ };
+ const angA=interiorAngle(D,A,B);
+ const angB=interiorAngle(A,B,C);
+ const angC=interiorAngle(B,C,D);
+ const angD=interiorAngle(C,D,A);
+
+ let s='';
+ s += ' ';
+ // Окружность
+ s += ' ';
+ // Центр
+ s += ' ';
+ s += 'O ';
+ // Четырёхугольник
+ s += ' ';
+ // Вершины
+ const labs=[{p:A,n:'A',ang:aA},{p:B,n:'B',ang:aB},{p:C,n:'C',ang:aC},{p:D,n:'D',ang:aD}];
+ labs.forEach(L=>{
+ s += ' ';
+ // подпись наружу
+ const ra=deg2rad(L.ang);
+ const lx=cx + (R+18)*Math.cos(ra);
+ const ly=cy - (R+18)*Math.sin(ra);
+ s += ''+L.n+' ';
+ });
+ svg.innerHTML=s;
+
+ const sumAC=angA+angC, sumBD=angB+angD;
+ const okAC=Math.abs(sumAC-180)<0.5;
+ const okBD=Math.abs(sumBD-180)<0.5;
+ let info = 'Углы: $\\alpha = '+angA.toFixed(1)+'°$, $\\beta = '+angB.toFixed(1)+'°$, $\\gamma = '+angC.toFixed(1)+'°$, $\\delta = '+angD.toFixed(1)+'°$ '
+ + '$\\alpha + \\gamma = '+sumAC.toFixed(1)+'°$ · $\\beta + \\delta = '+sumBD.toFixed(1)+'°$';
+ if(okAC && okBD) info += '✓ Это вписанный четырёхугольник! ';
+ out.innerHTML=info;
+ renderMath(out);
+ seen.add(aA+'|'+aB+'|'+aC+'|'+aD);
+ if(seen.size>=4 && !seen.has('done')){ addXp(10,'p9-iv1'); bumpProgress('p9',15); seen.add('done'); }
+ }
+ [sA,sB,sC,sD].forEach(s=>s.addEventListener('input', draw));
+ draw();
+ })();
+
+ /* IV2 — Калькулятор 4-й стороны */
+ (function(){
+ const aI=document.getElementById('p9-iv2-a');
+ const bI=document.getElementById('p9-iv2-b');
+ const cI=document.getElementById('p9-iv2-c');
+ const go=document.getElementById('p9-iv2-go');
+ const out=document.getElementById('p9-iv2-out');
+ const fb=document.getElementById('p9-iv2-fb');
+ let solved=0;
+ function calc(){
+ const a=parseFloat(aI.value), b=parseFloat(bI.value), c=parseFloat(cI.value);
+ if(!isFinite(a)||!isFinite(b)||!isFinite(c)){ feedback(fb,false,'✗ Введи три числа.'); return; }
+ if(a<=0||b<=0||c<=0){ feedback(fb,false,'✗ Стороны должны быть положительными.'); return; }
+ const d = a + c - b;
+ if(d<=0){
+ out.innerHTML = '$a + c = b + d \\Rightarrow d = a + c - b = '+a+' + '+c+' - '+b+' = '+fmt(d)+'$';
+ renderMath(out);
+ feedback(fb,false,'✗ Не существует описанного четырёхугольника с такими сторонами: $d \\le 0$.');
+ return;
+ }
+ out.innerHTML = '$a + c = b + d$ '
+ + '$d = a + c - b = '+a+' + '+c+' - '+b+' = '+fmt(d)+'$ '
+ + '4-я сторона: $d = '+fmt(d)+'$ ';
+ renderMath(out);
+ feedback(fb,true,'✓ Готово!');
+ solved++;
+ if(solved===1){ addXp(10,'p9-iv2'); bumpProgress('p9',15); }
+ }
+ go.addEventListener('click', calc);
+ })();
+
+ /* IV3 — Quickfire «Вписан или описан?» */
+ (function(){
+ const Q=[
+ {t:'Квадрат.', a:'both'},
+ {t:'Прямоугольник (не квадрат).', a:'in'},
+ {t:'Ромб (не квадрат).', a:'out'},
+ {t:'Трапеция, у которой суммы противоположных углов равны $180°$.', a:'in'},
+ {t:'Четырёхугольник со сторонами $3, 5, 7, 5$ (по порядку).', a:'out'},
+ {t:'Четырёхугольник с углами $90°, 80°, 90°, 100°$ (по порядку).', a:'in'}
+ ];
+ const explain={
+ in:'Вписан: $\\alpha + \\gamma = 180°$.',
+ out:'Описан: $a + c = b + d$.',
+ both:'И вписан, и описан (правильный четырёхугольник).'
+ };
+ const qBox=document.getElementById('p9-iv3-q');
+ const iEl=document.getElementById('p9-iv3-i');
+ const sEl=document.getElementById('p9-iv3-s');
+ const fb=document.getElementById('p9-iv3-fb');
+ const btns=document.querySelectorAll('#p9-iv3 button[data-ans]');
+ let i=0, score=0, done=false;
+ function show(){
+ if(i>=Q.length){
+ qBox.innerHTML='Завершено! Очки: '+score+' / '+Q.length;
+ if(!done){ done=true; addXp(15,'p9-iv3'); bumpProgress('p9',25); if(score===Q.length) achievement('p9_done'); }
+ btns.forEach(b=>b.disabled=true);
+ return;
+ }
+ qBox.innerHTML=Q[i].t; renderMath(qBox);
+ iEl.textContent=(i+1); sEl.textContent=score;
+ fb.style.display='none';
+ }
+ btns.forEach(b=>b.addEventListener('click', ()=>{
+ const ok = b.dataset.ans===Q[i].a;
+ if(ok) score++;
+ feedback(fb, ok, ok?('✓ Верно! '+explain[Q[i].a]):('✗ Правильный ответ: '+({in:'Вписан',out:'Описан',both:'И то, и то'}[Q[i].a])+'. '+explain[Q[i].a]));
+ i++;
+ setTimeout(show, 900);
+ }));
+ show();
+ })();
+
+ /* IV4 — Тренажёр */
+ (function(){
+ const Q=[
+ {t:'Во вписанном 4-уг. углы $\\angle A = 75°$, $\\angle B = 110°$. Найди $\\angle C$.', a:105},
+ {t:'Во вписанном 4-уг. углы $\\angle A = 90°$, $\\angle B = 95°$. Найди $\\angle D$.', a:85},
+ {t:'В описанном 4-уг. стороны $a=4$, $b=6$, $c=8$. Найди $d$.', a:6},
+ {t:'В описанном 4-уг. стороны $a=5$, $b=7$, $c=9$. Найди $d$.', a:7},
+ {t:'Сумма противоположных углов вписанного четырёхугольника равна …', a:180},
+ {t:'Параллелограмм вписан в окружность. Чему равны его углы (в градусах)?', a:90}
+ ];
+ const qBox=document.getElementById('p9-iv4-q');
+ const ans=document.getElementById('p9-iv4-ans');
+ const go=document.getElementById('p9-iv4-go');
+ const reset=document.getElementById('p9-iv4-start');
+ const iEl=document.getElementById('p9-iv4-i');
+ const sEl=document.getElementById('p9-iv4-s');
+ const fb=document.getElementById('p9-iv4-fb');
+ let i=0, score=0, done=false;
+ function show(){
+ if(i>=Q.length){
+ qBox.innerHTML='Финиш! Очки: '+score+' / '+Q.length;
+ if(!done){ done=true; addXp(15,'p9-iv4'); bumpProgress('p9',25); }
+ go.disabled=true; ans.disabled=true;
+ return;
+ }
+ qBox.innerHTML=Q[i].t; renderMath(qBox);
+ iEl.textContent=(i+1); sEl.textContent=score;
+ ans.value=''; fb.style.display='none'; go.disabled=false; ans.disabled=false;
+ }
+ go.addEventListener('click', ()=>{
+ const v=parseFloat((ans.value||'').replace(',', '.'));
+ if(!isFinite(v)){ feedback(fb,false,'✗ Введи число.'); return; }
+ const ok=Math.abs(v-Q[i].a)<0.05;
+ if(ok) score++;
+ feedback(fb, ok, ok?'✓ Верно!':'✗ Правильный ответ: $'+Q[i].a+'$');
+ i++;
+ setTimeout(show, 900);
+ });
+ reset.addEventListener('click', ()=>{ i=0; score=0; done=false; show(); });
+ show();
+ })();
+
+ wireReadBtn('p9');
+}
+
+/* ===== Финал главы 2 ===== */
+function buildFinal2(){
+ const box = document.getElementById('final2-body');
+ let html = '';
+
+ /* Часть А — Шпаргалка главы (3 mini-карточки) */
+ html += `
+
+
+
Ключевые формулы и критерии главы «Окружности» — в одном месте. Просмотри перед боссами!
+
+
+
+
+
§ 7 · Окружности треугольника
+
+
Описанная — пересечение серединных $\\perp$, $R = \\dfrac{abc}{4S}$. Вписанная — пересечение биссектрис, $r = \\dfrac{S}{p}$.
+
+
+
+
+
§ 8 · Прямоугольный $\\triangle$
+
+
$R = \\dfrac{c}{2}$ — центр на середине гипотенузы. $r = \\dfrac{a + b - c}{2}$.
+
+
+
+
+
§ 9 · Четырёхугольники
+
+
Вписан $\\Leftrightarrow$ $\\alpha + \\gamma = 180°$. Описан $\\Leftrightarrow$ $a + c = b + d$.
+
+
+
+
`;
+
+ /* Часть Б — 5 боссов */
+ html += `
+
+
+
5 интегрированных задач — каждая комбинирует темы §§7–9. За каждого побеждённого босса — +10 XP и +18% к прогрессу. Победишь всех — +50 XP бонус и ачивка «Магистр окружностей»!
+
+
`;
+
+ html += '
';
+
+ html += `
+
Прогресс по боссам
+
0 / 5 боссов побеждено
+
+
+
+
+
Магистр окружностей
+
+
Глава 2 пройдена! Все 5 боссов повержены. +50 XP бонус.
+
Дальше: Глава 3
+
+
`;
+
html += secNav('p9', null);
- body.innerHTML = html;
- wireReadBtn('final2');
- if(window.renderMathInElement) renderMath(body);
+
+ box.innerHTML = html;
+ renderMath(box);
+
+ /* Боссы */
+ const BOSSES = [
+ {
+ n:1, color:'#10b981',
+ title:'Циклоп Окружностей',
+ tag:'§ 8 + § 7',
+ q:'В прямоугольном треугольнике катеты $a = 6$ и $b = 8$. Найди радиус описанной окружности.',
+ ans:5, decimal:false,
+ hint:'Гипотенуза $c = \\sqrt{6^2 + 8^2} = 10$. $R = \\dfrac{c}{2} = 5$.'
+ },
+ {
+ n:2, color:'#0891b2',
+ title:'Минотавр Вписанной',
+ tag:'§ 8 + § 7',
+ q:'В прямоугольном треугольнике катеты $a = 9$ и $b = 12$. Найди радиус вписанной окружности.',
+ ans:3, decimal:false,
+ hint:'Гипотенуза $c = \\sqrt{81+144} = 15$. $r = \\dfrac{a + b - c}{2} = \\dfrac{9 + 12 - 15}{2} = 3$.'
+ },
+ {
+ n:3, color:'#7c3aed',
+ title:'Гарпия 4-угольников',
+ tag:'§ 9',
+ q:'Во вписанном четырёхугольнике углы $\\angle A = 65°$ и $\\angle B = 95°$. Найди сумму $\\angle C + \\angle D$ в градусах.',
+ ans:200, decimal:false,
+ hint:'$\\angle C = 180° - \\angle A = 115°$, $\\angle D = 180° - \\angle B = 85°$. Сумма $= 115 + 85 = 200°$.'
+ },
+ {
+ n:4, color:'#dc2626',
+ title:'Дракон Окружности',
+ tag:'§ 7 + § 9',
+ q:'В равностороннем треугольнике со стороной $a = 6$ проведена описанная окружность. Найди её радиус $R$ (округли до 2 знаков).',
+ ans:3.46, decimal:true,
+ hint:'Для равностороннего $R = \\dfrac{a}{\\sqrt{3}} = \\dfrac{6}{\\sqrt{3}} = 2\\sqrt{3} \\approx 3{,}46$.'
+ },
+ {
+ n:5, color:'#f59e0b',
+ title:'Мастер Окружностей',
+ tag:'§§ 7-9 — синтез',
+ q:'В описанном четырёхугольнике стороны последовательно равны $5, 7, x, 4$. Найди $x$.',
+ ans:6, decimal:false,
+ hint:'$a + c = b + d \\Rightarrow 5 + x = 7 + 4 \\Rightarrow x = 6$.'
+ }
+ ];
+
+ const cont = document.getElementById('ch2G-bosses-container');
+ const STATE_KEY = 'geometry9_ch2_bosses';
+ const BOSS_STATE = (function(){
+ try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }catch(e){}
+ return BOSSES.map(()=>({defeated:false}));
+ })();
+ function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
+
+ cont.innerHTML = BOSSES.map((b)=>{
+ const stepAttr = b.decimal ? 'step="0.01"' : 'step="1"';
+ const ph = b.decimal ? 'число (можно с запятой)' : 'целое число';
+ return ''
+ +'
'
+ +'
'
+ +'
Босс '+b.n+': '+b.title+'
'
+ +'
'+b.tag+'
'
+ +'
'
+ +'
'+b.q+'
'
+ +'
'
+ +'ответ = '
+ +' '
+ +'Атаковать '
+ +'Подсказка '
+ +'
'
+ +'
'
+ +'
';
+ }).join('');
+ renderMath(cont);
+
+ function markDefeatedUI(b){
+ const card = document.getElementById('bossG2-'+b.n+'-card');
+ const goBtn = document.getElementById('bossG2-'+b.n+'-go');
+ const ansInp = document.getElementById('bossG2-'+b.n+'-ans');
+ if(!card || !goBtn || !ansInp) return;
+ card.style.background = 'linear-gradient(135deg,var(--acc-soft),var(--pri-soft))';
+ card.style.boxShadow = '0 0 0 2px '+b.color+'33, 0 8px 24px rgba(16,185,129,.12)';
+ goBtn.disabled = true; goBtn.style.opacity = .55;
+ goBtn.innerHTML = ' Повержен';
+ ansInp.disabled = true;
+ }
+
+ function refreshOverall(){
+ const won = BOSS_STATE.filter(s => s.defeated).length;
+ const txt = document.getElementById('ch2G-boss-overall');
+ const fill = document.getElementById('ch2G-boss-overall-fill');
+ if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено';
+ if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%';
+ if(won >= BOSSES.length){
+ const reward = document.getElementById('ch2G-final-reward');
+ if(reward && reward.style.display === 'none'){
+ reward.style.display = 'block';
+ if(!STATE.achievements.has('ch2_done')){
+ achievement('ch2_done','Магистр окружностей');
+ addXp(50, 'ch2-bonus');
+ bumpProgress('final2', 10);
+ if(window.confetti){ try{ window.confetti(); }catch(e){} }
+ }
+ }
+ }
+ }
+
+ BOSSES.forEach((b, idx)=>{
+ const goBtn = document.getElementById('bossG2-'+b.n+'-go');
+ const hintBtn = document.getElementById('bossG2-'+b.n+'-hint');
+ const ansInp = document.getElementById('bossG2-'+b.n+'-ans');
+ if(BOSS_STATE[idx].defeated) markDefeatedUI(b);
+ goBtn.addEventListener('click', ()=>{
+ if(BOSS_STATE[idx].defeated) return;
+ const fb = document.getElementById('bossG2-'+b.n+'-fb');
+ const raw = (ansInp.value||'').replace(',', '.').trim();
+ const val = parseFloat(raw);
+ if(isNaN(val) || raw === ''){ feedback(fb, false, '✗ Введи число.'); return; }
+ const ok = Math.abs(val - b.ans) < 0.05;
+ if(ok){
+ BOSS_STATE[idx].defeated = true; saveBosses();
+ feedback(fb, true, '✓ Босс '+b.n+' повержен! +10 XP. '+b.hint);
+ addXp(10, 'boss-ch2-'+b.n);
+ bumpProgress('final2', 18);
+ markDefeatedUI(b);
+ refreshOverall();
+ } else {
+ feedback(fb, false, '✗ Промах. Попробуй ещё. Подсказка доступна.');
+ }
+ });
+ hintBtn.addEventListener('click', ()=>{
+ const fb = document.getElementById('bossG2-'+b.n+'-fb');
+ fb.className = 'feedback ok';
+ fb.innerHTML = 'Подсказка: '+b.hint;
+ fb.style.display = 'block';
+ fb.style.background = 'var(--warn-bg)';
+ fb.style.color = '#92400e';
+ fb.style.borderLeftColor = 'var(--warn)';
+ renderMath(fb);
+ });
+ ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
+ });
+
+ refreshOverall();
}
/* ===== Search ===== */