fix(geom7 ch5): переделаны рисунки §27 и §31 + KaTeX-ошибки

- KaTeX:
  • PARAS p29/p30: убрана математика из psel-карточек
    ($M$ → M, $\perp$ → ⊥), т.к. psel не рендерил KaTeX.
  • Boss "\§29-30" title: $\perp$ → ⊥ (boss-title не рендерился).
  • Защитно добавлен renderMath(g) после buildParaSelector
    и renderMath(cont) после вставки boss-карточек.

- §27 SVG: чистая 2-панельная схема с разделителем.
  Слева: ЛИНЕЙКА (корпус с штрихами без цифр) → ↓ →
  пример (точки A, B + прямая).
  Справа: ЦИРКУЛЬ (шарнир + игла + грифель) → ↓ →
  пример (окружность с центром O и радиусом r).

- §31 SVG: пересчитанные координаты, чёткие плашки-подписи
  ГМТ 1 (биссектриса, красная) и ГМТ 2 (окружность, синяя).
  Точки K₁, K₂ — крупные зелёные с белой обводкой.
  Дуги показывают, что биссектриса делит угол ровно пополам.
This commit is contained in:
Maxim Dolgolyov
2026-05-29 09:42:05 +03:00
parent 7546fe0553
commit 79aaf27b7f
+84 -41
View File
@@ -317,8 +317,8 @@ function achievement(id,text){
const PARAS = [
{ id:'p27', num:'§ 27', name:'Простейшие построения', sub:'циркуль + линейка' },
{ id:'p28', num:'§ 28', name:'Треугольник по 3 сторонам', sub:'отрезок + 2 окружн.' },
{ id:'p29', num:'§ 29', name:'Биссектриса угла', sub:'3 дуги + точка $M$' },
{ id:'p30', num:'§ 30', name:'Середина и $\\perp$', sub:'2 окружн. → 2 точки' },
{ id:'p29', num:'§ 29', name:'Биссектриса угла', sub:'3 дуги + точка M' },
{ id:'p30', num:'§ 30', name:'Середина и ', sub:'2 окружн. → 2 точки' },
{ id:'p31', num:'§ 31', name:'Метод ГМТ', sub:'2 ГМТ → пересечение' },
{ id:'final5', num:'★', name:'Финал главы', sub:'Итоги \xB7 5 боссов', final:true },
];
@@ -333,6 +333,7 @@ function buildParaSelector(){
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
if(window.renderMathInElement) try{ renderMath(g); }catch(e){}
}
const BUILT=new Set();
@@ -540,23 +541,47 @@ function buildP27(){
let svgTools='';
if(G){
const b=G.svgBox(320,160,{id:'p27-tools',cell:20});
svgTools = b.open
/* Линейка слева */
+ '<rect x="20" y="40" width="120" height="22" fill="#fbcfe8" stroke="#be185d" stroke-width="1.5" rx="3"/>'
+ '<text x="80" y="56" text-anchor="middle" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#be185d">ЛИНЕЙКА</text>'
+ '<text x="80" y="82" text-anchor="middle" font-size="9" fill="#7c2d52">↑ прямая через 2 точки</text>'
+ G.point(30,120,'',{r:3,color:'#1e293b'})
+ G.point(130,128,'',{r:3,color:'#1e293b'})
+ G.segment({x:30,y:120},{x:130,y:128},{color:'#0891b2',width:2})
/* Циркуль справа: схематично */
+ '<line x1="220" y1="30" x2="250" y2="80" stroke="#be185d" stroke-width="2.5"/>'
+ '<line x1="280" y1="30" x2="250" y2="80" stroke="#be185d" stroke-width="2.5"/>'
+ '<circle cx="250" cy="80" r="3" fill="#be185d"/>'
+ '<text x="250" y="22" text-anchor="middle" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#be185d">ЦИРКУЛЬ</text>'
+ G.circle({x:250,y:108},32,{color:'#0891b2',width:1.5,dash:'3 2'})
+ '<text x="250" y="155" text-anchor="middle" font-size="9" fill="#7c2d52">↑ окружность</text>'
/* Две панели: ЛИНЕЙКА слева, ЦИРКУЛЬ справа.
В каждой панели: инструмент сверху → стрелка ↓ → результат снизу. */
const b=G.svgBox(340,210,{id:'p27-tools',cell:0,grid:false,bg:'#fff'});
/* Вертикальный разделитель */
let s=b.open
+ '<line x1="170" y1="15" x2="170" y2="195" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="3 4"/>'
/* ===== ЛЕВАЯ ПАНЕЛЬ — ЛИНЕЙКА ===== */
+ '<text x="85" y="25" text-anchor="middle" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="#be185d">ЛИНЕЙКА</text>'
/* Корпус линейки */
+ '<rect x="22" y="33" width="126" height="20" fill="#fbcfe8" stroke="#be185d" stroke-width="1.8" rx="2"/>';
/* Маленькие штрихи на верхнем крае линейки (без чисел) */
for(let i=0;i<=9;i++){ const x=22+i*14; s+='<line x1="'+x+'" y1="33" x2="'+x+'" y2="'+(33+(i%2===0?7:4))+'" stroke="#be185d" stroke-width="1"/>'; }
s += '<text x="85" y="70" text-anchor="middle" font-size="8" fill="#7c2d52">(без делений)</text>'
/* Стрелка ↓ */
+ '<path d="M 85 80 L 85 100 M 80 95 L 85 100 L 90 95" fill="none" stroke="#10b981" stroke-width="2.2" stroke-linecap="round"/>'
/* Результат: 2 точки и прямая через них */
+ '<line x1="30" y1="148" x2="140" y2="158" stroke="#0891b2" stroke-width="2.2" stroke-linecap="round"/>'
+ G.point(30,148,'A',{color:'#1e293b',dx:-12,dy:-2,fontSize:11,r:3.5})
+ G.point(140,158,'B',{color:'#1e293b',dx:6,dy:-2,fontSize:11,r:3.5})
+ '<text x="85" y="188" text-anchor="middle" font-size="10" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#0891b2">прямая через 2 точки</text>'
/* ===== ПРАВАЯ ПАНЕЛЬ — ЦИРКУЛЬ ===== */
+ '<text x="255" y="25" text-anchor="middle" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="#be185d">ЦИРКУЛЬ</text>'
/* Две ноги от шарнира вниз */
+ '<line x1="255" y1="33" x2="228" y2="78" stroke="#be185d" stroke-width="3" stroke-linecap="round"/>'
+ '<line x1="255" y1="33" x2="282" y2="78" stroke="#be185d" stroke-width="3" stroke-linecap="round"/>'
/* Шарнир */
+ '<circle cx="255" cy="33" r="3.5" fill="#fbcfe8" stroke="#be185d" stroke-width="1.5"/>'
/* Игла (треугольник) на левой ноге */
+ '<polygon points="228,78 224,85 232,85" fill="#dc2626"/>'
/* Грифель на правой ноге */
+ '<polygon points="282,78 278,85 286,85" fill="#0f172a"/>'
/* Стрелка ↓ */
+ '<path d="M 255 92 L 255 110 M 250 105 L 255 110 L 260 105" fill="none" stroke="#10b981" stroke-width="2.2" stroke-linecap="round"/>'
/* Результат: окружность с центром O и пунктирным радиусом */
+ '<circle cx="255" cy="148" r="26" fill="none" stroke="#7c3aed" stroke-width="2"/>'
+ '<line x1="255" y1="148" x2="273" y2="129" stroke="#7c3aed" stroke-width="1.3" stroke-dasharray="3 2"/>'
+ '<text x="265" y="138" font-size="9" fill="#6d28d9" font-weight="700">r</text>'
+ G.point(255,148,'O',{color:'#1e293b',dx:-10,dy:-2,fontSize:11,r:3})
+ '<text x="255" y="188" text-anchor="middle" font-size="10" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">окружность (O, r)</text>'
+ b.close;
svgTools = s;
}
html += makeCard('theory', 'Что такое задача на построение?', '27.1', `
@@ -1044,34 +1069,51 @@ function buildP31(){
let svgGMT='';
if(G){
const b=G.svgBox(300,200,{id:'p31-gmt',cell:20});
const O={x:50,y:120};
const ang=25*Math.PI/180;
const len=240;
/* Чистый рисунок: угол ∠AOB слева, биссектриса (ГМТ 1, красная)
и окружность (ГМТ 2, синяя) пересекаются в двух точках K₁, K₂. */
const b=G.svgBox(320,230,{id:'p31-gmt',cell:20});
const O={x:50,y:135};
const ang=20*Math.PI/180; /* половина раствора угла */
const len=220;
const Aend={x:O.x+len*Math.cos(-ang), y:O.y+len*Math.sin(-ang)};
const Bend={x:O.x+len*Math.cos(ang), y:O.y+len*Math.sin(ang)};
/* Биссектриса — ось x от O */
/* Окружность с центром (170,80) радиуса 50.
Пересечение биссектрисы (y=120) с этой окружностью:
(x-170)² + (120-80)² = 2500 → (x-170)² = 900 → x = 140 или 200 */
const Cc={x:170,y:80}, R=50;
const K1={x:140,y:120}, K2={x:200,y:120};
/* Окружность (ГМТ 2): центр Cc, радиус R.
Биссектриса — горизонталь y=O.y от O.
Пересечение: (x-Cc.x)² + (O.y-Cc.y)² = R² → x = Cc.x ± √(R² - dy²) */
const Cc={x:180,y:90}, R=55;
const dy = O.y - Cc.y;
const dx = Math.sqrt(R*R - dy*dy);
const K1={x:Cc.x-dx, y:O.y}, K2={x:Cc.x+dx, y:O.y};
/* Конец биссектрисы — заходит чуть за K2 */
const Bs={x:O.x+len,y:O.y};
svgGMT = b.open
/* Стороны угла */
+ G.segment(O,Aend,{color:'#7c3aed',width:2.5})
+ G.segment(O,Bend,{color:'#7c3aed',width:2.5})
/* ГМТ 1: биссектриса */
+ G.segment(O,{x:O.x+len,y:O.y},{color:'#dc2626',width:2,dash:'6 3'})
/* ГМТ 2: окружность */
+ G.circle(Cc,R,{color:'#0891b2',width:1.8,dash:'4 3'})
/* Точки пересечения */
+ G.point(K1.x,K1.y,'K₁',{color:'#059669',dx:-14,dy:-8,fontSize:12,r:4})
+ G.point(K2.x,K2.y,'K₂',{color:'#059669',dx:6,dy:-8,fontSize:12,r:4})
+ G.point(O.x,O.y,'O',{color:'#1e293b',dx:-14,dy:5,fontSize:12})
+ G.point(Cc.x,Cc.y,'C',{color:'#0891b2',dx:6,dy:-6,fontSize:12,r:3})
+ '<text x="'+(O.x+90)+'" y="'+(O.y-6)+'" font-size="10" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#dc2626">ГМТ 1: бис.</text>'
+ '<text x="'+(Cc.x-30)+'" y="'+(Cc.y-R-6)+'" font-size="10" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#0891b2">ГМТ 2: окружн.</text>'
+ '<text x="170" y="190" text-anchor="middle" font-size="11" font-weight="700" fill="#059669">K₁, K₂ = ГМТ 1 ∩ ГМТ 2</text>'
+ G.point(O.x,O.y,'O',{color:'#1e293b',dx:-14,dy:5,fontSize:13})
+ '<text x="'+(Aend.x+3)+'" y="'+(Aend.y-1)+'" font-size="12" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">A</text>'
+ '<text x="'+(Bend.x+3)+'" y="'+(Bend.y+12)+'" font-size="12" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">B</text>'
/* Маленькие дуги показывают, что биссектриса делит угол пополам */
+ G.arc(O,18,-ang,0,{color:'#dc2626',width:1.4})
+ G.arc(O,18,0,ang,{color:'#dc2626',width:1.4})
/* ГМТ 1 — биссектриса (красная пунктирная) */
+ G.segment(O,Bs,{color:'#dc2626',width:2.2,dash:'7 4'})
/* ГМТ 2 — окружность (синяя пунктирная) */
+ G.circle(Cc,R,{color:'#0891b2',width:2,dash:'5 3'})
+ G.point(Cc.x,Cc.y,'C',{color:'#0891b2',dx:6,dy:-6,fontSize:11,r:3})
/* Точки пересечения K₁, K₂ — крупные зелёные с обводкой */
+ '<circle cx="'+K1.x+'" cy="'+K1.y+'" r="5.5" fill="#10b981" stroke="#fff" stroke-width="2"/>'
+ '<circle cx="'+K2.x+'" cy="'+K2.y+'" r="5.5" fill="#10b981" stroke="#fff" stroke-width="2"/>'
+ '<text x="'+(K1.x-5)+'" y="'+(K1.y+25)+'" text-anchor="middle" font-size="13" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="#047857">K₁</text>'
+ '<text x="'+(K2.x+5)+'" y="'+(K2.y+25)+'" text-anchor="middle" font-size="13" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="#047857">K₂</text>'
/* Подпись ГМТ 1 (плашка справа от биссектрисы, за K₂) */
+ '<rect x="'+(Bs.x-65)+'" y="'+(Bs.y-11)+'" width="62" height="20" rx="4" fill="#fee2e2" stroke="#dc2626" stroke-width="1"/>'
+ '<text x="'+(Bs.x-34)+'" y="'+(Bs.y+3)+'" text-anchor="middle" font-size="10" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="#dc2626">ГМТ 1: бис.</text>'
/* Подпись ГМТ 2 (плашка сверху над окружностью) */
+ '<rect x="'+(Cc.x-45)+'" y="'+(Cc.y-R-22)+'" width="90" height="20" rx="4" fill="#cffafe" stroke="#0891b2" stroke-width="1"/>'
+ '<text x="'+Cc.x+'" y="'+(Cc.y-R-8)+'" text-anchor="middle" font-size="10" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="#0891b2">ГМТ 2: окружн.</text>'
/* Итог снизу */
+ '<text x="160" y="218" text-anchor="middle" font-size="11" font-weight="800" fill="#047857">K₁, K₂ = ГМТ 1 ∩ ГМТ 2</text>'
+ b.close;
}
@@ -1203,7 +1245,7 @@ const BOSSES = [
]
},
{
n:3, title:'Босс \xA729-30 — Биссектриса, середина, $\\perp$', color:'#7c3aed',
n:3, title:'Босс \xA729-30 — Биссектриса, середина, ', color:'#7c3aed',
steps:[
{ q:'По какому признаку доказываем равенство $\\triangle OXM = \\triangle OYM$ в построении бис.? «ССС», «СУС», «УСУ»', verify:(v)=>String(v).trim().toUpperCase().startsWith('ССС'), hint:'ССС — три стороны.' },
{ q:'Сколько окружностей в построении серединного $\\perp$?', verify:(v)=>+v===2, hint:'Из $A$ и $B$.' },
@@ -1288,6 +1330,7 @@ function buildFinal5(){
+'<div class="feedback" id="boss-'+idx+'-fb"></div>'
+'</div>';
}).join('');
if(window.renderMathInElement) try{ renderMath(cont); }catch(e){}
BOSSES.forEach((b,idx)=>{
function show(){