diff --git a/frontend/textbooks/geometry_9_ch1.html b/frontend/textbooks/geometry_9_ch1.html index af03fb9..9d51763 100644 --- a/frontend/textbooks/geometry_9_ch1.html +++ b/frontend/textbooks/geometry_9_ch1.html @@ -103,6 +103,46 @@ a{color:inherit;text-decoration:none} .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)} +.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} +.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s} +.dnd-pool.over{border-color:var(--pri);background:var(--pri-soft);border-style:solid} +.dnd-pool.col{flex-direction:column;align-items:stretch} +.dnd-pool.col .dnd-chip{width:auto} +.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%} +.dnd-chip:hover{transform:translateY(-1px);border-color:var(--pri);box-shadow:var(--sh)} +.dnd-chip:active{cursor:grabbing} +.dnd-chip.armed{border-color:var(--pri);background:var(--pri-soft);box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)} +.dnd-chip.dragging{opacity:.28} +.dnd-chip.placed{background:var(--pri-soft);border-color:var(--pri)} +.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer} +.dnd-chip .dnd-x:hover{color:var(--bad);background:var(--fail-bg)} +.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s} +.drop-box:hover{border-color:var(--pri);background:var(--pri-soft)} +.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--pri2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em} +.drop-box.over{border-color:var(--pri);background:var(--pri-soft);border-style:solid;transform:scale(1.015)} +.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px} +.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px} +.dnd-hint svg{width:14px;height:14px;flex-shrink:0} + .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 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)} @@ -403,6 +443,49 @@ const ICONS = { oral:'', }; +/* ===== Геометрические SVG-хелперы (используются во всей главе) ===== */ +// L-маркер прямого угла (polyline ВНУТРЬ угла). +// V — вершина прямого угла; uIn, wIn — единичные векторы в SVG-координатах вдоль двух катетов. +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; +} +// Дуга угла с автовыбором sweep (через cross product). +// V — вершина, uA/uB — единичные векторы вдоль сторон угла (в SVG-координатах), R — радиус. +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; +} +// Нормализованный вектор от p1 к p2 (в координатах того же пространства). +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; } +function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; } + +/* ===== DnD сортер ===== */ +function setupSorter(cfg){ + const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector); + if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}}; + pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col'); + let armed = null; + function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML=''+it.html+'\xd7'; attach(e,it.id); return e; } + function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return; + ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); } + function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); } + function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} } + attachBoxTaps(); render(); + return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }}; +} + function makeCard(kind, title, num, body){ const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'}; return '
'+ICONS[kind]+'
'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'
'+(num?'
'+num+'
':'')+'
'+body+'
'; @@ -447,8 +530,651 @@ function _stubBuilder(paraId, num, name, prev, next){ if(window.renderMathInElement) renderMath(body); } -function buildP1(){ _stubBuilder('p1', '§1', 'sin, cos, tg, ctg острого угла', null, 'p2'); } -function buildP2(){ _stubBuilder('p2', '§2', 'Решение прямоугольного треугольника', 'p1', 'p3'); } +/* ===== §1 sin, cos, tg, ctg острого угла ===== */ +function buildP1(){ + const box = document.getElementById('p1-body'); + let html = ''; + + html += makeCard('theory', 'Определения', '1.1', ` +

Рассмотрим прямоугольный треугольник с острым углом $\\alpha$. Относительно $\\alpha$ катеты делятся на противолежащий (лежит напротив $\\alpha$) и прилежащий (образует $\\alpha$ вместе с гипотенузой).

+

Тригонометрические функции острого угла:

+ +
Почему отношения, а не «длины»?
+ Все прямоугольные треугольники с одним и тем же острым углом $\\alpha$ подобны. У подобных треугольников отношения сторон совпадают. Поэтому $\\sin \\alpha$, $\\cos \\alpha$ и $\\tan \\alpha$ зависят только от угла, а не от размера треугольника. +
`); + + html += makeCard('rule', 'Свойства тригонометрических функций', '1.2', ` +

Для острого угла $\\alpha$ ($0^\\circ < \\alpha < 90^\\circ$):

+ +
Откуда тождество $\\sin^2 + \\cos^2 = 1$?
+ В прямоугольном треугольнике катеты $a, b$ и гипотенуза $c$. По Пифагору $a^2 + b^2 = c^2$. Поделим на $c^2$: $\\left(\\dfrac{a}{c}\\right)^2 + \\left(\\dfrac{b}{c}\\right)^2 = 1$, то есть $\\sin^2 \\alpha + \\cos^2 \\alpha = 1$. +
`); + + html += makeCard('example', 'Эталонные значения 30°, 45°, 60°', '1.3', ` +

