feat(geom9 ch1 wave1): §1 «sin/cos/tg острого» + §2 «Решение прямоугольного» + SVG-хелперы
This commit is contained in:
@@ -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 class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||||
};
|
||||
|
||||
/* ===== Геометрические 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='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; 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 '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
|
||||
@@ -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', `
|
||||
<p>Рассмотрим прямоугольный треугольник с острым углом $\\alpha$. Относительно $\\alpha$ катеты делятся на <b>противолежащий</b> (лежит напротив $\\alpha$) и <b>прилежащий</b> (образует $\\alpha$ вместе с гипотенузой).</p>
|
||||
<p>Тригонометрические функции острого угла:</p>
|
||||
<ul style="padding-left:22px;line-height:1.95">
|
||||
<li>$\\sin \\alpha = \\dfrac{\\text{противолежащий катет}}{\\text{гипотенуза}}$</li>
|
||||
<li>$\\cos \\alpha = \\dfrac{\\text{прилежащий катет}}{\\text{гипотенуза}}$</li>
|
||||
<li>$\\tan \\alpha = \\dfrac{\\sin \\alpha}{\\cos \\alpha} = \\dfrac{\\text{противолежащий}}{\\text{прилежащий}}$</li>
|
||||
<li>$\\cot \\alpha = \\dfrac{\\cos \\alpha}{\\sin \\alpha} = \\dfrac{1}{\\tan \\alpha} = \\dfrac{\\text{прилежащий}}{\\text{противолежащий}}$</li>
|
||||
</ul>
|
||||
<details class="spoiler"><summary>Почему отношения, а не «длины»?</summary><div class="spoiler-body">
|
||||
Все прямоугольные треугольники с одним и тем же острым углом $\\alpha$ <b>подобны</b>. У подобных треугольников <b>отношения сторон</b> совпадают. Поэтому $\\sin \\alpha$, $\\cos \\alpha$ и $\\tan \\alpha$ зависят только от угла, а не от размера треугольника.
|
||||
</div></details>`);
|
||||
|
||||
html += makeCard('rule', 'Свойства тригонометрических функций', '1.2', `
|
||||
<p>Для острого угла $\\alpha$ ($0^\\circ < \\alpha < 90^\\circ$):</p>
|
||||
<ul style="padding-left:22px;line-height:1.95">
|
||||
<li><b>Ограничены единицей:</b> $0 < \\sin \\alpha < 1$ и $0 < \\cos \\alpha < 1$ (катет меньше гипотенузы).</li>
|
||||
<li><b>Косинус дополнительного угла:</b> $\\sin \\alpha = \\cos(90^\\circ - \\alpha)$ и $\\cos \\alpha = \\sin(90^\\circ - \\alpha)$.</li>
|
||||
<li><b>Произведение:</b> $\\tan \\alpha \\cdot \\cot \\alpha = 1$.</li>
|
||||
<li><b>Основное тригонометрическое тождество:</b> $\\sin^2 \\alpha + \\cos^2 \\alpha = 1$.</li>
|
||||
</ul>
|
||||
<details class="spoiler"><summary>Откуда тождество $\\sin^2 + \\cos^2 = 1$?</summary><div class="spoiler-body">
|
||||
В прямоугольном треугольнике катеты $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$.
|
||||
</div></details>`);
|
||||
|
||||
html += makeCard('example', 'Эталонные значения 30°, 45°, 60°', '1.3', `
|
||||
<p>Запомни эту таблицу — она встречается во всех задачах:</p>
|
||||
<div style="overflow-x:auto;margin:10px 0">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:.95rem;background:var(--card);border:1.5px solid var(--border);border-radius:9px;overflow:hidden">
|
||||
<thead><tr style="background:var(--pri-soft)">
|
||||
<th style="padding:8px;border-bottom:1px solid var(--border);text-align:left">Угол</th>
|
||||
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\sin$</th>
|
||||
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\cos$</th>
|
||||
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\tan$</th>
|
||||
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\cot$</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="padding:8px;border-bottom:1px solid var(--border)"><b>30°</b></td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{1}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{3}}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{1}{\\sqrt{3}}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\sqrt{3}$</td></tr>
|
||||
<tr><td style="padding:8px;border-bottom:1px solid var(--border)"><b>45°</b></td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{2}}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{2}}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$1$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$1$</td></tr>
|
||||
<tr><td style="padding:8px"><b>60°</b></td><td style="padding:8px;text-align:center">$\\tfrac{\\sqrt{3}}{2}$</td><td style="padding:8px;text-align:center">$\\tfrac{1}{2}$</td><td style="padding:8px;text-align:center">$\\sqrt{3}$</td><td style="padding:8px;text-align:center">$\\tfrac{1}{\\sqrt{3}}$</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p><b>Заметь:</b> $\\sin 30^\\circ = \\cos 60^\\circ$, $\\sin 45^\\circ = \\cos 45^\\circ$ — это и есть «косинус дополнительного угла» из §1.2.</p>`);
|
||||
|
||||
/* IV1 — Конструктор прямоугольного треугольника */
|
||||
html += `<div class="wg" id="p1-iv1">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конструктор прямоугольного треугольника</div></div>
|
||||
<div class="wg-help">Меняй угол $\\alpha$ ползунком — катеты и гипотенуза перестроятся, а внизу появятся значения $\\sin \\alpha$, $\\cos \\alpha$, $\\tan \\alpha$, $\\cot \\alpha$.</div>
|
||||
<div class="sliders">
|
||||
<label>Угол $\\alpha$, °<b id="p1-iv1-aval">40</b><input type="range" id="p1-iv1-a" min="10" max="80" step="1" value="40"></label>
|
||||
</div>
|
||||
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
|
||||
<svg id="p1-iv1-svg" viewBox="0 0 400 320" style="width:100%;min-width:320px;height:auto;display:block"></svg>
|
||||
</div>
|
||||
<div id="p1-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="p1-iv2">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор сторон</div></div>
|
||||
<div class="wg-help">Введи угол $\\alpha$ и гипотенузу $c$ — посчитаем оба катета: $a = c \\sin \\alpha$ (противолежащий) и $b = c \\cos \\alpha$ (прилежащий).</div>
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
|
||||
<span style="font-family:'JetBrains Mono',monospace">$\\alpha$ =</span>
|
||||
<input type="number" id="p1-iv2-a" class="tinp" style="width:90px;text-align:center" value="30" min="10" max="80">
|
||||
<span style="font-family:'JetBrains Mono',monospace">°, $c$ =</span>
|
||||
<input type="number" id="p1-iv2-c" class="tinp" style="width:90px;text-align:center" value="10" min="0.1" step="0.1">
|
||||
<button class="btn primary" id="p1-iv2-go">Найти катеты</button>
|
||||
</div>
|
||||
<div id="p1-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:50px"></div>
|
||||
<div class="feedback" id="p1-iv2-fb"></div>
|
||||
</div>`;
|
||||
|
||||
/* IV3 — Quickfire «Какое отношение?» */
|
||||
html += `<div class="wg" id="p1-iv3">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какое отношение?</div></div>
|
||||
<div class="wg-help">Прямоугольный треугольник с гипотенузой $c$, катетами $a$ (противолежащий $\\alpha$) и $b$ (прилежащий $\\alpha$). Какое из четырёх отношений равно указанному выражению?</div>
|
||||
<div class="score-display"><span>Задача <b id="p1-iv3-i">1</b> / 8</span><span>Очки: <b id="p1-iv3-s">0</b> / 8</span></div>
|
||||
<div id="p1-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.15rem;text-align:center;margin-bottom:10px"></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px">
|
||||
<button class="btn primary" data-ans="ac" id="p1-iv3-ac">$a / c$</button>
|
||||
<button class="btn primary" data-ans="bc" id="p1-iv3-bc">$b / c$</button>
|
||||
<button class="btn primary" data-ans="ab" id="p1-iv3-ab">$a / b$</button>
|
||||
<button class="btn primary" data-ans="ba" id="p1-iv3-ba">$b / a$</button>
|
||||
</div>
|
||||
<div class="feedback" id="p1-iv3-fb"></div>
|
||||
</div>`;
|
||||
|
||||
/* IV4 — Тренажёр */
|
||||
html += `<div class="wg" id="p1-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="p1-iv4-i">1</b> / 6</span><span>Очки: <b id="p1-iv4-s">0</b> / 6</span></div>
|
||||
<div id="p1-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="p1-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
|
||||
<button class="btn primary" id="p1-iv4-go">Проверить</button>
|
||||
<button class="btn" id="p1-iv4-start">Заново</button>
|
||||
</div>
|
||||
<div class="feedback" id="p1-iv4-fb"></div>
|
||||
</div>`;
|
||||
|
||||
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 += '<rect x="0" y="0" width="400" height="320" fill="none"/>';
|
||||
// треугольник
|
||||
s += '<polygon points="'+A.x+','+A.y+' '+B.x+','+B.y+' '+C.x+','+C.y+'" fill="rgba(217,119,6,.08)" stroke="#b45309" stroke-width="2.2" stroke-linejoin="round"/>';
|
||||
// маркер прямого угла в B (внутренние векторы — uBA и uBC)
|
||||
s += '<polyline points="'+rightAngleMark(B, uBA, uBC, 12)+'" fill="none" stroke="#0f172a" stroke-width="1.8"/>';
|
||||
// дуга угла α при C (от CA к CB)
|
||||
s += '<path d="'+angleArcAuto(C, uCA, uCB, 30)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
|
||||
// подпись α
|
||||
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 += '<text x="'+aMid.x+'" y="'+aMid.y+'" text-anchor="middle" dominant-baseline="middle" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#dc2626">α</text>';
|
||||
// вершины
|
||||
s += '<circle cx="'+A.x+'" cy="'+A.y+'" r="4" fill="#0f172a"/>';
|
||||
s += '<circle cx="'+B.x+'" cy="'+B.y+'" r="4" fill="#0f172a"/>';
|
||||
s += '<circle cx="'+C.x+'" cy="'+C.y+'" r="4" fill="#0f172a"/>';
|
||||
s += '<text x="'+(A.x-12)+'" y="'+(A.y+18)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">A</text>';
|
||||
s += '<text x="'+(B.x-12)+'" y="'+(B.y-4)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">B</text>';
|
||||
s += '<text x="'+(C.x+12)+'" y="'+(C.y+18)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">C</text>';
|
||||
// подписи сторон
|
||||
const labBC = 'BC='+(BCpx/22).toFixed(2);
|
||||
const labAB = 'AB='+(ABpx/22).toFixed(2);
|
||||
const labAC = 'AC='+(c/22).toFixed(2);
|
||||
s += '<text x="'+((B.x+C.x)/2)+'" y="'+(cyBase+34)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">'+labBC+'</text>';
|
||||
s += '<text x="'+(B.x-32)+'" y="'+((A.y+B.y)/2)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">'+labAB+'</text>';
|
||||
// подпись гипотенузы — поднимем над линией 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 += '<text x="'+labP.x+'" y="'+labP.y+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">'+labAC+'</text>';
|
||||
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)+'$<br>'
|
||||
+ '$\\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 = '<b>$a = c \\sin \\alpha = '+c+' \\cdot \\sin '+aDeg+'^\\circ \\approx '+a.toFixed(2)+'$</b> (противолежащий)<br>'
|
||||
+ '<b>$b = c \\cos \\alpha = '+c+' \\cdot \\cos '+aDeg+'^\\circ \\approx '+b.toFixed(2)+'$</b> (прилежащий)';
|
||||
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 = '<b>Готово!</b> Результат: ' + 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 = '<b>Готово!</b> Результат: ' + 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', `
|
||||
<p><b>Решить треугольник</b> — найти все его неизвестные элементы (стороны и углы) по заданным.</p>
|
||||
<p>В прямоугольном треугольнике один угол всегда равен $90^\\circ$. Поэтому, чтобы найти все остальные элементы, достаточно знать <b>любые два из них</b> (кроме случая «два угла» — там стороны определяются с точностью до подобия):</p>
|
||||
<ul style="padding-left:22px;line-height:1.95">
|
||||
<li>две стороны (два катета; катет и гипотенузу), <b>или</b></li>
|
||||
<li>одну сторону и один из острых углов.</li>
|
||||
</ul>
|
||||
<p>Обозначения: пусть $A, B, C$ — вершины (угол $C = 90^\\circ$), $a = BC$ — катет, лежащий напротив $A$, $b = AC$ — катет, лежащий напротив $B$, $c = AB$ — гипотенуза. Тогда $A + B = 90^\\circ$.</p>`);
|
||||
|
||||
html += makeCard('rule', 'Четыре случая решения', '2.2', `
|
||||
<p>В каждом случае схема одна: применяем теорему Пифагора или тригонометрическую функцию.</p>
|
||||
<p><b>Случай 1.</b> Даны два катета $a, b$.<br>
|
||||
$c = \\sqrt{a^2 + b^2}$; $\\tan A = \\dfrac{a}{b} \\Rightarrow A$; $B = 90^\\circ - A$.</p>
|
||||
<p><b>Случай 2.</b> Даны катет $a$ и гипотенуза $c$.<br>
|
||||
$b = \\sqrt{c^2 - a^2}$; $\\sin A = \\dfrac{a}{c} \\Rightarrow A$; $B = 90^\\circ - A$.</p>
|
||||
<p><b>Случай 3.</b> Даны катет $a$ и противолежащий угол $A$.<br>
|
||||
$c = \\dfrac{a}{\\sin A}$; $b = \\dfrac{a}{\\tan A}$; $B = 90^\\circ - A$.</p>
|
||||
<p><b>Случай 4.</b> Даны гипотенуза $c$ и угол $A$.<br>
|
||||
$a = c \\sin A$; $b = c \\cos A$; $B = 90^\\circ - A$.</p>
|
||||
<details class="spoiler"><summary>Совет по выбору формулы</summary><div class="spoiler-body">
|
||||
<ul style="padding-left:18px;line-height:1.85;margin:0">
|
||||
<li>Есть <b>обе</b> стороны без угла — теорема Пифагора + $\\tan$.</li>
|
||||
<li>Есть <b>гипотенуза и катет</b> — Пифагор для второго катета, $\\sin$ для угла.</li>
|
||||
<li>Есть <b>сторона и угол</b> — выбирай функцию так, чтобы данный отрезок встал в числитель или знаменатель: $\\sin$ для пары «противолежащий–гипотенуза», $\\cos$ — «прилежащий–гипотенуза», $\\tan$ — два катета.</li>
|
||||
</ul>
|
||||
</div></details>`);
|
||||
|
||||
html += makeCard('example', 'Пример: даны два катета', '2.3', `
|
||||
<p><b>Дано:</b> $a = 6$, $b = 8$. <b>Найти:</b> $c$, $A$, $B$.</p>
|
||||
<p><b>1.</b> $c = \\sqrt{a^2 + b^2} = \\sqrt{36 + 64} = \\sqrt{100} = 10$.</p>
|
||||
<p><b>2.</b> $\\tan A = \\dfrac{a}{b} = \\dfrac{6}{8} = 0{,}75 \\Rightarrow A \\approx 36{,}87^\\circ$.</p>
|
||||
<p><b>3.</b> $B = 90^\\circ - A \\approx 53{,}13^\\circ$.</p>
|
||||
<p><b>Ответ:</b> $a = 6$, $b = 8$, $c = 10$, $A \\approx 36{,}87^\\circ$, $B \\approx 53{,}13^\\circ$.</p>
|
||||
<details class="spoiler"><summary>Проверка через $\\sin$</summary><div class="spoiler-body">
|
||||
$\\sin A = a / c = 6/10 = 0{,}6 \\Rightarrow A = \\arcsin 0{,}6 \\approx 36{,}87^\\circ$ — совпадает.
|
||||
</div></details>`);
|
||||
|
||||
/* IV1 — Универсальный решатель */
|
||||
html += `<div class="wg" id="p2-iv1">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Универсальный решатель</div></div>
|
||||
<div class="wg-help">Выбери тип задачи и введи данные — найдём все остальные элементы с подстановкой формул.</div>
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px" id="p2-iv1-cases">
|
||||
<button class="btn primary" data-c="1">Случай 1: $a, b$</button>
|
||||
<button class="btn" data-c="2">Случай 2: $a, c$</button>
|
||||
<button class="btn" data-c="3">Случай 3: $a, A$</button>
|
||||
<button class="btn" data-c="4">Случай 4: $c, A$</button>
|
||||
</div>
|
||||
<div id="p2-iv1-inputs" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px"></div>
|
||||
<div style="text-align:center;margin-bottom:10px"><button class="btn primary" id="p2-iv1-go">Решить</button></div>
|
||||
<div id="p2-iv1-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;line-height:1.75;min-height:50px"></div>
|
||||
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto;margin-top:10px">
|
||||
<svg id="p2-iv1-svg" viewBox="0 0 400 300" style="width:100%;min-width:320px;height:auto;display:block"></svg>
|
||||
</div>
|
||||
<div class="feedback" id="p2-iv1-fb"></div>
|
||||
</div>`;
|
||||
|
||||
/* IV2 — DnD сортер «Подбор формулы» */
|
||||
html += `<div class="wg" id="p2-iv2">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Подбор формулы по случаю</div></div>
|
||||
<div class="wg-help">Распредели 6 формул по четырём случаям решения. Тапни карточку, потом — нужный ящик (или перетащи).</div>
|
||||
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20"/><path d="M5 12h14"/></svg> 6 формул — 4 ящика</div>
|
||||
<div id="p2-iv2-pool"></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-top:8px">
|
||||
<div class="drop-box"><h5 data-cat="c1">Случай 1 ($a, b$)</h5><div class="drop-items" data-cat="c1"></div></div>
|
||||
<div class="drop-box"><h5 data-cat="c2">Случай 2 ($a, c$)</h5><div class="drop-items" data-cat="c2"></div></div>
|
||||
<div class="drop-box"><h5 data-cat="c3">Случай 3 ($a, A$)</h5><div class="drop-items" data-cat="c3"></div></div>
|
||||
<div class="drop-box"><h5 data-cat="c4">Случай 4 ($c, A$)</h5><div class="drop-items" data-cat="c4"></div></div>
|
||||
</div>
|
||||
<div class="actions"><button class="btn primary" id="p2-iv2-check">Проверить</button><button class="btn" id="p2-iv2-reset">Сначала</button></div>
|
||||
<div class="feedback" id="p2-iv2-fb"></div>
|
||||
</div>`;
|
||||
|
||||
/* IV3 — Какой угол найдём? */
|
||||
html += `<div class="wg" id="p2-iv3">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой функцией найдём угол?</div></div>
|
||||
<div class="wg-help">Дано — нужно выбрать тригонометрическую функцию, через которую быстрее всего найти угол $A$.</div>
|
||||
<div class="score-display"><span>Задача <b id="p2-iv3-i">1</b> / 6</span><span>Очки: <b id="p2-iv3-s">0</b> / 6</span></div>
|
||||
<div id="p2-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(130px,1fr));gap:8px">
|
||||
<button class="btn primary" data-ans="sin" id="p2-iv3-sin">через $\\sin$</button>
|
||||
<button class="btn primary" data-ans="cos" id="p2-iv3-cos">через $\\cos$</button>
|
||||
<button class="btn primary" data-ans="tan" id="p2-iv3-tan">через $\\tan$</button>
|
||||
</div>
|
||||
<div class="feedback" id="p2-iv3-fb"></div>
|
||||
</div>`;
|
||||
|
||||
/* IV4 — Тренажёр решения */
|
||||
html += `<div class="wg" id="p2-iv4">
|
||||
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр решения треугольника</div></div>
|
||||
<div class="wg-help">Реши задачу и введи число (округли до 1 знака, если получается дробное).</div>
|
||||
<div class="score-display"><span>Задача <b id="p2-iv4-i">1</b> / 5</span><span>Очки: <b id="p2-iv4-s">0</b> / 5</span></div>
|
||||
<div id="p2-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="p2-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.1">
|
||||
<button class="btn primary" id="p2-iv4-go">Проверить</button>
|
||||
<button class="btn" id="p2-iv4-start">Заново</button>
|
||||
</div>
|
||||
<div class="feedback" id="p2-iv4-fb"></div>
|
||||
</div>`;
|
||||
|
||||
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$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="6"> $b$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="8">'; }
|
||||
else if(curCase === 2){ h = '$a$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="6"> $c$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="10">'; }
|
||||
else if(curCase === 3){ h = '$a$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="5"> $A$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="30">°'; }
|
||||
else { h = '$c$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="10"> $A$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="30">°'; }
|
||||
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 += '<polygon points="'+A2.x+','+A2.y+' '+B2.x+','+B2.y+' '+C2.x+','+C2.y+'" fill="rgba(217,119,6,.08)" stroke="#b45309" stroke-width="2.2" stroke-linejoin="round"/>';
|
||||
// прямой угол в C
|
||||
s += '<polyline points="'+rightAngleMark(C2, uCA, uCB, 12)+'" fill="none" stroke="#0f172a" stroke-width="1.8"/>';
|
||||
// угол A (от AC к AB)
|
||||
s += '<path d="'+angleArcAuto(A2, uAC, uAB, 28)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
|
||||
// вершины
|
||||
s += '<circle cx="'+A2.x+'" cy="'+A2.y+'" r="4" fill="#0f172a"/>';
|
||||
s += '<circle cx="'+B2.x+'" cy="'+B2.y+'" r="4" fill="#0f172a"/>';
|
||||
s += '<circle cx="'+C2.x+'" cy="'+C2.y+'" r="4" fill="#0f172a"/>';
|
||||
s += '<text x="'+(A2.x-12)+'" y="'+(A2.y+18)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="16" font-weight="700">A</text>';
|
||||
s += '<text x="'+(B2.x+12)+'" y="'+(B2.y-4)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700">B</text>';
|
||||
s += '<text x="'+(C2.x+12)+'" y="'+(C2.y+18)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700">C</text>';
|
||||
// подписи сторон
|
||||
s += '<text x="'+((A2.x+C2.x)/2)+'" y="'+(C2.y+22)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">b='+b.toFixed(2)+'</text>';
|
||||
s += '<text x="'+(C2.x+22)+'" y="'+((B2.y+C2.y)/2)+'" text-anchor="start" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">a='+a.toFixed(2)+'</text>';
|
||||
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 += '<text x="'+labP.x+'" y="'+labP.y+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">c='+c.toFixed(2)+'</text>';
|
||||
s += '<text x="'+(A2.x+24)+'" y="'+(A2.y-10)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">A='+ADeg.toFixed(1)+'°</text>';
|
||||
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 = '<b>$c = \\sqrt{a^2+b^2} = \\sqrt{'+(a*a)+'+'+(b*b)+'} = \\sqrt{'+(a*a+b*b)+'} \\approx '+c.toFixed(2)+'$</b><br>'
|
||||
+ '$\\tan A = a/b = '+a+'/'+b+' \\approx '+(a/b).toFixed(3)+' \\Rightarrow A \\approx '+ADeg.toFixed(2)+'^\\circ$<br>'
|
||||
+ '$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>$b = \\sqrt{c^2-a^2} = \\sqrt{'+(c*c)+'-'+(a*a)+'} = \\sqrt{'+(c*c-a*a)+'} \\approx '+b.toFixed(2)+'$</b><br>'
|
||||
+ '$\\sin A = a/c = '+a+'/'+c+' \\approx '+(a/c).toFixed(3)+' \\Rightarrow A \\approx '+ADeg.toFixed(2)+'^\\circ$<br>'
|
||||
+ '$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 = '<b>$c = a/\\sin A = '+a+'/\\sin '+ADeg+'^\\circ \\approx '+c.toFixed(2)+'$</b><br>'
|
||||
+ '<b>$b = a/\\tan A = '+a+'/\\tan '+ADeg+'^\\circ \\approx '+b.toFixed(2)+'$</b><br>'
|
||||
+ '$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 = '<b>$a = c \\sin A = '+c+' \\cdot \\sin '+ADeg+'^\\circ \\approx '+a.toFixed(2)+'$</b><br>'
|
||||
+ '<b>$b = c \\cos A = '+c+' \\cdot \\cos '+ADeg+'^\\circ \\approx '+b.toFixed(2)+'$</b><br>'
|
||||
+ '$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 = '<b>Готово!</b> Результат: ' + 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 = '<b>Готово!</b> Результат: ' + 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'); }
|
||||
|
||||
Reference in New Issue
Block a user