feat(geom9 ch3 wave1): §10 «Теорема синусов» + §11 «Теорема косинусов»

This commit is contained in:
Maxim Dolgolyov
2026-05-29 10:08:34 +03:00
parent 948b831273
commit 8cb461827c
+617 -2
View File
@@ -103,6 +103,27 @@ a{color:inherit;text-decoration:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)} .feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)} .feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.wg{background:linear-gradient(135deg,var(--card),var(--pri-soft));border:1.5px solid var(--pri);border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--pri);color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--pri2);flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border-left:4px solid var(--warn);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
.tinp:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--pri2);margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--pri)}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--pri-soft);border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--pri2);font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--pri-soft);font-weight:700;cursor:pointer;font-size:.88rem;color:var(--pri2);list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--pri);width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto} .col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)} .sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)} .sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
@@ -420,6 +441,28 @@ function wireReadBtn(paraId){
}); });
} }
/* ===== SVG helpers ===== */
function rightAngleMark(V, uIn, wIn, s){
s = s || 9;
const p1 = {x: V.x + s*uIn.x, y: V.y + s*uIn.y};
const c = {x: p1.x + s*wIn.x, y: p1.y + s*wIn.y};
const p2 = {x: V.x + s*wIn.x, y: V.y + s*wIn.y};
return p1.x+','+p1.y+' '+c.x+','+c.y+' '+p2.x+','+p2.y;
}
function angleArcAuto(V, uA, uB, R){
const sA = {x: V.x + R*uA.x, y: V.y + R*uA.y};
const eB = {x: V.x + R*uB.x, y: V.y + R*uB.y};
const cross = uA.x*uB.y - uA.y*uB.x;
const sweep = cross > 0 ? 1 : 0;
return 'M'+sA.x+','+sA.y+' A'+R+','+R+' 0 0,'+sweep+' '+eB.x+','+eB.y;
}
function unitVec(p1, p2){
const dx = p2.x - p1.x, dy = p2.y - p1.y;
const len = Math.sqrt(dx*dx + dy*dy) || 1;
return {x: dx/len, y: dy/len};
}
function deg2rad(d){ return d * Math.PI / 180; }
/* ===== STUB BUILDERS — наполнение в Phase 7+ ===== */ /* ===== STUB BUILDERS — наполнение в Phase 7+ ===== */
function _stubBuilder(paraId, num, name, prev, next){ function _stubBuilder(paraId, num, name, prev, next){
@@ -435,8 +478,580 @@ function _stubBuilder(paraId, num, name, prev, next){
if(window.renderMathInElement) renderMath(body); if(window.renderMathInElement) renderMath(body);
} }
function buildP10(){ _stubBuilder('p10', '§10', 'Теорема синусов', null, 'p11'); } /* ===== §10 — Теорема синусов ===== */
function buildP11(){ _stubBuilder('p11', '§11', 'Теорема косинусов', 'p10', 'p12'); } function buildP10(){
const box = document.getElementById('p10-body');
let html = '';
html += makeCard('theory', 'Формулировка теоремы', '10.1', `
<p>В любом треугольнике стороны пропорциональны синусам противолежащих углов:</p>
$$\\dfrac{a}{\\sin A} = \\dfrac{b}{\\sin B} = \\dfrac{c}{\\sin C} = 2R$$
<p>где $a, b, c$ — стороны треугольника, $A, B, C$ — противолежащие им углы, а $R$ — радиус описанной окружности.</p>
<details class="spoiler"><summary>Почему именно $2R$?</summary><div class="spoiler-body">
Идея доказательства: в треугольнике, вписанном в окружность радиуса $R$, сторона $a$ — это хорда, стягиваемая центральным углом $2A$ (по теореме о вписанном угле). Длина такой хорды равна $2R \\sin A$, откуда $\\dfrac{a}{\\sin A} = 2R$. То же для $b$ и $c$.
</div></details>`);
html += makeCard('rule', 'Радиус описанной окружности', '10.2', `
<p>Из теоремы синусов сразу получаем формулы для радиуса описанной окружности:</p>
$$R = \\dfrac{a}{2 \\sin A} = \\dfrac{b}{2 \\sin B} = \\dfrac{c}{2 \\sin C} = \\dfrac{abc}{4S}$$
<p>А также — выражение стороны через радиус и противолежащий угол:</p>
$$a = 2R \\sin A, \\qquad b = 2R \\sin B, \\qquad c = 2R \\sin C$$
<p><b>Это очень удобно:</b> зная всего один угол и противолежащую ему сторону, можно сразу найти $R$ — а через него и любую другую сторону.</p>`);
html += makeCard('example', 'Применение', '10.3', `
<p><b>Что можно вычислить?</b></p>
<ul style="padding-left:22px;line-height:1.95">
<li>Найти $b$, зная $a, A, B$: $b = a \\cdot \\dfrac{\\sin B}{\\sin A}$.</li>
<li>Найти $R$, зная $a$ и $A$: $R = \\dfrac{a}{2 \\sin A}$.</li>
<li>Найти угол $B$, зная $a, b, A$: $\\sin B = \\dfrac{b \\sin A}{a}$.</li>
</ul>
<p><b>Пример.</b> $a = 10$, $A = 30°$, $B = 45°$.</p>
$$b = a \\cdot \\dfrac{\\sin B}{\\sin A} = 10 \\cdot \\dfrac{\\sin 45°}{\\sin 30°} = 10 \\cdot \\dfrac{\\sqrt{2}/2}{1/2} = 10\\sqrt{2} \\approx 14{,}14$$
<p>Радиус описанной окружности: $R = \\dfrac{a}{2 \\sin A} = \\dfrac{10}{2 \\cdot 0{,}5} = 10$.</p>`);
/* IV1 — Треугольник с описанной окружностью */
html += `<div class="wg" id="p10-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Треугольник в описанной окружности</div></div>
<div class="wg-help">Меняй углы $A$ и $B$ — программа найдёт $C = 180° - A - B$ и стороны $a = 2R\\sin A$, $b = 2R\\sin B$, $c = 2R\\sin C$. Радиус $R = 130$ фиксирован.</div>
<div class="sliders">
<label>$A$ (°)<b id="p10-iv1-aval">60</b><input type="range" id="p10-iv1-a" min="20" max="140" step="1" value="60"></label>
<label>$B$ (°)<b id="p10-iv1-bval">50</b><input type="range" id="p10-iv1-b" min="20" max="140" step="1" value="50"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p10-iv1-svg" viewBox="0 0 420 400" style="width:100%;min-width:340px;height:auto;display:block"></svg>
</div>
<div id="p10-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--pri-soft);border-radius:9px;font-size:.95rem;text-align:center;line-height:1.9"></div>
</div>`;
/* IV2 — Калькулятор */
html += `<div class="wg" id="p10-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $R$ и сторон</div></div>
<div class="wg-help">Введи сторону $a$ и два угла $A$, $B$ — программа найдёт $C$, $R$, $b$ и $c$ по теореме синусов.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.85rem;color:var(--muted)">сторона $a$<input type="number" id="p10-iv2-a" class="tinp" style="width:100%;margin-top:4px" value="10" step="0.1" min="0.1"></label>
<label style="display:block;font-size:.85rem;color:var(--muted)">угол $A$ (°)<input type="number" id="p10-iv2-A" class="tinp" style="width:100%;margin-top:4px" value="30" step="1" min="1" max="178"></label>
<label style="display:block;font-size:.85rem;color:var(--muted)">угол $B$ (°)<input type="number" id="p10-iv2-B" class="tinp" style="width:100%;margin-top:4px" value="45" step="1" min="1" max="178"></label>
</div>
<div style="text-align:center;margin-bottom:10px"><button class="btn primary" id="p10-iv2-go">Вычислить</button></div>
<div id="p10-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.95rem;min-height:50px;line-height:1.9"></div>
<div class="feedback" id="p10-iv2-fb"></div>
</div>`;
/* IV3 — Что вычислить? */
html += `<div class="wg" id="p10-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какую формулу применить?</div></div>
<div class="wg-help">Дано условие — выбери формулу теоремы синусов, которая поможет найти искомое.</div>
<div class="score-display"><span>Задача <b id="p10-iv3-i">1</b> / 6</span><span>Очки: <b id="p10-iv3-s">0</b> / 6</span></div>
<div id="p10-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px">
<button class="btn primary" data-ans="R" id="p10-iv3-R">$\\dfrac{a}{\\sin A} = 2R$</button>
<button class="btn primary" data-ans="side" id="p10-iv3-side">$a = 2R\\sin A$</button>
<button class="btn primary" data-ans="prop" id="p10-iv3-prop">$\\dfrac{a}{\\sin A} = \\dfrac{b}{\\sin B}$</button>
</div>
<div class="feedback" id="p10-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p10-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр теоремы синусов</div></div>
<div class="wg-help">Реши задачу и введи число (округляй до 2 знаков после запятой).</div>
<div class="score-display"><span>Задача <b id="p10-iv4-i">1</b> / 6</span><span>Очки: <b id="p10-iv4-s">0</b> / 6</span></div>
<div id="p10-iv4-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p10-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.01">
<button class="btn primary" id="p10-iv4-go">Проверить</button>
<button class="btn" id="p10-iv4-start">Заново</button>
</div>
<div class="feedback" id="p10-iv4-fb"></div>
</div>`;
html += readButton('p10');
html += secNav(null, 'p11');
box.innerHTML = html;
renderMath(box);
/* IV1 — Треугольник в описанной окружности */
(function(){
const sA=document.getElementById('p10-iv1-a');
const sB=document.getElementById('p10-iv1-b');
const lA=document.getElementById('p10-iv1-aval');
const lB=document.getElementById('p10-iv1-bval');
const svg=document.getElementById('p10-iv1-svg');
const out=document.getElementById('p10-iv1-out');
const seen=new Set();
const cx=210, cy=200, R=130;
function draw(){
let A=+sA.value, B=+sB.value;
// ограничение чтобы сумма не превышала 170
if(A+B>170){ if(A>B) A=170-B; else B=170-A; }
sA.value=A; sB.value=B;
const C=180-A-B;
lA.textContent=A; lB.textContent=B;
// строим треугольник, вписанный в окружность с углами A, B, C
// выбираем для удобства: вершина CA на угле 210° (центральном) от центра
// Используем: положение вершин на окружности — углы относительно центра 2A, 2B, 2C
// Простой способ: разместим точку A_v внизу слева, B_v внизу справа, C_v вверху
// Углы при центре: arc BC = 2A, arc CA = 2B, arc AB = 2C
// Начнём с A_v на угле theta0=210°, пойдём по окружности
const tA=deg2rad(210);
const tB=tA + deg2rad(2*C); // от A через дугу AB (=2C) до B
const tC=tB + deg2rad(2*A); // от B через дугу BC (=2A) до C
const Av={x:cx+R*Math.cos(tA), y:cy+R*Math.sin(tA)};
const Bv={x:cx+R*Math.cos(tB), y:cy+R*Math.sin(tB)};
const Cv={x:cx+R*Math.cos(tC), y:cy+R*Math.sin(tC)};
const a=Math.hypot(Bv.x-Cv.x,Bv.y-Cv.y);
const b=Math.hypot(Av.x-Cv.x,Av.y-Cv.y);
const c=Math.hypot(Av.x-Bv.x,Av.y-Bv.y);
let s='';
s += '<rect x="0" y="0" width="420" height="400" fill="none"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="rgba(124,58,237,.06)" stroke="#7c3aed" stroke-width="2" stroke-dasharray="6 4"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="3" fill="#7c3aed"/>';
s += '<text x="'+(cx+8)+'" y="'+(cy-6)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#5b21b6">O</text>';
// радиус к одной вершине (показать R)
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+Av.x.toFixed(2)+'" y2="'+Av.y.toFixed(2)+'" stroke="#7c3aed" stroke-width="1.4" stroke-dasharray="3 3"/>';
const mx=(cx+Av.x)/2, my=(cy+Av.y)/2;
s += '<text x="'+mx.toFixed(2)+'" y="'+(my-4).toFixed(2)+'" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#5b21b6">R</text>';
// треугольник
s += '<polygon points="'+Av.x.toFixed(2)+','+Av.y.toFixed(2)+' '+Bv.x.toFixed(2)+','+Bv.y.toFixed(2)+' '+Cv.x.toFixed(2)+','+Cv.y.toFixed(2)+'" fill="rgba(16,185,129,.10)" stroke="#059669" stroke-width="2.2" stroke-linejoin="round"/>';
// вершины и подписи
function lab(P, name, color){
const ux=(P.x-cx)/R, uy=(P.y-cy)/R;
const lx=P.x+ux*18, ly=P.y+uy*18;
return '<circle cx="'+P.x.toFixed(2)+'" cy="'+P.y.toFixed(2)+'" r="4" fill="#0f172a"/>'
+'<text x="'+lx.toFixed(2)+'" y="'+(ly+5).toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="'+color+'">'+name+'</text>';
}
s += lab(Av,'A','#0f172a');
s += lab(Bv,'B','#0f172a');
s += lab(Cv,'C','#0f172a');
svg.innerHTML=s;
const sinA=Math.sin(deg2rad(A))||1e-9;
const ratio = 2*R; // в наших экранных единицах
out.innerHTML = '$A = '+A+'°$, $B = '+B+'°$, $C = '+C+'°$<br>'
+ '$\\dfrac{a}{\\sin A} = \\dfrac{b}{\\sin B} = \\dfrac{c}{\\sin C} = 2R = '+(2*R).toFixed(0)+'$ <span style="color:var(--muted);font-size:.85rem">(в пикселях SVG)</span><br>'
+ '$a \\approx '+a.toFixed(1)+'$, $b \\approx '+b.toFixed(1)+'$, $c \\approx '+c.toFixed(1)+'$ &nbsp;·&nbsp; $a/\\sin A \\approx '+(a/sinA).toFixed(1)+'$';
renderMath(out);
seen.add(A+'|'+B);
if(seen.size>=4 && !seen.has('done')){ addXp(10,'p10-iv1'); bumpProgress('p10',15); seen.add('done'); }
}
[sA,sB].forEach(s=>s.addEventListener('input', draw));
draw();
})();
/* IV2 — Калькулятор */
(function(){
const aI=document.getElementById('p10-iv2-a');
const AI=document.getElementById('p10-iv2-A');
const BI=document.getElementById('p10-iv2-B');
const go=document.getElementById('p10-iv2-go');
const out=document.getElementById('p10-iv2-out');
const fb=document.getElementById('p10-iv2-fb');
let solved=0;
function calc(){
const a=parseFloat(aI.value), A=parseFloat(AI.value), B=parseFloat(BI.value);
if(!isFinite(a)||!isFinite(A)||!isFinite(B)){ feedback(fb,false,'&#10007; Введи все значения.'); return; }
if(a<=0){ feedback(fb,false,'&#10007; Сторона должна быть положительной.'); return; }
if(A<=0||B<=0||A+B>=180){ feedback(fb,false,'&#10007; Углы должны быть положительными, сумма $A + B < 180°$.'); return; }
const C=180-A-B;
const sinA=Math.sin(deg2rad(A)), sinB=Math.sin(deg2rad(B)), sinC=Math.sin(deg2rad(C));
const R=a/(2*sinA);
const b=2*R*sinB;
const c=2*R*sinC;
out.innerHTML = '$C = 180° - A - B = '+C.toFixed(1)+'°$<br>'
+ '$R = \\dfrac{a}{2 \\sin A} = \\dfrac{'+a+'}{2 \\sin '+A+'°} \\approx '+R.toFixed(3)+'$<br>'
+ '$b = 2R \\sin B = 2 \\cdot '+R.toFixed(3)+' \\cdot \\sin '+B+'° \\approx '+b.toFixed(3)+'$<br>'
+ '$c = 2R \\sin C = 2 \\cdot '+R.toFixed(3)+' \\cdot \\sin '+C.toFixed(1)+'° \\approx '+c.toFixed(3)+'$';
renderMath(out);
feedback(fb,true,'&#10003; Готово!');
solved++;
if(solved===1){ addXp(10,'p10-iv2'); bumpProgress('p10',10); }
}
go.addEventListener('click', calc);
})();
/* IV3 — «Какую формулу применить?» */
(function(){
const Q=[
{t:'Известны $a$ и $A$. Найти радиус $R$ описанной окружности.', a:'R'},
{t:'Известны $R$ и угол $A$. Найти сторону $a$.', a:'side'},
{t:'Известны $a$, $A$ и угол $B$. Найти сторону $b$.', a:'prop'},
{t:'Известны $R$ и угол $B$. Найти сторону $b$.', a:'side'},
{t:'Известны $b$, $B$ и угол $C$. Найти сторону $c$.', a:'prop'},
{t:'Известны сторона $c$ и угол $C$. Найти радиус $R$.', a:'R'}
];
const explain={
R:'Прямо $R = \\dfrac{a}{2\\sin A}$ — сторону пропускаем через формулу $2R$.',
side:'Сторона через радиус: $a = 2R \\sin A$.',
prop:'Связываем две стороны через их углы: $\\dfrac{a}{\\sin A} = \\dfrac{b}{\\sin B}$.'
};
const labels={R:'$\\dfrac{a}{\\sin A} = 2R$', side:'$a = 2R\\sin A$', prop:'$\\dfrac{a}{\\sin A} = \\dfrac{b}{\\sin B}$'};
const qBox=document.getElementById('p10-iv3-q');
const iEl=document.getElementById('p10-iv3-i');
const sEl=document.getElementById('p10-iv3-s');
const fb=document.getElementById('p10-iv3-fb');
const btns=document.querySelectorAll('#p10-iv3 button[data-ans]');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Завершено! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p10-iv3'); bumpProgress('p10',25); if(score===Q.length) achievement('p10_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?('&#10003; Верно! '+explain[Q[i].a]):('&#10007; Правильная формула: '+labels[Q[i].a]+'. '+explain[Q[i].a]));
i++;
setTimeout(show, 1100);
}));
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q=[
{t:'$a = 10$, $A = 30°$. Найди радиус $R$ описанной окружности.', a:10, tol:0.02},
{t:'$R = 5$, $A = 90°$. Найди сторону $a$.', a:10, tol:0.02},
{t:'$a = 8$, $A = 45°$, $B = 60°$. Найди сторону $b$.', a:9.80, tol:0.05},
{t:'В прямоугольном треугольнике гипотенуза $c = 10$, угол $C = 90°$. Найди $R$.', a:5, tol:0.02},
{t:'$a = 6$, $A = 30°$, $B = 60°$. Найди сторону $b$.', a:10.39, tol:0.05},
{t:'$b = 12$, $B = 60°$. Найди радиус $R$.', a:6.93, tol:0.05}
];
const qBox=document.getElementById('p10-iv4-q');
const ans=document.getElementById('p10-iv4-ans');
const go=document.getElementById('p10-iv4-go');
const reset=document.getElementById('p10-iv4-start');
const iEl=document.getElementById('p10-iv4-i');
const sEl=document.getElementById('p10-iv4-s');
const fb=document.getElementById('p10-iv4-fb');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Финиш! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p10-iv4'); bumpProgress('p10',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,'&#10007; Введи число.'); return; }
const tol=Q[i].tol||0.05;
const ok=Math.abs(v-Q[i].a)<=tol;
if(ok) score++;
feedback(fb, ok, ok?'&#10003; Верно!':'&#10007; Правильный ответ: $'+Q[i].a+'$');
i++;
setTimeout(show, 1000);
});
reset.addEventListener('click', ()=>{ i=0; score=0; done=false; show(); });
show();
})();
wireReadBtn('p10');
}
/* ===== §11 — Теорема косинусов ===== */
function buildP11(){
const box = document.getElementById('p11-body');
let html = '';
html += makeCard('theory', 'Формулировка теоремы', '11.1', `
<p>В любом треугольнике квадрат стороны равен сумме квадратов двух других сторон минус удвоенное произведение этих сторон на косинус угла между ними:</p>
$$a^2 = b^2 + c^2 - 2bc \\cos A$$
<p>Аналогично — для двух других сторон:</p>
$$b^2 = a^2 + c^2 - 2ac \\cos B, \\qquad c^2 = a^2 + b^2 - 2ab \\cos C$$
<p>Это <b>обобщение теоремы Пифагора</b>: при $A = 90°$ имеем $\\cos A = 0$, и формула превращается в $a^2 = b^2 + c^2$.</p>
<details class="spoiler"><summary>Когда нужна теорема косинусов?</summary><div class="spoiler-body">
Если известны две стороны и угол <b>между ними</b> — третья сторона находится теоремой косинусов. Если известны все три стороны — любой угол находится из неё же. В этих двух случаях теорема синусов <i>не помогает</i> (нет пары «сторона + противолежащий угол»).
</div></details>`);
html += makeCard('rule', 'Применение для нахождения сторон и углов', '11.2', `
<p><b>Случай 1:</b> известны две стороны $b, c$ и угол $A$ между ними. Тогда</p>
$$a = \\sqrt{b^2 + c^2 - 2bc \\cos A}$$
<p><b>Случай 2:</b> известны все три стороны $a, b, c$. Тогда любой угол находится по формуле:</p>
$$\\cos A = \\dfrac{b^2 + c^2 - a^2}{2bc}, \\qquad \\cos B = \\dfrac{a^2 + c^2 - b^2}{2ac}, \\qquad \\cos C = \\dfrac{a^2 + b^2 - c^2}{2ab}$$
<p>Затем $A = \\arccos(\\cos A)$.</p>
<p><b>Удобно знать:</b> если $\\cos A > 0$ — угол $A$ острый, если $\\cos A < 0$ — тупой, если $\\cos A = 0$ — прямой.</p>`);
html += makeCard('example', 'Примеры', '11.3', `
<p><b>Пример 1.</b> Две стороны $b = 5$, $c = 7$, угол между ними $A = 60°$. Найти третью сторону.</p>
$$a^2 = 25 + 49 - 2 \\cdot 5 \\cdot 7 \\cdot \\cos 60° = 74 - 70 \\cdot 0{,}5 = 39$$
$$a = \\sqrt{39} \\approx 6{,}24$$
<p><b>Пример 2.</b> Стороны $3, 4, 5$. Найти угол между сторонами $3$ и $4$ (т.е. противолежащий стороне $5$).</p>
$$\\cos C = \\dfrac{9 + 16 - 25}{2 \\cdot 3 \\cdot 4} = 0 \\Rightarrow C = 90°$$
<p>Это знакомый прямоугольный треугольник 3–4–5.</p>
<p><b>Пример 3.</b> Стороны $7, 8, 13$. Найти наибольший угол (он лежит против $13$).</p>
$$\\cos A = \\dfrac{49 + 64 - 169}{2 \\cdot 7 \\cdot 8} = \\dfrac{-56}{112} = -0{,}5 \\Rightarrow A = 120°$$`);
/* IV1 — Slider угла → одна сторона */
html += `<div class="wg" id="p11-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Угол меняется — сторона тоже</div></div>
<div class="wg-help">Стороны $b = 100$ и $c = 150$ зафиксированы. Меняй угол $A$ между ними — третья сторона $a$ вычисляется по теореме косинусов.</div>
<div class="sliders">
<label>$A$ (°)<b id="p11-iv1-aval">60</b><input type="range" id="p11-iv1-a" min="30" max="150" step="1" value="60"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p11-iv1-svg" viewBox="0 0 400 320" style="width:100%;min-width:320px;height:auto;display:block"></svg>
</div>
<div id="p11-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--pri-soft);border-radius:9px;font-size:.95rem;text-align:center;line-height:1.9"></div>
</div>`;
/* IV2 — Калькулятор */
html += `<div class="wg" id="p11-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор стороны и угла</div></div>
<div class="wg-help">Две формы: одна находит сторону по двум сторонам и углу, другая — угол по трём сторонам.</div>
<div style="font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">Найти сторону $a$</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:8px">
<label style="display:block;font-size:.85rem;color:var(--muted)">сторона $b$<input type="number" id="p11-iv2-b" class="tinp" style="width:100%;margin-top:4px" value="5" step="0.1" min="0.1"></label>
<label style="display:block;font-size:.85rem;color:var(--muted)">сторона $c$<input type="number" id="p11-iv2-c" class="tinp" style="width:100%;margin-top:4px" value="8" step="0.1" min="0.1"></label>
<label style="display:block;font-size:.85rem;color:var(--muted)">угол $A$ (°)<input type="number" id="p11-iv2-A" class="tinp" style="width:100%;margin-top:4px" value="60" step="1" min="1" max="179"></label>
</div>
<div style="text-align:center;margin-bottom:8px"><button class="btn primary" id="p11-iv2-goS">Найти $a$</button></div>
<div id="p11-iv2-outS" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.95rem;min-height:40px;line-height:1.9;margin-bottom:14px"></div>
<div style="font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">Найти угол $A$</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:8px">
<label style="display:block;font-size:.85rem;color:var(--muted)">сторона $a$<input type="number" id="p11-iv2-a2" class="tinp" style="width:100%;margin-top:4px" value="7" step="0.1" min="0.1"></label>
<label style="display:block;font-size:.85rem;color:var(--muted)">сторона $b$<input type="number" id="p11-iv2-b2" class="tinp" style="width:100%;margin-top:4px" value="5" step="0.1" min="0.1"></label>
<label style="display:block;font-size:.85rem;color:var(--muted)">сторона $c$<input type="number" id="p11-iv2-c2" class="tinp" style="width:100%;margin-top:4px" value="8" step="0.1" min="0.1"></label>
</div>
<div style="text-align:center;margin-bottom:8px"><button class="btn primary" id="p11-iv2-goA">Найти $A$</button></div>
<div id="p11-iv2-outA" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.95rem;min-height:40px;line-height:1.9"></div>
<div class="feedback" id="p11-iv2-fb"></div>
</div>`;
/* IV3 — Что найти? */
html += `<div class="wg" id="p11-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Что искать — сторону или угол?</div></div>
<div class="wg-help">Дано условие — выбери, в каком виде применить теорему косинусов: для нахождения стороны или для нахождения угла.</div>
<div class="score-display"><span>Задача <b id="p11-iv3-i">1</b> / 6</span><span>Очки: <b id="p11-iv3-s">0</b> / 6</span></div>
<div id="p11-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:8px">
<button class="btn primary" data-ans="side" id="p11-iv3-side">Для стороны<br><span style="font-size:.78rem;opacity:.85">$a^2 = b^2+c^2-2bc\\cos A$</span></button>
<button class="btn primary" data-ans="angle" id="p11-iv3-angle">Для угла<br><span style="font-size:.78rem;opacity:.85">$\\cos A = \\dfrac{b^2+c^2-a^2}{2bc}$</span></button>
</div>
<div class="feedback" id="p11-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p11-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр теоремы косинусов</div></div>
<div class="wg-help">Реши задачу и введи число (округляй до 2 знаков после запятой).</div>
<div class="score-display"><span>Задача <b id="p11-iv4-i">1</b> / 6</span><span>Очки: <b id="p11-iv4-s">0</b> / 6</span></div>
<div id="p11-iv4-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p11-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.01">
<button class="btn primary" id="p11-iv4-go">Проверить</button>
<button class="btn" id="p11-iv4-start">Заново</button>
</div>
<div class="feedback" id="p11-iv4-fb"></div>
</div>`;
html += readButton('p11');
html += secNav('p10', 'p12');
box.innerHTML = html;
renderMath(box);
/* IV1 — Slider угла A */
(function(){
const sA=document.getElementById('p11-iv1-a');
const lA=document.getElementById('p11-iv1-aval');
const svg=document.getElementById('p11-iv1-svg');
const out=document.getElementById('p11-iv1-out');
const seen=new Set();
const b=100, c=150;
function draw(){
const A=+sA.value; lA.textContent=A;
// A_v в позиции (90, 240), B_v = A_v + (c, 0) = (240, 240). C_v под углом A от A_v на расстояние b.
const Av={x:90, y:240};
const Bv={x:Av.x+c, y:Av.y};
const Cv={x:Av.x+b*Math.cos(deg2rad(A)), y:Av.y-b*Math.sin(deg2rad(A))};
const a=Math.hypot(Bv.x-Cv.x, Bv.y-Cv.y);
let s='';
s += '<rect x="0" y="0" width="400" height="320" fill="none"/>';
// треугольник
s += '<polygon points="'+Av.x.toFixed(2)+','+Av.y.toFixed(2)+' '+Bv.x.toFixed(2)+','+Bv.y.toFixed(2)+' '+Cv.x.toFixed(2)+','+Cv.y.toFixed(2)+'" fill="rgba(124,58,237,.10)" stroke="#7c3aed" stroke-width="2.2" stroke-linejoin="round"/>';
// дуга угла A
const uAB=unitVec(Av,Bv), uAC=unitVec(Av,Cv);
s += '<path d="'+angleArcAuto(Av, uAB, uAC, 24)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
// прямой угол, если A=90
if(Math.abs(A-90)<0.5){
s += '<polyline points="'+rightAngleMark(Av, uAB, uAC, 14)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
}
// подписи сторон
const midAB={x:(Av.x+Bv.x)/2, y:(Av.y+Bv.y)/2};
const midAC={x:(Av.x+Cv.x)/2, y:(Av.y+Cv.y)/2};
const midBC={x:(Bv.x+Cv.x)/2, y:(Bv.y+Cv.y)/2};
s += '<text x="'+midAB.x.toFixed(2)+'" y="'+(midAB.y+18).toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#5b21b6">c = '+c+'</text>';
s += '<text x="'+(midAC.x-18).toFixed(2)+'" y="'+midAC.y.toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#5b21b6">b = '+b+'</text>';
s += '<text x="'+(midBC.x+14).toFixed(2)+'" y="'+midBC.y.toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">a = '+a.toFixed(1)+'</text>';
// подпись угла A
s += '<text x="'+(Av.x+34).toFixed(2)+'" y="'+(Av.y-10).toFixed(2)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">A = '+A+'°</text>';
// вершины
[['A',Av],['B',Bv],['C',Cv]].forEach(([n,P])=>{
s += '<circle cx="'+P.x.toFixed(2)+'" cy="'+P.y.toFixed(2)+'" r="4" fill="#0f172a"/>';
const dx=(n==='A'?-12:(n==='B'?12:0)), dy=(n==='C'?-10:18);
s += '<text x="'+(P.x+dx).toFixed(2)+'" y="'+(P.y+dy).toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#0f172a">'+n+'</text>';
});
svg.innerHTML=s;
const cosA=Math.cos(deg2rad(A));
const a2 = b*b + c*c - 2*b*c*cosA;
let info = '$a^2 = b^2 + c^2 - 2bc \\cos A = '+(b*b)+' + '+(c*c)+' - '+(2*b*c)+' \\cdot \\cos '+A+'° \\approx '+a2.toFixed(1)+'$<br>'
+ '$a \\approx '+Math.sqrt(a2).toFixed(2)+'$';
if(Math.abs(A-90)<0.5) info += '<br><b style="color:#dc2626">&#10003; Прямой угол — это теорема Пифагора!</b>';
out.innerHTML=info;
renderMath(out);
seen.add(A);
if(seen.size>=4 && !seen.has('done')){ addXp(10,'p11-iv1'); bumpProgress('p11',15); seen.add('done'); }
}
sA.addEventListener('input', draw);
draw();
})();
/* IV2 — Калькулятор */
(function(){
const bI=document.getElementById('p11-iv2-b');
const cI=document.getElementById('p11-iv2-c');
const AI=document.getElementById('p11-iv2-A');
const goS=document.getElementById('p11-iv2-goS');
const outS=document.getElementById('p11-iv2-outS');
const aI=document.getElementById('p11-iv2-a2');
const b2I=document.getElementById('p11-iv2-b2');
const c2I=document.getElementById('p11-iv2-c2');
const goA=document.getElementById('p11-iv2-goA');
const outA=document.getElementById('p11-iv2-outA');
const fb=document.getElementById('p11-iv2-fb');
let solvedS=0, solvedA=0;
goS.addEventListener('click', ()=>{
const b=parseFloat(bI.value), c=parseFloat(cI.value), A=parseFloat(AI.value);
if(!isFinite(b)||!isFinite(c)||!isFinite(A)){ feedback(fb,false,'&#10007; Введи все значения.'); return; }
if(b<=0||c<=0){ feedback(fb,false,'&#10007; Стороны должны быть положительными.'); return; }
if(A<=0||A>=180){ feedback(fb,false,'&#10007; Угол $A$ от $0°$ до $180°$.'); return; }
const a2 = b*b + c*c - 2*b*c*Math.cos(deg2rad(A));
const a = Math.sqrt(a2);
outS.innerHTML = '$a^2 = b^2 + c^2 - 2bc \\cos A = '+(b*b).toFixed(2)+' + '+(c*c).toFixed(2)+' - '+(2*b*c).toFixed(2)+' \\cdot \\cos '+A+'° \\approx '+a2.toFixed(3)+'$<br>'
+ '$a \\approx '+a.toFixed(3)+'$';
renderMath(outS);
feedback(fb,true,'&#10003; Сторона найдена.');
solvedS++; if(solvedS+solvedA===1){ addXp(10,'p11-iv2'); bumpProgress('p11',10); }
});
goA.addEventListener('click', ()=>{
const a=parseFloat(aI.value), b=parseFloat(b2I.value), c=parseFloat(c2I.value);
if(!isFinite(a)||!isFinite(b)||!isFinite(c)){ feedback(fb,false,'&#10007; Введи все значения.'); return; }
if(a<=0||b<=0||c<=0){ feedback(fb,false,'&#10007; Стороны должны быть положительными.'); return; }
// проверка неравенства треугольника
if(a+b<=c || a+c<=b || b+c<=a){ feedback(fb,false,'&#10007; Неравенство треугольника не выполняется.'); return; }
let cosA = (b*b + c*c - a*a)/(2*b*c);
if(cosA>1) cosA=1; if(cosA<-1) cosA=-1;
const A = Math.acos(cosA) * 180 / Math.PI;
outA.innerHTML = '$\\cos A = \\dfrac{b^2 + c^2 - a^2}{2bc} = \\dfrac{'+(b*b).toFixed(2)+' + '+(c*c).toFixed(2)+' - '+(a*a).toFixed(2)+'}{'+(2*b*c).toFixed(2)+'} \\approx '+cosA.toFixed(4)+'$<br>'
+ '$A = \\arccos('+cosA.toFixed(4)+') \\approx '+A.toFixed(2)+'°$';
renderMath(outA);
feedback(fb,true,'&#10003; Угол найден.');
solvedA++; if(solvedS+solvedA===1){ addXp(10,'p11-iv2'); bumpProgress('p11',10); }
});
})();
/* IV3 — «Что искать?» */
(function(){
const Q=[
{t:'Известны 2 стороны и угол между ними. Найти третью сторону.', a:'side'},
{t:'Известны 3 стороны. Найти один из углов.', a:'angle'},
{t:'Стороны $b = 5$, $c = 7$, угол $A = 60°$. Найти сторону $a$.', a:'side'},
{t:'Стороны $a = 13$, $b = 14$, $c = 15$. Найти угол $B$.', a:'angle'},
{t:'Стороны $b = 8$, $c = 10$, угол $A = 120°$. Найти сторону $a$.', a:'side'},
{t:'Стороны треугольника $5, 12, 13$. Найти наибольший угол.', a:'angle'}
];
const explain={
side:'$a^2 = b^2 + c^2 - 2bc \\cos A$ — даёт квадрат искомой стороны.',
angle:'$\\cos A = \\dfrac{b^2 + c^2 - a^2}{2bc}$ — даёт косинус искомого угла.'
};
const labels={side:'для стороны', angle:'для угла'};
const qBox=document.getElementById('p11-iv3-q');
const iEl=document.getElementById('p11-iv3-i');
const sEl=document.getElementById('p11-iv3-s');
const fb=document.getElementById('p11-iv3-fb');
const btns=document.querySelectorAll('#p11-iv3 button[data-ans]');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Завершено! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p11-iv3'); bumpProgress('p11',25); if(score===Q.length) achievement('p11_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?('&#10003; Верно! '+explain[Q[i].a]):('&#10007; Правильно: '+labels[Q[i].a]+'. '+explain[Q[i].a]));
i++;
setTimeout(show, 1100);
}));
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q=[
{t:'Стороны $b = 5$, $c = 8$, угол $A = 60°$. Найди $a$.', a:7, tol:0.02},
{t:'Стороны $b = 3$, $c = 4$, угол $A = 90°$. Найди $a$.', a:5, tol:0.02},
{t:'Стороны треугольника $7, 8, 13$. Найди наибольший угол (в градусах).', a:120, tol:0.5},
{t:'Стороны $b = 6$, $c = 10$, угол $A = 120°$. Найди $a$.', a:14, tol:0.02},
{t:'Стороны $3, 5, 7$. Найди угол против стороны $7$ (в градусах).', a:120, tol:0.5},
{t:'Стороны $b = 4$, $c = 6$, угол $A = 45°$. Найди $a^2$.', a:18.06, tol:0.1}
];
const qBox=document.getElementById('p11-iv4-q');
const ans=document.getElementById('p11-iv4-ans');
const go=document.getElementById('p11-iv4-go');
const reset=document.getElementById('p11-iv4-start');
const iEl=document.getElementById('p11-iv4-i');
const sEl=document.getElementById('p11-iv4-s');
const fb=document.getElementById('p11-iv4-fb');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Финиш! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p11-iv4'); bumpProgress('p11',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,'&#10007; Введи число.'); return; }
const tol=Q[i].tol||0.05;
const ok=Math.abs(v-Q[i].a)<=tol;
if(ok) score++;
feedback(fb, ok, ok?'&#10003; Верно!':'&#10007; Правильный ответ: $'+Q[i].a+'$');
i++;
setTimeout(show, 1000);
});
reset.addEventListener('click', ()=>{ i=0; score=0; done=false; show(); });
show();
})();
wireReadBtn('p11');
}
function buildP12(){ _stubBuilder('p12', '§12', 'Формула Герона. Решение треугольников', 'p11', 'final3'); } function buildP12(){ _stubBuilder('p12', '§12', 'Формула Герона. Решение треугольников', 'p11', 'final3'); }
function buildFinal3(){ function buildFinal3(){