Запомни эту таблицу — она встречается во всех задачах:

+
+ + + + + + + + + + + + + +
Угол$\\sin$$\\cos$$\\tan$$\\cot$
30°$\\tfrac{1}{2}$$\\tfrac{\\sqrt{3}}{2}$$\\tfrac{1}{\\sqrt{3}}$$\\sqrt{3}$
45°$\\tfrac{\\sqrt{2}}{2}$$\\tfrac{\\sqrt{2}}{2}$$1$$1$
60°$\\tfrac{\\sqrt{3}}{2}$$\\tfrac{1}{2}$$\\sqrt{3}$$\\tfrac{1}{\\sqrt{3}}$
+
+

Заметь: $\\sin 30^\\circ = \\cos 60^\\circ$, $\\sin 45^\\circ = \\cos 45^\\circ$ — это и есть «косинус дополнительного угла» из §1.2.

`); + + /* IV1 — Конструктор прямоугольного треугольника */ + html += `
+
ИНТЕРАКТИВ 1
Конструктор прямоугольного треугольника
+
Меняй угол $\\alpha$ ползунком — катеты и гипотенуза перестроятся, а внизу появятся значения $\\sin \\alpha$, $\\cos \\alpha$, $\\tan \\alpha$, $\\cot \\alpha$.
+
+ +
+
+ +
+
+
`; + + /* IV2 — Калькулятор сторон */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор сторон
+
Введи угол $\\alpha$ и гипотенузу $c$ — посчитаем оба катета: $a = c \\sin \\alpha$ (противолежащий) и $b = c \\cos \\alpha$ (прилежащий).
+
+ $\\alpha$ = + + °,  $c$ = + + +
+
+
+
`; + + /* IV3 — Quickfire «Какое отношение?» */ + html += `
+
ИНТЕРАКТИВ 3
Какое отношение?
+
Прямоугольный треугольник с гипотенузой $c$, катетами $a$ (противолежащий $\\alpha$) и $b$ (прилежащий $\\alpha$). Какое из четырёх отношений равно указанному выражению?
+
Задача 1 / 8Очки: 0 / 8
+
+
+ + + + +
+
+
`; + + /* IV4 — Тренажёр */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр
+
Реши задачу и введи число (округли до 2 знаков, если получается дробное).
+
Задача 1 / 6Очки: 0 / 6
+
+
+ ответ = + + + +
+
+
`; + + html += secNav(null, 'p2'); + html += readButton('p1'); + + box.innerHTML = html; + renderMath(box); + + /* IV1 — слайдер + SVG */ + (function(){ + const sl = document.getElementById('p1-iv1-a'); + const lab = document.getElementById('p1-iv1-aval'); + const svg = document.getElementById('p1-iv1-svg'); + const out = document.getElementById('p1-iv1-out'); + const seen = new Set(); + function draw(){ + const aDeg = +sl.value; + lab.textContent = aDeg; + const aRad = deg2rad(aDeg); + const c = 220; // гипотенуза в пикселях + // Геометрические вершины: A (внизу слева), B (верх — прямой угол), C (внизу справа). + // Прямой угол при B. Угол α при C. + // BC = c·cos α (горизонтальный катет, прилежащий к α) + // AB = c·sin α (вертикальный катет, противолежащий α) + const BCpx = c * Math.cos(aRad); + const ABpx = c * Math.sin(aRad); + const cx = 60, cyBase = 270; // позиция A + const A = {x: cx, y: cyBase}; + const C = {x: cx + BCpx, y: cyBase}; + const B = {x: cx, y: cyBase - ABpx}; + // Внутренние векторы в B (прямой угол): по BA — вниз, по BC — вправо-вниз + const uBA = unitVec(B, A); + const uBC = unitVec(B, C); + const uCA = unitVec(C, A); // вдоль гипотенузы из C + const uCB = unitVec(C, B); // вдоль катета из C + let s = ''; + // фон + s += ''; + // треугольник + s += ''; + // маркер прямого угла в B (внутренние векторы — uBA и uBC) + s += ''; + // дуга угла α при C (от CA к CB) + s += ''; + // подпись α + const aMid = {x: C.x + 44*Math.cos(Math.atan2((uCA.y+uCB.y)/2,(uCA.x+uCB.x)/2)), y: C.y + 44*Math.sin(Math.atan2((uCA.y+uCB.y)/2,(uCA.x+uCB.x)/2))}; + s += 'α'; + // вершины + s += ''; + s += ''; + s += ''; + s += 'A'; + s += 'B'; + s += 'C'; + // подписи сторон + const labBC = 'BC='+(BCpx/22).toFixed(2); + const labAB = 'AB='+(ABpx/22).toFixed(2); + const labAC = 'AC='+(c/22).toFixed(2); + s += ''+labBC+''; + s += ''+labAB+''; + // подпись гипотенузы — поднимем над линией AC + const midAC = {x:(A.x+C.x)/2, y:(A.y+C.y)/2}; + const nAC = {x:-(C.y-A.y), y:(C.x-A.x)}; + const nL = Math.sqrt(nAC.x*nAC.x+nAC.y*nAC.y)||1; + const labP = {x: midAC.x + 16*nAC.x/nL, y: midAC.y + 16*nAC.y/nL}; + s += ''+labAC+''; + svg.innerHTML = s; + // числовые значения + const sn = Math.sin(aRad), cs = Math.cos(aRad), tn = Math.tan(aRad), ct = 1/Math.tan(aRad); + out.innerHTML = '$\\sin '+aDeg+'^\\circ \\approx '+sn.toFixed(3)+'$  ·  $\\cos '+aDeg+'^\\circ \\approx '+cs.toFixed(3)+'$
' + + '$\\tan '+aDeg+'^\\circ \\approx '+tn.toFixed(3)+'$  ·  $\\cot '+aDeg+'^\\circ \\approx '+ct.toFixed(3)+'$'; + renderMath(out); + seen.add(aDeg); + if(seen.size >= 6 && !seen.has('done')){ addXp(10,'p1-iv1'); bumpProgress('p1', 15); seen.add('done'); } + } + sl.addEventListener('input', draw); + draw(); + })(); + + /* IV2 — калькулятор сторон */ + (function(){ + const aI = document.getElementById('p1-iv2-a'); + const cI = document.getElementById('p1-iv2-c'); + const go = document.getElementById('p1-iv2-go'); + const out= document.getElementById('p1-iv2-out'); + const fb = document.getElementById('p1-iv2-fb'); + let solved = 0; + function calc(){ + const aDeg = parseFloat(aI.value), c = parseFloat(cI.value); + if(isNaN(aDeg) || isNaN(c)){ feedback(fb, false, '✗ Введи число для $\\alpha$ и $c$.'); return; } + if(aDeg<=0 || aDeg>=90){ feedback(fb, false, '✗ Угол должен быть в диапазоне (0°; 90°). Лучше 10..80.'); return; } + if(c<=0){ feedback(fb, false, '✗ Гипотенуза должна быть положительной.'); return; } + const r = deg2rad(aDeg); + const a = c * Math.sin(r); + const b = c * Math.cos(r); + out.innerHTML = '$a = c \\sin \\alpha = '+c+' \\cdot \\sin '+aDeg+'^\\circ \\approx '+a.toFixed(2)+'$ (противолежащий)
' + + '$b = c \\cos \\alpha = '+c+' \\cdot \\cos '+aDeg+'^\\circ \\approx '+b.toFixed(2)+'$ (прилежащий)'; + renderMath(out); + feedback(fb, true, '✓ Катеты найдены.'); + solved++; + if(solved === 1){ addXp(10,'p1-iv2'); bumpProgress('p1', 15); } + } + go.addEventListener('click', calc); + calc(); + })(); + + /* IV3 — Какое отношение? */ + (function(){ + const Q = [ + { expr:'$\\sin \\alpha$', ans:'ac', why:'противолежащий $a$ к гипотенузе $c$' }, + { expr:'$\\cos \\alpha$', ans:'bc', why:'прилежащий $b$ к гипотенузе $c$' }, + { expr:'$\\tan \\alpha$', ans:'ab', why:'противолежащий $a$ к прилежащему $b$' }, + { expr:'$\\cot \\alpha$', ans:'ba', why:'прилежащий $b$ к противолежащему $a$' }, + { expr:'$\\cos(90^\\circ - \\alpha)$', ans:'ac', why:'$= \\sin \\alpha = a/c$' }, + { expr:'$\\sin(90^\\circ - \\alpha)$', ans:'bc', why:'$= \\cos \\alpha = b/c$' }, + { expr:'$\\dfrac{1}{\\cot \\alpha}$', ans:'ab', why:'$= \\tan \\alpha = a/b$' }, + { expr:'$\\dfrac{\\sin \\alpha \\cdot \\cos \\alpha}{\\sin \\alpha}$', ans:'bc', why:'$= \\cos \\alpha = b/c$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p1-iv3-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p1-iv3'); bumpProgress('p1', 25); } + else if(score >= Q.length - 2){ addXp(8,'p1-iv3'); bumpProgress('p1', 15); } + return; + } + document.getElementById('p1-iv3-i').textContent = (i+1); + document.getElementById('p1-iv3-s').textContent = score; + document.getElementById('p1-iv3-q').innerHTML = Q[i].expr; + renderMath(document.getElementById('p1-iv3-q')); + document.getElementById('p1-iv3-fb').style.display = 'none'; + } + function answer(a){ + if(i >= Q.length) return; + const fb = document.getElementById('p1-iv3-fb'); + if(a === Q[i].ans){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+'. Дальше ▶'); } + else feedback(fb, false, '✗ Нет. '+Q[i].why+'. Дальше ▶'); + document.getElementById('p1-iv3-s').textContent = score; + i++; + setTimeout(show, 1100); + } + ['ac','bc','ab','ba'].forEach(k=>{ + const b = document.getElementById('p1-iv3-'+k); if(b) b.addEventListener('click', ()=>answer(k)); + }); + show(); + })(); + + /* IV4 — Тренажёр */ + (function(){ + const Q = [ + { q:'$\\sin 30^\\circ = ?$', ans:0.5, tol:0.02, hint:'эталон: $\\sin 30^\\circ = 1/2$' }, + { q:'$\\cos 60^\\circ = ?$', ans:0.5, tol:0.02, hint:'эталон: $\\cos 60^\\circ = 1/2$' }, + { q:'В прямоуг. треугольнике катет $a = 3$, гипотенуза $c = 5$. Найди $\\sin \\alpha$ ($\\alpha$ — угол, противолежащий $a$).', ans:0.6, tol:0.02, hint:'$\\sin \\alpha = a/c = 3/5 = 0{,}6$' }, + { q:'В прямоуг. треугольнике катеты 3 и 4. Чему равна гипотенуза?', ans:5, tol:0.05, hint:'$c = \\sqrt{9+16} = \\sqrt{25} = 5$' }, + { q:'$\\tan 45^\\circ = ?$', ans:1, tol:0.02, hint:'эталон: $\\tan 45^\\circ = 1$' }, + { q:'$c = 10$, $\\alpha = 30^\\circ$. Найди катет, противолежащий $\\alpha$.', ans:5, tol:0.05, hint:'$a = c \\sin \\alpha = 10 \\cdot 0{,}5 = 5$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p1-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p1-iv4'); bumpProgress('p1', 25); } + else if(score >= 4){ addXp(8,'p1-iv4'); bumpProgress('p1', 15); } + return; + } + document.getElementById('p1-iv4-i').textContent = (i+1); + document.getElementById('p1-iv4-s').textContent = score; + document.getElementById('p1-iv4-q').innerHTML = Q[i].q; + document.getElementById('p1-iv4-ans').value = ''; + renderMath(document.getElementById('p1-iv4-q')); + document.getElementById('p1-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p1-iv4-fb'); + const ans = parseFloat(document.getElementById('p1-iv4-ans').value); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; } + if(Math.abs(ans - Q[i].ans) <= Q[i].tol){ score++; feedback(fb, true, '✓ Верно! '+Q[i].hint+'. Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Ответ: '+Q[i].ans+' ('+Q[i].hint+'). Дальше ▶'); + document.getElementById('p1-iv4-s').textContent = score; + i++; + setTimeout(show, 1300); + } + document.getElementById('p1-iv4-go').addEventListener('click', go); + document.getElementById('p1-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); }); + document.getElementById('p1-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); }); + show(); + })(); + + wireReadBtn('p1'); +} + +/* ===== §2 Решение прямоугольного треугольника ===== */ +function buildP2(){ + const box = document.getElementById('p2-body'); + let html = ''; + + html += makeCard('theory', 'Что значит «решить треугольник»', '2.1', ` +

Решить треугольник — найти все его неизвестные элементы (стороны и углы) по заданным.

+

В прямоугольном треугольнике один угол всегда равен $90^\\circ$. Поэтому, чтобы найти все остальные элементы, достаточно знать любые два из них (кроме случая «два угла» — там стороны определяются с точностью до подобия):

+
    +
  • две стороны (два катета; катет и гипотенузу), или
  • +
  • одну сторону и один из острых углов.
  • +
+

Обозначения: пусть $A, B, C$ — вершины (угол $C = 90^\\circ$), $a = BC$ — катет, лежащий напротив $A$, $b = AC$ — катет, лежащий напротив $B$, $c = AB$ — гипотенуза. Тогда $A + B = 90^\\circ$.

`); + + html += makeCard('rule', 'Четыре случая решения', '2.2', ` +

В каждом случае схема одна: применяем теорему Пифагора или тригонометрическую функцию.

+

Случай 1. Даны два катета $a, b$.
+ $c = \\sqrt{a^2 + b^2}$;   $\\tan A = \\dfrac{a}{b} \\Rightarrow A$;   $B = 90^\\circ - A$.

+

Случай 2. Даны катет $a$ и гипотенуза $c$.
+ $b = \\sqrt{c^2 - a^2}$;   $\\sin A = \\dfrac{a}{c} \\Rightarrow A$;   $B = 90^\\circ - A$.

+

Случай 3. Даны катет $a$ и противолежащий угол $A$.
+ $c = \\dfrac{a}{\\sin A}$;   $b = \\dfrac{a}{\\tan A}$;   $B = 90^\\circ - A$.

+

Случай 4. Даны гипотенуза $c$ и угол $A$.
+ $a = c \\sin A$;   $b = c \\cos A$;   $B = 90^\\circ - A$.

+
Совет по выбору формулы
+
    +
  • Есть обе стороны без угла — теорема Пифагора + $\\tan$.
  • +
  • Есть гипотенуза и катет — Пифагор для второго катета, $\\sin$ для угла.
  • +
  • Есть сторона и угол — выбирай функцию так, чтобы данный отрезок встал в числитель или знаменатель: $\\sin$ для пары «противолежащий–гипотенуза», $\\cos$ — «прилежащий–гипотенуза», $\\tan$ — два катета.
  • +
+
`); + + html += makeCard('example', 'Пример: даны два катета', '2.3', ` +

Дано: $a = 6$, $b = 8$. Найти: $c$, $A$, $B$.

+

1. $c = \\sqrt{a^2 + b^2} = \\sqrt{36 + 64} = \\sqrt{100} = 10$.

+

2. $\\tan A = \\dfrac{a}{b} = \\dfrac{6}{8} = 0{,}75 \\Rightarrow A \\approx 36{,}87^\\circ$.

+

3. $B = 90^\\circ - A \\approx 53{,}13^\\circ$.

+

Ответ: $a = 6$, $b = 8$, $c = 10$, $A \\approx 36{,}87^\\circ$, $B \\approx 53{,}13^\\circ$.

+
Проверка через $\\sin$
+ $\\sin A = a / c = 6/10 = 0{,}6 \\Rightarrow A = \\arcsin 0{,}6 \\approx 36{,}87^\\circ$ — совпадает. +
`); + + /* IV1 — Универсальный решатель */ + html += `
+
ИНТЕРАКТИВ 1
Универсальный решатель
+
Выбери тип задачи и введи данные — найдём все остальные элементы с подстановкой формул.
+
+ + + + +
+
+
+
+
+ +
+ +
`; + + /* IV2 — DnD сортер «Подбор формулы» */ + html += `
+
ИНТЕРАКТИВ 2
Подбор формулы по случаю
+
Распредели 6 формул по четырём случаям решения. Тапни карточку, потом — нужный ящик (или перетащи).
+
6 формул — 4 ящика
+
+
+
Случай 1 ($a, b$)
+
Случай 2 ($a, c$)
+
Случай 3 ($a, A$)
+
Случай 4 ($c, A$)
+
+
+ +
`; + + /* IV3 — Какой угол найдём? */ + html += `
+
ИНТЕРАКТИВ 3
Какой функцией найдём угол?
+
Дано — нужно выбрать тригонометрическую функцию, через которую быстрее всего найти угол $A$.
+
Задача 1 / 6Очки: 0 / 6
+
+
+ + + +
+ +
`; + + /* IV4 — Тренажёр решения */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр решения треугольника
+
Реши задачу и введи число (округли до 1 знака, если получается дробное).
+
Задача 1 / 5Очки: 0 / 5
+
+
+ ответ = + + + +
+ +
`; + + html += secNav('p1', 'p3'); + html += readButton('p2'); + + box.innerHTML = html; + renderMath(box); + + /* IV1 — универсальный решатель */ + (function(){ + let curCase = 1; + const inputsBox = document.getElementById('p2-iv1-inputs'); + const out = document.getElementById('p2-iv1-out'); + const fb = document.getElementById('p2-iv1-fb'); + const svg = document.getElementById('p2-iv1-svg'); + const caseBtns = document.querySelectorAll('#p2-iv1-cases button'); + let solved = 0; + function setInputs(){ + let h = ''; + if(curCase === 1){ h = '$a$ =   $b$ = '; } + else if(curCase === 2){ h = '$a$ =   $c$ = '; } + else if(curCase === 3){ h = '$a$ =   $A$ = °'; } + else { h = '$c$ =   $A$ = °'; } + inputsBox.innerHTML = h; + renderMath(inputsBox); + } + function drawTri(a, b, c, ADeg){ + const aR = deg2rad(ADeg); + // нарисуем по c и углу A: BC=a (вертикаль), AC=b (горизонталь), угол A внизу слева + const scale = Math.min(200/Math.max(b,1), 160/Math.max(a,1), 22); + const Bx = 80, By = 250; // вершина B (прямой угол) внизу слева + const Ax = Bx, Ay = By; // ...A совпадёт ниже + // ставим: B (низ-лев, прямой угол), C (низ-прав, угол A?). Свяжем по стандарту: C=90°. + // На этот раз: C = (low-right) — прямой угол; A = (low-left); B = (top-right). + // a = BC (противолежащий A) — вертикальный; b = AC — горизонтальный. + const Cx = Bx + b*scale, Cy = By; + const A2 = {x: Bx, y: By}; + const C2 = {x: Cx, y: Cy}; + const B2 = {x: Cx, y: Cy - a*scale}; + const uCA = unitVec(C2, A2); + const uCB = unitVec(C2, B2); + const uAB = unitVec(A2, B2); + const uAC = unitVec(A2, C2); + let s = ''; + s += ''; + // прямой угол в C + s += ''; + // угол A (от AC к AB) + s += ''; + // вершины + s += ''; + s += ''; + s += ''; + s += 'A'; + s += 'B'; + s += 'C'; + // подписи сторон + s += 'b='+b.toFixed(2)+''; + s += 'a='+a.toFixed(2)+''; + const midAB = {x:(A2.x+B2.x)/2, y:(A2.y+B2.y)/2}; + const nAB = {x:-(B2.y-A2.y), y:(B2.x-A2.x)}; + const nL = Math.sqrt(nAB.x*nAB.x+nAB.y*nAB.y)||1; + const labP = {x:midAB.x - 18*nAB.x/nL, y:midAB.y - 18*nAB.y/nL}; + s += 'c='+c.toFixed(2)+''; + s += 'A='+ADeg.toFixed(1)+'°'; + svg.innerHTML = s; + } + function solve(){ + const v1 = parseFloat(document.getElementById('p2-iv1-i1').value); + const v2 = parseFloat(document.getElementById('p2-iv1-i2').value); + if(isNaN(v1) || isNaN(v2)){ feedback(fb, false, '✗ Введи оба числа.'); return; } + let a, b, c, ADeg, BDeg, html; + if(curCase === 1){ + a = v1; b = v2; + if(a<=0||b<=0){ feedback(fb,false,'✗ Стороны должны быть положительны.'); return; } + c = Math.sqrt(a*a+b*b); + ADeg = Math.atan2(a,b) * 180 / Math.PI; + BDeg = 90 - ADeg; + html = '$c = \\sqrt{a^2+b^2} = \\sqrt{'+(a*a)+'+'+(b*b)+'} = \\sqrt{'+(a*a+b*b)+'} \\approx '+c.toFixed(2)+'$
' + + '$\\tan A = a/b = '+a+'/'+b+' \\approx '+(a/b).toFixed(3)+' \\Rightarrow A \\approx '+ADeg.toFixed(2)+'^\\circ$
' + + '$B = 90^\\circ - A \\approx '+BDeg.toFixed(2)+'^\\circ$'; + } else if(curCase === 2){ + a = v1; c = v2; + if(a<=0||c<=0||a>=c){ feedback(fb,false,'✗ Нужно $0 < a < c$.'); return; } + b = Math.sqrt(c*c-a*a); + ADeg = Math.asin(a/c) * 180 / Math.PI; + BDeg = 90 - ADeg; + html = '$b = \\sqrt{c^2-a^2} = \\sqrt{'+(c*c)+'-'+(a*a)+'} = \\sqrt{'+(c*c-a*a)+'} \\approx '+b.toFixed(2)+'$
' + + '$\\sin A = a/c = '+a+'/'+c+' \\approx '+(a/c).toFixed(3)+' \\Rightarrow A \\approx '+ADeg.toFixed(2)+'^\\circ$
' + + '$B = 90^\\circ - A \\approx '+BDeg.toFixed(2)+'^\\circ$'; + } else if(curCase === 3){ + a = v1; ADeg = v2; + if(a<=0||ADeg<=0||ADeg>=90){ feedback(fb,false,'✗ Нужно $a > 0$ и $0 < A < 90°$.'); return; } + const r = deg2rad(ADeg); + c = a / Math.sin(r); + b = a / Math.tan(r); + BDeg = 90 - ADeg; + html = '$c = a/\\sin A = '+a+'/\\sin '+ADeg+'^\\circ \\approx '+c.toFixed(2)+'$
' + + '$b = a/\\tan A = '+a+'/\\tan '+ADeg+'^\\circ \\approx '+b.toFixed(2)+'$
' + + '$B = 90^\\circ - A = '+BDeg.toFixed(2)+'^\\circ$'; + } else { + c = v1; ADeg = v2; + if(c<=0||ADeg<=0||ADeg>=90){ feedback(fb,false,'✗ Нужно $c > 0$ и $0 < A < 90°$.'); return; } + const r = deg2rad(ADeg); + a = c * Math.sin(r); + b = c * Math.cos(r); + BDeg = 90 - ADeg; + html = '$a = c \\sin A = '+c+' \\cdot \\sin '+ADeg+'^\\circ \\approx '+a.toFixed(2)+'$
' + + '$b = c \\cos A = '+c+' \\cdot \\cos '+ADeg+'^\\circ \\approx '+b.toFixed(2)+'$
' + + '$B = 90^\\circ - A = '+BDeg.toFixed(2)+'^\\circ$'; + } + out.innerHTML = html; + renderMath(out); + drawTri(a, b, c, ADeg); + feedback(fb, true, '✓ Треугольник решён.'); + solved++; + if(solved === 1){ addXp(10,'p2-iv1'); bumpProgress('p2', 15); } + } + caseBtns.forEach(b=>{ + b.addEventListener('click', ()=>{ + curCase = +b.dataset.c; + caseBtns.forEach(x=>{ x.classList.remove('primary'); x.classList.add('btn'); }); + b.classList.remove('btn'); b.classList.add('btn','primary'); + setInputs(); + out.innerHTML = ''; svg.innerHTML=''; + fb.style.display = 'none'; + }); + }); + document.getElementById('p2-iv1-go').addEventListener('click', solve); + setInputs(); + solve(); + })(); + + /* IV2 — DnD сортер «Подбор формулы» */ + (function(){ + const items = [ + { id:'f1', cat:'c1', html:'$c = \\sqrt{a^2 + b^2}$' }, + { id:'f2', cat:'c1', html:'$\\tan A = a / b$' }, + { id:'f3', cat:'c2', html:'$b = \\sqrt{c^2 - a^2}$' }, + { id:'f4', cat:'c3', html:'$c = a / \\sin A$' }, + { id:'f5', cat:'c4', html:'$a = c \\sin A$' }, + { id:'f6', cat:'c4', html:'$b = c \\cos A$' }, + ]; + const sorter = setupSorter({ + poolId:'p2-iv2-pool', + scopeSelector:'#p2-iv2', + items: items, + cats:['c1','c2','c3','c4'], + columnLayout:false, + }); + document.getElementById('p2-iv2-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p2-iv2-fb'); + const placedCount = items.filter(it => sorter.placed[it.id]).length; + const correct = items.filter(it => sorter.placed[it.id] === it.cat).length; + if(placedCount < items.length){ feedback(fb, false, '✗ Размести все 6 формул.'); return; } + if(correct === items.length){ feedback(fb, true, '✓ Все 6 на месте! +10 XP'); addXp(10,'p2-iv2'); bumpProgress('p2', 15); } + else feedback(fb, false, '✗ Правильно ' + correct + ' из 6. Попробуй ещё.'); + }); + document.getElementById('p2-iv2-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv2-fb').style.display = 'none'; }); + })(); + + /* IV3 — Какой функцией найдём угол */ + (function(){ + const Q = [ + { expr:'Даны $a$ (противолежащий $A$) и гипотенуза $c$.', ans:'sin', why:'$\\sin A = a/c$' }, + { expr:'Даны $b$ (прилежащий $A$) и гипотенуза $c$.', ans:'cos', why:'$\\cos A = b/c$' }, + { expr:'Даны оба катета $a$ и $b$.', ans:'tan', why:'$\\tan A = a/b$' }, + { expr:'Известно произведение $c \\sin A$ и значение $c$. Найти $A$.', ans:'sin', why:'$\\sin A = (c \\sin A)/c$' }, + { expr:'Даны два катета (противолежащий и прилежащий).', ans:'tan', why:'$\\tan A$ — отношение катетов' }, + { expr:'Даны противолежащий катет и гипотенуза.', ans:'sin', why:'$\\sin A = \\text{противолежащий}/\\text{гипотенуза}$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p2-iv3-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p2-iv3'); bumpProgress('p2', 25); } + else if(score >= Q.length - 1){ addXp(8,'p2-iv3'); bumpProgress('p2', 15); } + return; + } + document.getElementById('p2-iv3-i').textContent = (i+1); + document.getElementById('p2-iv3-s').textContent = score; + document.getElementById('p2-iv3-q').innerHTML = Q[i].expr; + renderMath(document.getElementById('p2-iv3-q')); + document.getElementById('p2-iv3-fb').style.display = 'none'; + } + function answer(a){ + if(i >= Q.length) return; + const fb = document.getElementById('p2-iv3-fb'); + if(a === Q[i].ans){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+'. Дальше ▶'); } + else feedback(fb, false, '✗ Нет. Нужно: '+Q[i].why+'. Дальше ▶'); + document.getElementById('p2-iv3-s').textContent = score; + i++; + setTimeout(show, 1100); + } + ['sin','cos','tan'].forEach(k=>{ + const b = document.getElementById('p2-iv3-'+k); if(b) b.addEventListener('click', ()=>answer(k)); + }); + show(); + })(); + + /* IV4 — Тренажёр решения */ + (function(){ + const Q = [ + { q:'Катеты 3 и 4. Чему равна гипотенуза?', ans:5, tol:0.1, hint:'$c = \\sqrt{9+16} = 5$' }, + { q:'Катет $5$, гипотенуза $13$. Найди второй катет.', ans:12, tol:0.1, hint:'$b = \\sqrt{169-25} = \\sqrt{144} = 12$' }, + { q:'В прямоуг. треуг. $c = 20$, $A = 30^\\circ$. Найди катет $a$ (противолежащий $A$).', ans:10, tol:0.1, hint:'$a = c \\sin A = 20 \\cdot 0{,}5 = 10$' }, + { q:'$c = 10$, $a = 6$. Найди угол $A$ в градусах (округли до целых).', ans:37, tol:1, hint:'$\\sin A = 0{,}6 \\Rightarrow A \\approx 36{,}87^\\circ \\approx 37^\\circ$' }, + { q:'Катет $a = 3$, угол $A = 45^\\circ$. Чему равна гипотенуза?', ans:4.2, tol:0.2, hint:'$c = a/\\sin 45^\\circ = 3\\sqrt{2} \\approx 4{,}24$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p2-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p2-iv4'); bumpProgress('p2', 25); } + else if(score >= 3){ addXp(8,'p2-iv4'); bumpProgress('p2', 15); } + return; + } + document.getElementById('p2-iv4-i').textContent = (i+1); + document.getElementById('p2-iv4-s').textContent = score; + document.getElementById('p2-iv4-q').innerHTML = Q[i].q; + document.getElementById('p2-iv4-ans').value = ''; + renderMath(document.getElementById('p2-iv4-q')); + document.getElementById('p2-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p2-iv4-fb'); + const ans = parseFloat(document.getElementById('p2-iv4-ans').value); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; } + if(Math.abs(ans - Q[i].ans) <= Q[i].tol){ score++; feedback(fb, true, '✓ Верно! '+Q[i].hint+'. Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Ответ: '+Q[i].ans+' ('+Q[i].hint+'). Дальше ▶'); + document.getElementById('p2-iv4-s').textContent = score; + i++; + setTimeout(show, 1300); + } + document.getElementById('p2-iv4-go').addEventListener('click', go); + document.getElementById('p2-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); }); + document.getElementById('p2-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); }); + show(); + })(); + + wireReadBtn('p2'); +} function buildP3(){ _stubBuilder('p3', '§3', 'Тригонометрические формулы', 'p2', 'p4'); } function buildP4(){ _stubBuilder('p4', '§4', 'sin, cos, tg, ctg тупого угла', 'p3', 'p5'); } function buildP5(){ _stubBuilder('p5', '§5', 'Формулы площади', 'p4', 'p6'); }